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::Undrop(u) => self.generate_undrop(u),
3376            Expression::AlterTable(at) => self.generate_alter_table(at),
3377            Expression::CreateIndex(ci) => self.generate_create_index(ci),
3378            Expression::DropIndex(di) => self.generate_drop_index(di),
3379            Expression::CreateView(cv) => self.generate_create_view(cv),
3380            Expression::DropView(dv) => self.generate_drop_view(dv),
3381            Expression::AlterView(av) => self.generate_alter_view(av),
3382            Expression::AlterIndex(ai) => self.generate_alter_index(ai),
3383            Expression::Truncate(tr) => self.generate_truncate(tr),
3384            Expression::Use(u) => self.generate_use(u),
3385            // Phase 4: Additional DDL statements
3386            Expression::CreateSchema(cs) => self.generate_create_schema(cs),
3387            Expression::DropSchema(ds) => self.generate_drop_schema(ds),
3388            Expression::DropNamespace(dn) => self.generate_drop_namespace(dn),
3389            Expression::CreateDatabase(cd) => self.generate_create_database(cd),
3390            Expression::DropDatabase(dd) => self.generate_drop_database(dd),
3391            Expression::CreateFunction(cf) => self.generate_create_function(cf),
3392            Expression::DropFunction(df) => self.generate_drop_function(df),
3393            Expression::CreateProcedure(cp) => self.generate_create_procedure(cp),
3394            Expression::DropProcedure(dp) => self.generate_drop_procedure(dp),
3395            Expression::CreateSequence(cs) => self.generate_create_sequence(cs),
3396            Expression::CreateSynonym(cs) => {
3397                self.write_keyword("CREATE SYNONYM");
3398                self.write_space();
3399                self.generate_table(&cs.name)?;
3400                self.write_space();
3401                self.write_keyword("FOR");
3402                self.write_space();
3403                self.generate_table(&cs.target)?;
3404                Ok(())
3405            }
3406            Expression::DropSequence(ds) => self.generate_drop_sequence(ds),
3407            Expression::AlterSequence(als) => self.generate_alter_sequence(als),
3408            Expression::CreateTrigger(ct) => self.generate_create_trigger(ct),
3409            Expression::DropTrigger(dt) => self.generate_drop_trigger(dt),
3410            Expression::CreateType(ct) => self.generate_create_type(ct),
3411            Expression::DropType(dt) => self.generate_drop_type(dt),
3412            Expression::Describe(d) => self.generate_describe(d),
3413            Expression::Show(s) => self.generate_show(s),
3414
3415            // CACHE/UNCACHE/LOAD TABLE (Spark/Hive)
3416            Expression::Cache(c) => self.generate_cache(c),
3417            Expression::Uncache(u) => self.generate_uncache(u),
3418            Expression::LoadData(l) => self.generate_load_data(l),
3419            Expression::Pragma(p) => self.generate_pragma(p),
3420            Expression::Grant(g) => self.generate_grant(g),
3421            Expression::Revoke(r) => self.generate_revoke(r),
3422            Expression::Comment(c) => self.generate_comment(c),
3423            Expression::SetStatement(s) => self.generate_set_statement(s),
3424
3425            // PIVOT/UNPIVOT
3426            Expression::Pivot(pivot) => self.generate_pivot(pivot),
3427            Expression::Unpivot(unpivot) => self.generate_unpivot(unpivot),
3428
3429            // VALUES table constructor
3430            Expression::Values(values) => self.generate_values(values),
3431
3432            // === BATCH-GENERATED MATCH ARMS (481 variants) ===
3433            Expression::AIAgg(e) => self.generate_ai_agg(e),
3434            Expression::AIClassify(e) => self.generate_ai_classify(e),
3435            Expression::AddPartition(e) => self.generate_add_partition(e),
3436            Expression::AlgorithmProperty(e) => self.generate_algorithm_property(e),
3437            Expression::Aliases(e) => self.generate_aliases(e),
3438            Expression::AllowedValuesProperty(e) => self.generate_allowed_values_property(e),
3439            Expression::AlterColumn(e) => self.generate_alter_column(e),
3440            Expression::AlterSession(e) => self.generate_alter_session(e),
3441            Expression::AlterSet(e) => self.generate_alter_set(e),
3442            Expression::AlterSortKey(e) => self.generate_alter_sort_key(e),
3443            Expression::Analyze(e) => self.generate_analyze(e),
3444            Expression::AnalyzeDelete(e) => self.generate_analyze_delete(e),
3445            Expression::AnalyzeHistogram(e) => self.generate_analyze_histogram(e),
3446            Expression::AnalyzeListChainedRows(e) => self.generate_analyze_list_chained_rows(e),
3447            Expression::AnalyzeSample(e) => self.generate_analyze_sample(e),
3448            Expression::AnalyzeStatistics(e) => self.generate_analyze_statistics(e),
3449            Expression::AnalyzeValidate(e) => self.generate_analyze_validate(e),
3450            Expression::AnalyzeWith(e) => self.generate_analyze_with(e),
3451            Expression::Anonymous(e) => self.generate_anonymous(e),
3452            Expression::AnonymousAggFunc(e) => self.generate_anonymous_agg_func(e),
3453            Expression::Apply(e) => self.generate_apply(e),
3454            Expression::ApproxPercentileEstimate(e) => self.generate_approx_percentile_estimate(e),
3455            Expression::ApproxQuantile(e) => self.generate_approx_quantile(e),
3456            Expression::ApproxQuantiles(e) => self.generate_approx_quantiles(e),
3457            Expression::ApproxTopK(e) => self.generate_approx_top_k(e),
3458            Expression::ApproxTopKAccumulate(e) => self.generate_approx_top_k_accumulate(e),
3459            Expression::ApproxTopKCombine(e) => self.generate_approx_top_k_combine(e),
3460            Expression::ApproxTopKEstimate(e) => self.generate_approx_top_k_estimate(e),
3461            Expression::ApproxTopSum(e) => self.generate_approx_top_sum(e),
3462            Expression::ArgMax(e) => self.generate_arg_max(e),
3463            Expression::ArgMin(e) => self.generate_arg_min(e),
3464            Expression::ArrayAll(e) => self.generate_array_all(e),
3465            Expression::ArrayAny(e) => self.generate_array_any(e),
3466            Expression::ArrayConstructCompact(e) => self.generate_array_construct_compact(e),
3467            Expression::ArraySum(e) => self.generate_array_sum(e),
3468            Expression::AtIndex(e) => self.generate_at_index(e),
3469            Expression::Attach(e) => self.generate_attach(e),
3470            Expression::AttachOption(e) => self.generate_attach_option(e),
3471            Expression::AutoIncrementProperty(e) => self.generate_auto_increment_property(e),
3472            Expression::AutoRefreshProperty(e) => self.generate_auto_refresh_property(e),
3473            Expression::BackupProperty(e) => self.generate_backup_property(e),
3474            Expression::Base64DecodeBinary(e) => self.generate_base64_decode_binary(e),
3475            Expression::Base64DecodeString(e) => self.generate_base64_decode_string(e),
3476            Expression::Base64Encode(e) => self.generate_base64_encode(e),
3477            Expression::BlockCompressionProperty(e) => self.generate_block_compression_property(e),
3478            Expression::Booland(e) => self.generate_booland(e),
3479            Expression::Boolor(e) => self.generate_boolor(e),
3480            Expression::BuildProperty(e) => self.generate_build_property(e),
3481            Expression::ByteString(e) => self.generate_byte_string(e),
3482            Expression::CaseSpecificColumnConstraint(e) => {
3483                self.generate_case_specific_column_constraint(e)
3484            }
3485            Expression::CastToStrType(e) => self.generate_cast_to_str_type(e),
3486            Expression::Changes(e) => self.generate_changes(e),
3487            Expression::CharacterSetColumnConstraint(e) => {
3488                self.generate_character_set_column_constraint(e)
3489            }
3490            Expression::CharacterSetProperty(e) => self.generate_character_set_property(e),
3491            Expression::CheckColumnConstraint(e) => self.generate_check_column_constraint(e),
3492            Expression::AssumeColumnConstraint(e) => self.generate_assume_column_constraint(e),
3493            Expression::CheckJson(e) => self.generate_check_json(e),
3494            Expression::CheckXml(e) => self.generate_check_xml(e),
3495            Expression::ChecksumProperty(e) => self.generate_checksum_property(e),
3496            Expression::Clone(e) => self.generate_clone(e),
3497            Expression::ClusterBy(e) => self.generate_cluster_by(e),
3498            Expression::ClusterByColumnsProperty(e) => self.generate_cluster_by_columns_property(e),
3499            Expression::ClusteredByProperty(e) => self.generate_clustered_by_property(e),
3500            Expression::CollateProperty(e) => self.generate_collate_property(e),
3501            Expression::ColumnConstraint(e) => self.generate_column_constraint(e),
3502            Expression::ColumnDef(e) => self.generate_column_def_expr(e),
3503            Expression::ColumnPosition(e) => self.generate_column_position(e),
3504            Expression::ColumnPrefix(e) => self.generate_column_prefix(e),
3505            Expression::Columns(e) => self.generate_columns(e),
3506            Expression::CombinedAggFunc(e) => self.generate_combined_agg_func(e),
3507            Expression::CombinedParameterizedAgg(e) => self.generate_combined_parameterized_agg(e),
3508            Expression::Commit(e) => self.generate_commit(e),
3509            Expression::Comprehension(e) => self.generate_comprehension(e),
3510            Expression::Compress(e) => self.generate_compress(e),
3511            Expression::CompressColumnConstraint(e) => self.generate_compress_column_constraint(e),
3512            Expression::ComputedColumnConstraint(e) => self.generate_computed_column_constraint(e),
3513            Expression::ConditionalInsert(e) => self.generate_conditional_insert(e),
3514            Expression::Constraint(e) => self.generate_constraint(e),
3515            Expression::ConvertTimezone(e) => self.generate_convert_timezone(e),
3516            Expression::ConvertToCharset(e) => self.generate_convert_to_charset(e),
3517            Expression::Copy(e) => self.generate_copy(e),
3518            Expression::CopyParameter(e) => self.generate_copy_parameter(e),
3519            Expression::Corr(e) => self.generate_corr(e),
3520            Expression::CosineDistance(e) => self.generate_cosine_distance(e),
3521            Expression::CovarPop(e) => self.generate_covar_pop(e),
3522            Expression::CovarSamp(e) => self.generate_covar_samp(e),
3523            Expression::Credentials(e) => self.generate_credentials(e),
3524            Expression::CredentialsProperty(e) => self.generate_credentials_property(e),
3525            Expression::Cte(e) => self.generate_cte(e),
3526            Expression::Cube(e) => self.generate_cube(e),
3527            Expression::CurrentDatetime(e) => self.generate_current_datetime(e),
3528            Expression::CurrentSchema(e) => self.generate_current_schema(e),
3529            Expression::CurrentSchemas(e) => self.generate_current_schemas(e),
3530            Expression::CurrentUser(e) => self.generate_current_user(e),
3531            Expression::DPipe(e) => self.generate_d_pipe(e),
3532            Expression::DataBlocksizeProperty(e) => self.generate_data_blocksize_property(e),
3533            Expression::DataDeletionProperty(e) => self.generate_data_deletion_property(e),
3534            Expression::Date(e) => self.generate_date_func(e),
3535            Expression::DateBin(e) => self.generate_date_bin(e),
3536            Expression::DateFormatColumnConstraint(e) => {
3537                self.generate_date_format_column_constraint(e)
3538            }
3539            Expression::DateFromParts(e) => self.generate_date_from_parts(e),
3540            Expression::Datetime(e) => self.generate_datetime(e),
3541            Expression::DatetimeAdd(e) => self.generate_datetime_add(e),
3542            Expression::DatetimeDiff(e) => self.generate_datetime_diff(e),
3543            Expression::DatetimeSub(e) => self.generate_datetime_sub(e),
3544            Expression::DatetimeTrunc(e) => self.generate_datetime_trunc(e),
3545            Expression::Dayname(e) => self.generate_dayname(e),
3546            Expression::Declare(e) => self.generate_declare(e),
3547            Expression::DeclareItem(e) => self.generate_declare_item(e),
3548            Expression::DecodeCase(e) => self.generate_decode_case(e),
3549            Expression::DecompressBinary(e) => self.generate_decompress_binary(e),
3550            Expression::DecompressString(e) => self.generate_decompress_string(e),
3551            Expression::Decrypt(e) => self.generate_decrypt(e),
3552            Expression::DecryptRaw(e) => self.generate_decrypt_raw(e),
3553            Expression::DefaultColumnConstraint(e) => {
3554                self.write_keyword("DEFAULT");
3555                self.write_space();
3556                self.generate_expression(&e.this)?;
3557                if let Some(ref col) = e.for_column {
3558                    self.write_space();
3559                    self.write_keyword("FOR");
3560                    self.write_space();
3561                    self.generate_identifier(col)?;
3562                }
3563                Ok(())
3564            }
3565            Expression::DefinerProperty(e) => self.generate_definer_property(e),
3566            Expression::Detach(e) => self.generate_detach(e),
3567            Expression::DictProperty(e) => self.generate_dict_property(e),
3568            Expression::DictRange(e) => self.generate_dict_range(e),
3569            Expression::Directory(e) => self.generate_directory(e),
3570            Expression::DistKeyProperty(e) => self.generate_dist_key_property(e),
3571            Expression::DistStyleProperty(e) => self.generate_dist_style_property(e),
3572            Expression::DistributeBy(e) => self.generate_distribute_by(e),
3573            Expression::DistributedByProperty(e) => self.generate_distributed_by_property(e),
3574            Expression::DotProduct(e) => self.generate_dot_product(e),
3575            Expression::DropPartition(e) => self.generate_drop_partition(e),
3576            Expression::DuplicateKeyProperty(e) => self.generate_duplicate_key_property(e),
3577            Expression::Elt(e) => self.generate_elt(e),
3578            Expression::Encode(e) => self.generate_encode(e),
3579            Expression::EncodeProperty(e) => self.generate_encode_property(e),
3580            Expression::Encrypt(e) => self.generate_encrypt(e),
3581            Expression::EncryptRaw(e) => self.generate_encrypt_raw(e),
3582            Expression::EngineProperty(e) => self.generate_engine_property(e),
3583            Expression::EnviromentProperty(e) => self.generate_enviroment_property(e),
3584            Expression::EphemeralColumnConstraint(e) => {
3585                self.generate_ephemeral_column_constraint(e)
3586            }
3587            Expression::EqualNull(e) => self.generate_equal_null(e),
3588            Expression::EuclideanDistance(e) => self.generate_euclidean_distance(e),
3589            Expression::ExecuteAsProperty(e) => self.generate_execute_as_property(e),
3590            Expression::Export(e) => self.generate_export(e),
3591            Expression::ExternalProperty(e) => self.generate_external_property(e),
3592            Expression::FallbackProperty(e) => self.generate_fallback_property(e),
3593            Expression::FarmFingerprint(e) => self.generate_farm_fingerprint(e),
3594            Expression::FeaturesAtTime(e) => self.generate_features_at_time(e),
3595            Expression::Fetch(e) => self.generate_fetch(e),
3596            Expression::FileFormatProperty(e) => self.generate_file_format_property(e),
3597            Expression::Filter(e) => self.generate_filter(e),
3598            Expression::Float64(e) => self.generate_float64(e),
3599            Expression::ForIn(e) => self.generate_for_in(e),
3600            Expression::ForeignKey(e) => self.generate_foreign_key(e),
3601            Expression::Format(e) => self.generate_format(e),
3602            Expression::FormatPhrase(e) => self.generate_format_phrase(e),
3603            Expression::FreespaceProperty(e) => self.generate_freespace_property(e),
3604            Expression::From(e) => self.generate_from(e),
3605            Expression::FromBase(e) => self.generate_from_base(e),
3606            Expression::FromTimeZone(e) => self.generate_from_time_zone(e),
3607            Expression::GapFill(e) => self.generate_gap_fill(e),
3608            Expression::GenerateDateArray(e) => self.generate_generate_date_array(e),
3609            Expression::GenerateEmbedding(e) => self.generate_generate_embedding(e),
3610            Expression::GenerateSeries(e) => self.generate_generate_series(e),
3611            Expression::GenerateTimestampArray(e) => self.generate_generate_timestamp_array(e),
3612            Expression::GeneratedAsIdentityColumnConstraint(e) => {
3613                self.generate_generated_as_identity_column_constraint(e)
3614            }
3615            Expression::GeneratedAsRowColumnConstraint(e) => {
3616                self.generate_generated_as_row_column_constraint(e)
3617            }
3618            Expression::Get(e) => self.generate_get(e),
3619            Expression::GetExtract(e) => self.generate_get_extract(e),
3620            Expression::Getbit(e) => self.generate_getbit(e),
3621            Expression::GrantPrincipal(e) => self.generate_grant_principal(e),
3622            Expression::GrantPrivilege(e) => self.generate_grant_privilege(e),
3623            Expression::Group(e) => self.generate_group(e),
3624            Expression::GroupBy(e) => self.generate_group_by(e),
3625            Expression::Grouping(e) => self.generate_grouping(e),
3626            Expression::GroupingId(e) => self.generate_grouping_id(e),
3627            Expression::GroupingSets(e) => self.generate_grouping_sets(e),
3628            Expression::HashAgg(e) => self.generate_hash_agg(e),
3629            Expression::Having(e) => self.generate_having(e),
3630            Expression::HavingMax(e) => self.generate_having_max(e),
3631            Expression::Heredoc(e) => self.generate_heredoc(e),
3632            Expression::HexEncode(e) => self.generate_hex_encode(e),
3633            Expression::Hll(e) => self.generate_hll(e),
3634            Expression::InOutColumnConstraint(e) => self.generate_in_out_column_constraint(e),
3635            Expression::IncludeProperty(e) => self.generate_include_property(e),
3636            Expression::Index(e) => self.generate_index(e),
3637            Expression::IndexColumnConstraint(e) => self.generate_index_column_constraint(e),
3638            Expression::IndexConstraintOption(e) => self.generate_index_constraint_option(e),
3639            Expression::IndexParameters(e) => self.generate_index_parameters(e),
3640            Expression::IndexTableHint(e) => self.generate_index_table_hint(e),
3641            Expression::InheritsProperty(e) => self.generate_inherits_property(e),
3642            Expression::InputModelProperty(e) => self.generate_input_model_property(e),
3643            Expression::InputOutputFormat(e) => self.generate_input_output_format(e),
3644            Expression::Install(e) => self.generate_install(e),
3645            Expression::IntervalOp(e) => self.generate_interval_op(e),
3646            Expression::IntervalSpan(e) => self.generate_interval_span(e),
3647            Expression::IntoClause(e) => self.generate_into_clause(e),
3648            Expression::Introducer(e) => self.generate_introducer(e),
3649            Expression::IsolatedLoadingProperty(e) => self.generate_isolated_loading_property(e),
3650            Expression::JSON(e) => self.generate_json(e),
3651            Expression::JSONArray(e) => self.generate_json_array(e),
3652            Expression::JSONArrayAgg(e) => self.generate_json_array_agg_struct(e),
3653            Expression::JSONArrayAppend(e) => self.generate_json_array_append(e),
3654            Expression::JSONArrayContains(e) => self.generate_json_array_contains(e),
3655            Expression::JSONArrayInsert(e) => self.generate_json_array_insert(e),
3656            Expression::JSONBExists(e) => self.generate_jsonb_exists(e),
3657            Expression::JSONBExtractScalar(e) => self.generate_jsonb_extract_scalar(e),
3658            Expression::JSONBObjectAgg(e) => self.generate_jsonb_object_agg(e),
3659            Expression::JSONObjectAgg(e) => self.generate_json_object_agg_struct(e),
3660            Expression::JSONColumnDef(e) => self.generate_json_column_def(e),
3661            Expression::JSONExists(e) => self.generate_json_exists(e),
3662            Expression::JSONCast(e) => self.generate_json_cast(e),
3663            Expression::JSONExtract(e) => self.generate_json_extract_path(e),
3664            Expression::JSONExtractArray(e) => self.generate_json_extract_array(e),
3665            Expression::JSONExtractQuote(e) => self.generate_json_extract_quote(e),
3666            Expression::JSONExtractScalar(e) => self.generate_json_extract_scalar(e),
3667            Expression::JSONFormat(e) => self.generate_json_format(e),
3668            Expression::JSONKeyValue(e) => self.generate_json_key_value(e),
3669            Expression::JSONKeys(e) => self.generate_json_keys(e),
3670            Expression::JSONKeysAtDepth(e) => self.generate_json_keys_at_depth(e),
3671            Expression::JSONPath(e) => self.generate_json_path_expr(e),
3672            Expression::JSONPathFilter(e) => self.generate_json_path_filter(e),
3673            Expression::JSONPathKey(e) => self.generate_json_path_key(e),
3674            Expression::JSONPathRecursive(e) => self.generate_json_path_recursive(e),
3675            Expression::JSONPathRoot(_) => self.generate_json_path_root(),
3676            Expression::JSONPathScript(e) => self.generate_json_path_script(e),
3677            Expression::JSONPathSelector(e) => self.generate_json_path_selector(e),
3678            Expression::JSONPathSlice(e) => self.generate_json_path_slice(e),
3679            Expression::JSONPathSubscript(e) => self.generate_json_path_subscript(e),
3680            Expression::JSONPathUnion(e) => self.generate_json_path_union(e),
3681            Expression::JSONRemove(e) => self.generate_json_remove(e),
3682            Expression::JSONSchema(e) => self.generate_json_schema(e),
3683            Expression::JSONSet(e) => self.generate_json_set(e),
3684            Expression::JSONStripNulls(e) => self.generate_json_strip_nulls(e),
3685            Expression::JSONTable(e) => self.generate_json_table(e),
3686            Expression::JSONType(e) => self.generate_json_type(e),
3687            Expression::JSONValue(e) => self.generate_json_value(e),
3688            Expression::JSONValueArray(e) => self.generate_json_value_array(e),
3689            Expression::JarowinklerSimilarity(e) => self.generate_jarowinkler_similarity(e),
3690            Expression::JoinHint(e) => self.generate_join_hint(e),
3691            Expression::JournalProperty(e) => self.generate_journal_property(e),
3692            Expression::LanguageProperty(e) => self.generate_language_property(e),
3693            Expression::Lateral(e) => self.generate_lateral(e),
3694            Expression::LikeProperty(e) => self.generate_like_property(e),
3695            Expression::Limit(e) => self.generate_limit(e),
3696            Expression::LimitOptions(e) => self.generate_limit_options(e),
3697            Expression::List(e) => self.generate_list(e),
3698            Expression::ToMap(e) => self.generate_tomap(e),
3699            Expression::Localtime(e) => self.generate_localtime(e),
3700            Expression::Localtimestamp(e) => self.generate_localtimestamp(e),
3701            Expression::LocationProperty(e) => self.generate_location_property(e),
3702            Expression::Lock(e) => self.generate_lock(e),
3703            Expression::LockProperty(e) => self.generate_lock_property(e),
3704            Expression::LockingProperty(e) => self.generate_locking_property(e),
3705            Expression::LockingStatement(e) => self.generate_locking_statement(e),
3706            Expression::LogProperty(e) => self.generate_log_property(e),
3707            Expression::MD5Digest(e) => self.generate_md5_digest(e),
3708            Expression::MLForecast(e) => self.generate_ml_forecast(e),
3709            Expression::MLTranslate(e) => self.generate_ml_translate(e),
3710            Expression::MakeInterval(e) => self.generate_make_interval(e),
3711            Expression::ManhattanDistance(e) => self.generate_manhattan_distance(e),
3712            Expression::Map(e) => self.generate_map(e),
3713            Expression::MapCat(e) => self.generate_map_cat(e),
3714            Expression::MapDelete(e) => self.generate_map_delete(e),
3715            Expression::MapInsert(e) => self.generate_map_insert(e),
3716            Expression::MapPick(e) => self.generate_map_pick(e),
3717            Expression::MaskingPolicyColumnConstraint(e) => {
3718                self.generate_masking_policy_column_constraint(e)
3719            }
3720            Expression::MatchAgainst(e) => self.generate_match_against(e),
3721            Expression::MatchRecognizeMeasure(e) => self.generate_match_recognize_measure(e),
3722            Expression::MaterializedProperty(e) => self.generate_materialized_property(e),
3723            Expression::Merge(e) => self.generate_merge(e),
3724            Expression::MergeBlockRatioProperty(e) => self.generate_merge_block_ratio_property(e),
3725            Expression::MergeTreeTTL(e) => self.generate_merge_tree_ttl(e),
3726            Expression::MergeTreeTTLAction(e) => self.generate_merge_tree_ttl_action(e),
3727            Expression::Minhash(e) => self.generate_minhash(e),
3728            Expression::ModelAttribute(e) => self.generate_model_attribute(e),
3729            Expression::Monthname(e) => self.generate_monthname(e),
3730            Expression::MultitableInserts(e) => self.generate_multitable_inserts(e),
3731            Expression::NextValueFor(e) => self.generate_next_value_for(e),
3732            Expression::Normal(e) => self.generate_normal(e),
3733            Expression::Normalize(e) => self.generate_normalize(e),
3734            Expression::NotNullColumnConstraint(e) => self.generate_not_null_column_constraint(e),
3735            Expression::Nullif(e) => self.generate_nullif(e),
3736            Expression::NumberToStr(e) => self.generate_number_to_str(e),
3737            Expression::ObjectAgg(e) => self.generate_object_agg(e),
3738            Expression::ObjectIdentifier(e) => self.generate_object_identifier(e),
3739            Expression::ObjectInsert(e) => self.generate_object_insert(e),
3740            Expression::Offset(e) => self.generate_offset(e),
3741            Expression::Qualify(e) => self.generate_qualify(e),
3742            Expression::OnCluster(e) => self.generate_on_cluster(e),
3743            Expression::OnCommitProperty(e) => self.generate_on_commit_property(e),
3744            Expression::OnCondition(e) => self.generate_on_condition(e),
3745            Expression::OnConflict(e) => self.generate_on_conflict(e),
3746            Expression::OnProperty(e) => self.generate_on_property(e),
3747            Expression::Opclass(e) => self.generate_opclass(e),
3748            Expression::OpenJSON(e) => self.generate_open_json(e),
3749            Expression::OpenJSONColumnDef(e) => self.generate_open_json_column_def(e),
3750            Expression::Operator(e) => self.generate_operator(e),
3751            Expression::OrderBy(e) => self.generate_order_by(e),
3752            Expression::OutputModelProperty(e) => self.generate_output_model_property(e),
3753            Expression::OverflowTruncateBehavior(e) => self.generate_overflow_truncate_behavior(e),
3754            Expression::ParameterizedAgg(e) => self.generate_parameterized_agg(e),
3755            Expression::ParseDatetime(e) => self.generate_parse_datetime(e),
3756            Expression::ParseIp(e) => self.generate_parse_ip(e),
3757            Expression::ParseJSON(e) => self.generate_parse_json(e),
3758            Expression::ParseTime(e) => self.generate_parse_time(e),
3759            Expression::ParseUrl(e) => self.generate_parse_url(e),
3760            Expression::Partition(e) => self.generate_partition_expr(e),
3761            Expression::PartitionBoundSpec(e) => self.generate_partition_bound_spec(e),
3762            Expression::PartitionByListProperty(e) => self.generate_partition_by_list_property(e),
3763            Expression::PartitionByRangeProperty(e) => self.generate_partition_by_range_property(e),
3764            Expression::PartitionByRangePropertyDynamic(e) => {
3765                self.generate_partition_by_range_property_dynamic(e)
3766            }
3767            Expression::PartitionByTruncate(e) => self.generate_partition_by_truncate(e),
3768            Expression::PartitionList(e) => self.generate_partition_list(e),
3769            Expression::PartitionRange(e) => self.generate_partition_range(e),
3770            Expression::PartitionByProperty(e) => self.generate_partition_by_property(e),
3771            Expression::PartitionedByBucket(e) => self.generate_partitioned_by_bucket(e),
3772            Expression::PartitionedByProperty(e) => self.generate_partitioned_by_property(e),
3773            Expression::PartitionedOfProperty(e) => self.generate_partitioned_of_property(e),
3774            Expression::PeriodForSystemTimeConstraint(e) => {
3775                self.generate_period_for_system_time_constraint(e)
3776            }
3777            Expression::PivotAlias(e) => self.generate_pivot_alias(e),
3778            Expression::PivotAny(e) => self.generate_pivot_any(e),
3779            Expression::Predict(e) => self.generate_predict(e),
3780            Expression::PreviousDay(e) => self.generate_previous_day(e),
3781            Expression::PrimaryKey(e) => self.generate_primary_key(e),
3782            Expression::PrimaryKeyColumnConstraint(e) => {
3783                self.generate_primary_key_column_constraint(e)
3784            }
3785            Expression::PathColumnConstraint(e) => self.generate_path_column_constraint(e),
3786            Expression::ProjectionDef(e) => self.generate_projection_def(e),
3787            Expression::OptionsProperty(e) => self.generate_options_property(e),
3788            Expression::Properties(e) => self.generate_properties(e),
3789            Expression::Property(e) => self.generate_property(e),
3790            Expression::PseudoType(e) => self.generate_pseudo_type(e),
3791            Expression::Put(e) => self.generate_put(e),
3792            Expression::Quantile(e) => self.generate_quantile(e),
3793            Expression::QueryBand(e) => self.generate_query_band(e),
3794            Expression::QueryOption(e) => self.generate_query_option(e),
3795            Expression::QueryTransform(e) => self.generate_query_transform(e),
3796            Expression::Randn(e) => self.generate_randn(e),
3797            Expression::Randstr(e) => self.generate_randstr(e),
3798            Expression::RangeBucket(e) => self.generate_range_bucket(e),
3799            Expression::RangeN(e) => self.generate_range_n(e),
3800            Expression::ReadCSV(e) => self.generate_read_csv(e),
3801            Expression::ReadParquet(e) => self.generate_read_parquet(e),
3802            Expression::RecursiveWithSearch(e) => self.generate_recursive_with_search(e),
3803            Expression::Reduce(e) => self.generate_reduce(e),
3804            Expression::Reference(e) => self.generate_reference(e),
3805            Expression::Refresh(e) => self.generate_refresh(e),
3806            Expression::RefreshTriggerProperty(e) => self.generate_refresh_trigger_property(e),
3807            Expression::RegexpCount(e) => self.generate_regexp_count(e),
3808            Expression::RegexpExtractAll(e) => self.generate_regexp_extract_all(e),
3809            Expression::RegexpFullMatch(e) => self.generate_regexp_full_match(e),
3810            Expression::RegexpILike(e) => self.generate_regexp_i_like(e),
3811            Expression::RegexpInstr(e) => self.generate_regexp_instr(e),
3812            Expression::RegexpSplit(e) => self.generate_regexp_split(e),
3813            Expression::RegrAvgx(e) => self.generate_regr_avgx(e),
3814            Expression::RegrAvgy(e) => self.generate_regr_avgy(e),
3815            Expression::RegrCount(e) => self.generate_regr_count(e),
3816            Expression::RegrIntercept(e) => self.generate_regr_intercept(e),
3817            Expression::RegrR2(e) => self.generate_regr_r2(e),
3818            Expression::RegrSlope(e) => self.generate_regr_slope(e),
3819            Expression::RegrSxx(e) => self.generate_regr_sxx(e),
3820            Expression::RegrSxy(e) => self.generate_regr_sxy(e),
3821            Expression::RegrSyy(e) => self.generate_regr_syy(e),
3822            Expression::RegrValx(e) => self.generate_regr_valx(e),
3823            Expression::RegrValy(e) => self.generate_regr_valy(e),
3824            Expression::RemoteWithConnectionModelProperty(e) => {
3825                self.generate_remote_with_connection_model_property(e)
3826            }
3827            Expression::RenameColumn(e) => self.generate_rename_column(e),
3828            Expression::ReplacePartition(e) => self.generate_replace_partition(e),
3829            Expression::Returning(e) => self.generate_returning(e),
3830            Expression::ReturnsProperty(e) => self.generate_returns_property(e),
3831            Expression::Rollback(e) => self.generate_rollback(e),
3832            Expression::Rollup(e) => self.generate_rollup(e),
3833            Expression::RowFormatDelimitedProperty(e) => {
3834                self.generate_row_format_delimited_property(e)
3835            }
3836            Expression::RowFormatProperty(e) => self.generate_row_format_property(e),
3837            Expression::RowFormatSerdeProperty(e) => self.generate_row_format_serde_property(e),
3838            Expression::SHA2(e) => self.generate_sha2(e),
3839            Expression::SHA2Digest(e) => self.generate_sha2_digest(e),
3840            Expression::SafeAdd(e) => self.generate_safe_add(e),
3841            Expression::SafeDivide(e) => self.generate_safe_divide(e),
3842            Expression::SafeMultiply(e) => self.generate_safe_multiply(e),
3843            Expression::SafeSubtract(e) => self.generate_safe_subtract(e),
3844            Expression::SampleProperty(e) => self.generate_sample_property(e),
3845            Expression::Schema(e) => self.generate_schema(e),
3846            Expression::SchemaCommentProperty(e) => self.generate_schema_comment_property(e),
3847            Expression::ScopeResolution(e) => self.generate_scope_resolution(e),
3848            Expression::Search(e) => self.generate_search(e),
3849            Expression::SearchIp(e) => self.generate_search_ip(e),
3850            Expression::SecurityProperty(e) => self.generate_security_property(e),
3851            Expression::SemanticView(e) => self.generate_semantic_view(e),
3852            Expression::SequenceProperties(e) => self.generate_sequence_properties(e),
3853            Expression::SerdeProperties(e) => self.generate_serde_properties(e),
3854            Expression::SessionParameter(e) => self.generate_session_parameter(e),
3855            Expression::Set(e) => self.generate_set(e),
3856            Expression::SetConfigProperty(e) => self.generate_set_config_property(e),
3857            Expression::SetItem(e) => self.generate_set_item(e),
3858            Expression::SetOperation(e) => self.generate_set_operation(e),
3859            Expression::SetProperty(e) => self.generate_set_property(e),
3860            Expression::SettingsProperty(e) => self.generate_settings_property(e),
3861            Expression::SharingProperty(e) => self.generate_sharing_property(e),
3862            Expression::Slice(e) => self.generate_slice(e),
3863            Expression::SortArray(e) => self.generate_sort_array(e),
3864            Expression::SortBy(e) => self.generate_sort_by(e),
3865            Expression::SortKeyProperty(e) => self.generate_sort_key_property(e),
3866            Expression::SplitPart(e) => self.generate_split_part(e),
3867            Expression::SqlReadWriteProperty(e) => self.generate_sql_read_write_property(e),
3868            Expression::SqlSecurityProperty(e) => self.generate_sql_security_property(e),
3869            Expression::StDistance(e) => self.generate_st_distance(e),
3870            Expression::StPoint(e) => self.generate_st_point(e),
3871            Expression::StabilityProperty(e) => self.generate_stability_property(e),
3872            Expression::StandardHash(e) => self.generate_standard_hash(e),
3873            Expression::StorageHandlerProperty(e) => self.generate_storage_handler_property(e),
3874            Expression::StrPosition(e) => self.generate_str_position(e),
3875            Expression::StrToDate(e) => self.generate_str_to_date(e),
3876            Expression::DateStrToDate(f) => self.generate_simple_func("DATE_STR_TO_DATE", &f.this),
3877            Expression::DateToDateStr(f) => self.generate_simple_func("DATE_TO_DATE_STR", &f.this),
3878            Expression::StrToMap(e) => self.generate_str_to_map(e),
3879            Expression::StrToTime(e) => self.generate_str_to_time(e),
3880            Expression::StrToUnix(e) => self.generate_str_to_unix(e),
3881            Expression::StringToArray(e) => self.generate_string_to_array(e),
3882            Expression::Struct(e) => self.generate_struct(e),
3883            Expression::Stuff(e) => self.generate_stuff(e),
3884            Expression::SubstringIndex(e) => self.generate_substring_index(e),
3885            Expression::Summarize(e) => self.generate_summarize(e),
3886            Expression::Systimestamp(e) => self.generate_systimestamp(e),
3887            Expression::TableAlias(e) => self.generate_table_alias(e),
3888            Expression::TableFromRows(e) => self.generate_table_from_rows(e),
3889            Expression::RowsFrom(e) => self.generate_rows_from(e),
3890            Expression::TableSample(e) => self.generate_table_sample(e),
3891            Expression::Tag(e) => self.generate_tag(e),
3892            Expression::Tags(e) => self.generate_tags(e),
3893            Expression::TemporaryProperty(e) => self.generate_temporary_property(e),
3894            Expression::Time(e) => self.generate_time_func(e),
3895            Expression::TimeAdd(e) => self.generate_time_add(e),
3896            Expression::TimeDiff(e) => self.generate_time_diff(e),
3897            Expression::TimeFromParts(e) => self.generate_time_from_parts(e),
3898            Expression::TimeSlice(e) => self.generate_time_slice(e),
3899            Expression::TimeStrToDate(e) => self.generate_time_str_to_date(e),
3900            Expression::TimeStrToTime(e) => self.generate_time_str_to_time(e),
3901            Expression::TimeSub(e) => self.generate_time_sub(e),
3902            Expression::TimeToStr(e) => self.generate_time_to_str(e),
3903            Expression::TimeToUnix(e) => self.generate_time_to_unix(e),
3904            Expression::TimeTrunc(e) => self.generate_time_trunc(e),
3905            Expression::TimeUnit(e) => self.generate_time_unit(e),
3906            Expression::Timestamp(e) => self.generate_timestamp_func(e),
3907            Expression::TimestampAdd(e) => self.generate_timestamp_add(e),
3908            Expression::TimestampDiff(e) => self.generate_timestamp_diff(e),
3909            Expression::TimestampFromParts(e) => self.generate_timestamp_from_parts(e),
3910            Expression::TimestampSub(e) => self.generate_timestamp_sub(e),
3911            Expression::TimestampTzFromParts(e) => self.generate_timestamp_tz_from_parts(e),
3912            Expression::ToBinary(e) => self.generate_to_binary(e),
3913            Expression::ToBoolean(e) => self.generate_to_boolean(e),
3914            Expression::ToChar(e) => self.generate_to_char(e),
3915            Expression::ToDecfloat(e) => self.generate_to_decfloat(e),
3916            Expression::ToDouble(e) => self.generate_to_double(e),
3917            Expression::ToFile(e) => self.generate_to_file(e),
3918            Expression::ToNumber(e) => self.generate_to_number(e),
3919            Expression::ToTableProperty(e) => self.generate_to_table_property(e),
3920            Expression::Transaction(e) => self.generate_transaction(e),
3921            Expression::Transform(e) => self.generate_transform(e),
3922            Expression::TransformModelProperty(e) => self.generate_transform_model_property(e),
3923            Expression::TransientProperty(e) => self.generate_transient_property(e),
3924            Expression::Translate(e) => self.generate_translate(e),
3925            Expression::TranslateCharacters(e) => self.generate_translate_characters(e),
3926            Expression::TruncateTable(e) => self.generate_truncate_table(e),
3927            Expression::TryBase64DecodeBinary(e) => self.generate_try_base64_decode_binary(e),
3928            Expression::TryBase64DecodeString(e) => self.generate_try_base64_decode_string(e),
3929            Expression::TryToDecfloat(e) => self.generate_try_to_decfloat(e),
3930            Expression::TsOrDsAdd(e) => self.generate_ts_or_ds_add(e),
3931            Expression::TsOrDsDiff(e) => self.generate_ts_or_ds_diff(e),
3932            Expression::TsOrDsToDate(e) => self.generate_ts_or_ds_to_date(e),
3933            Expression::TsOrDsToTime(e) => self.generate_ts_or_ds_to_time(e),
3934            Expression::Unhex(e) => self.generate_unhex(e),
3935            Expression::UnicodeString(e) => self.generate_unicode_string(e),
3936            Expression::Uniform(e) => self.generate_uniform(e),
3937            Expression::UniqueColumnConstraint(e) => self.generate_unique_column_constraint(e),
3938            Expression::UniqueKeyProperty(e) => self.generate_unique_key_property(e),
3939            Expression::RollupProperty(e) => self.generate_rollup_property(e),
3940            Expression::UnixToStr(e) => self.generate_unix_to_str(e),
3941            Expression::UnixToTime(e) => self.generate_unix_to_time(e),
3942            Expression::UnpivotColumns(e) => self.generate_unpivot_columns(e),
3943            Expression::UserDefinedFunction(e) => self.generate_user_defined_function(e),
3944            Expression::UsingTemplateProperty(e) => self.generate_using_template_property(e),
3945            Expression::UtcTime(e) => self.generate_utc_time(e),
3946            Expression::UtcTimestamp(e) => self.generate_utc_timestamp(e),
3947            Expression::Uuid(e) => self.generate_uuid(e),
3948            Expression::Var(v) => {
3949                if matches!(self.config.dialect, Some(DialectType::MySQL))
3950                    && v.this.len() > 2
3951                    && (v.this.starts_with("0x") || v.this.starts_with("0X"))
3952                    && !v.this[2..].chars().all(|c| c.is_ascii_hexdigit())
3953                {
3954                    return self.generate_identifier(&Identifier {
3955                        name: v.this.clone(),
3956                        quoted: true,
3957                        trailing_comments: Vec::new(),
3958                        span: None,
3959                    });
3960                }
3961                self.write(&v.this);
3962                Ok(())
3963            }
3964            Expression::Variadic(e) => {
3965                self.write_keyword("VARIADIC");
3966                self.write_space();
3967                self.generate_expression(&e.this)?;
3968                Ok(())
3969            }
3970            Expression::VarMap(e) => self.generate_var_map(e),
3971            Expression::VectorSearch(e) => self.generate_vector_search(e),
3972            Expression::Version(e) => self.generate_version(e),
3973            Expression::ViewAttributeProperty(e) => self.generate_view_attribute_property(e),
3974            Expression::VolatileProperty(e) => self.generate_volatile_property(e),
3975            Expression::WatermarkColumnConstraint(e) => {
3976                self.generate_watermark_column_constraint(e)
3977            }
3978            Expression::Week(e) => self.generate_week(e),
3979            Expression::When(e) => self.generate_when(e),
3980            Expression::Whens(e) => self.generate_whens(e),
3981            Expression::Where(e) => self.generate_where(e),
3982            Expression::WidthBucket(e) => self.generate_width_bucket(e),
3983            Expression::Window(e) => self.generate_window(e),
3984            Expression::WindowSpec(e) => self.generate_window_spec(e),
3985            Expression::WithDataProperty(e) => self.generate_with_data_property(e),
3986            Expression::WithFill(e) => self.generate_with_fill(e),
3987            Expression::WithJournalTableProperty(e) => self.generate_with_journal_table_property(e),
3988            Expression::WithOperator(e) => self.generate_with_operator(e),
3989            Expression::WithProcedureOptions(e) => self.generate_with_procedure_options(e),
3990            Expression::WithSchemaBindingProperty(e) => {
3991                self.generate_with_schema_binding_property(e)
3992            }
3993            Expression::WithSystemVersioningProperty(e) => {
3994                self.generate_with_system_versioning_property(e)
3995            }
3996            Expression::WithTableHint(e) => self.generate_with_table_hint(e),
3997            Expression::XMLElement(e) => self.generate_xml_element(e),
3998            Expression::XMLGet(e) => self.generate_xml_get(e),
3999            Expression::XMLKeyValueOption(e) => self.generate_xml_key_value_option(e),
4000            Expression::XMLTable(e) => self.generate_xml_table(e),
4001            Expression::Xor(e) => self.generate_xor(e),
4002            Expression::Zipf(e) => self.generate_zipf(e),
4003            _ => self.write_unsupported_comment("unsupported expression"),
4004        }
4005    }
4006
4007    fn generate_select(&mut self, select: &Select) -> Result<()> {
4008        use crate::dialects::DialectType;
4009
4010        // Redshift-style EXCLUDE: for dialects other than Redshift, wrap in a derived table
4011        // e.g., SELECT *, col4 EXCLUDE (col2, col3) FROM t
4012        //   → SELECT * EXCLUDE (col2, col3) FROM (SELECT *, col4 FROM t)
4013        if let Some(exclude) = &select.exclude {
4014            if !exclude.is_empty() && !matches!(self.config.dialect, Some(DialectType::Redshift)) {
4015                // Build the inner select (same as original but without exclude)
4016                let mut inner_select = select.clone();
4017                inner_select.exclude = None;
4018                let inner_expr = Expression::Select(Box::new(inner_select));
4019
4020                // Build the subquery
4021                let subquery = crate::expressions::Subquery {
4022                    this: inner_expr,
4023                    alias: None,
4024                    column_aliases: Vec::new(),
4025                    order_by: None,
4026                    limit: None,
4027                    offset: None,
4028                    distribute_by: None,
4029                    sort_by: None,
4030                    cluster_by: None,
4031                    lateral: false,
4032                    modifiers_inside: false,
4033                    trailing_comments: Vec::new(),
4034                    inferred_type: None,
4035                };
4036
4037                // Build the outer select: SELECT * EXCLUDE (cols) FROM (inner)
4038                let star = Expression::Star(crate::expressions::Star {
4039                    table: None,
4040                    except: Some(
4041                        exclude
4042                            .iter()
4043                            .map(|e| match e {
4044                                Expression::Column(col) => col.name.clone(),
4045                                Expression::Identifier(id) => id.clone(),
4046                                _ => crate::expressions::Identifier::new("unknown".to_string()),
4047                            })
4048                            .collect(),
4049                    ),
4050                    replace: None,
4051                    rename: None,
4052                    trailing_comments: Vec::new(),
4053                    span: None,
4054                });
4055
4056                let outer_select = Select {
4057                    expressions: vec![star],
4058                    from: Some(crate::expressions::From {
4059                        expressions: vec![Expression::Subquery(Box::new(subquery))],
4060                    }),
4061                    ..Select::new()
4062                };
4063
4064                return self.generate_select(&outer_select);
4065            }
4066        }
4067
4068        // Output leading comments before SELECT
4069        for comment in &select.leading_comments {
4070            self.write_formatted_comment(comment);
4071            self.write(" ");
4072        }
4073
4074        // WITH clause
4075        if let Some(with) = &select.with {
4076            self.generate_with(with)?;
4077            if self.config.pretty {
4078                self.write_newline();
4079                self.write_indent();
4080            } else {
4081                self.write_space();
4082            }
4083        }
4084
4085        // Output post-SELECT comments (comments that appeared after SELECT keyword)
4086        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
4087        for comment in &select.post_select_comments {
4088            self.write_formatted_comment(comment);
4089            self.write(" ");
4090        }
4091
4092        self.write_keyword("SELECT");
4093
4094        // Generate query hint if present /*+ ... */
4095        if let Some(hint) = &select.hint {
4096            self.generate_hint(hint)?;
4097        }
4098
4099        // For SQL Server, convert LIMIT to TOP (structural transformation)
4100        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
4101        // TOP clause (SQL Server style - before DISTINCT)
4102        let use_top_from_limit = matches!(self.config.dialect, Some(DialectType::TSQL))
4103            && select.top.is_none()
4104            && select.limit.is_some()
4105            && select.offset.is_none(); // Don't use TOP when there's OFFSET
4106
4107        // For TOP-supporting dialects: DISTINCT before TOP
4108        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
4109        let is_top_dialect = matches!(
4110            self.config.dialect,
4111            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
4112        );
4113        let keep_top_verbatim = !is_top_dialect
4114            && select.limit.is_none()
4115            && select
4116                .top
4117                .as_ref()
4118                .map_or(false, |top| top.percent || top.with_ties);
4119
4120        if select.distinct && (is_top_dialect || select.top.is_some()) {
4121            self.write_space();
4122            self.write_keyword("DISTINCT");
4123        }
4124
4125        if is_top_dialect || keep_top_verbatim {
4126            if let Some(top) = &select.top {
4127                self.write_space();
4128                self.write_keyword("TOP");
4129                if top.parenthesized {
4130                    self.write(" (");
4131                    self.generate_expression(&top.this)?;
4132                    self.write(")");
4133                } else {
4134                    self.write_space();
4135                    self.generate_expression(&top.this)?;
4136                }
4137                if top.percent {
4138                    self.write_space();
4139                    self.write_keyword("PERCENT");
4140                }
4141                if top.with_ties {
4142                    self.write_space();
4143                    self.write_keyword("WITH TIES");
4144                }
4145            } else if use_top_from_limit {
4146                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
4147                if let Some(limit) = &select.limit {
4148                    self.write_space();
4149                    self.write_keyword("TOP");
4150                    // Use parentheses for complex expressions, but not for simple literals
4151                    let is_simple_literal = matches!(&limit.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)));
4152                    if is_simple_literal {
4153                        self.write_space();
4154                        self.generate_expression(&limit.this)?;
4155                    } else {
4156                        self.write(" (");
4157                        self.generate_expression(&limit.this)?;
4158                        self.write(")");
4159                    }
4160                }
4161            }
4162        }
4163
4164        if select.distinct && !is_top_dialect && select.top.is_none() {
4165            self.write_space();
4166            self.write_keyword("DISTINCT");
4167        }
4168
4169        // DISTINCT ON clause (PostgreSQL)
4170        if let Some(distinct_on) = &select.distinct_on {
4171            self.write_space();
4172            self.write_keyword("ON");
4173            self.write(" (");
4174            for (i, expr) in distinct_on.iter().enumerate() {
4175                if i > 0 {
4176                    self.write(", ");
4177                }
4178                self.generate_expression(expr)?;
4179            }
4180            self.write(")");
4181        }
4182
4183        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
4184        for modifier in &select.operation_modifiers {
4185            self.write_space();
4186            self.write_keyword(modifier);
4187        }
4188
4189        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
4190        if let Some(kind) = &select.kind {
4191            self.write_space();
4192            self.write_keyword("AS");
4193            self.write_space();
4194            self.write_keyword(kind);
4195        }
4196
4197        // Expressions (only if there are any)
4198        if !select.expressions.is_empty() {
4199            if self.config.pretty {
4200                self.write_newline();
4201                self.indent_level += 1;
4202            } else {
4203                self.write_space();
4204            }
4205        }
4206
4207        for (i, expr) in select.expressions.iter().enumerate() {
4208            if i > 0 {
4209                self.write(",");
4210                if self.config.pretty {
4211                    self.write_newline();
4212                } else {
4213                    self.write_space();
4214                }
4215            }
4216            if self.config.pretty {
4217                self.write_indent();
4218            }
4219            self.generate_expression(expr)?;
4220        }
4221
4222        if self.config.pretty && !select.expressions.is_empty() {
4223            self.indent_level -= 1;
4224        }
4225
4226        // Redshift-style EXCLUDE clause at the end of the projection list
4227        // For Redshift dialect: append EXCLUDE (col1, col2) after the expressions
4228        // For other dialects (DuckDB, Snowflake): this is handled by wrapping in a derived table
4229        // (done after the full select is generated below)
4230        if let Some(exclude) = &select.exclude {
4231            if !exclude.is_empty() && matches!(self.config.dialect, Some(DialectType::Redshift)) {
4232                self.write_space();
4233                self.write_keyword("EXCLUDE");
4234                self.write(" (");
4235                for (i, col) in exclude.iter().enumerate() {
4236                    if i > 0 {
4237                        self.write(", ");
4238                    }
4239                    self.generate_expression(col)?;
4240                }
4241                self.write(")");
4242            }
4243        }
4244
4245        // INTO clause (SELECT ... INTO table_name)
4246        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4247        if let Some(into) = &select.into {
4248            if self.config.pretty {
4249                self.write_newline();
4250                self.write_indent();
4251            } else {
4252                self.write_space();
4253            }
4254            if into.bulk_collect {
4255                self.write_keyword("BULK COLLECT INTO");
4256            } else {
4257                self.write_keyword("INTO");
4258            }
4259            if into.temporary {
4260                self.write_space();
4261                self.write_keyword("TEMPORARY");
4262            }
4263            if into.unlogged {
4264                self.write_space();
4265                self.write_keyword("UNLOGGED");
4266            }
4267            self.write_space();
4268            // If we have multiple expressions, output them comma-separated
4269            if !into.expressions.is_empty() {
4270                for (i, expr) in into.expressions.iter().enumerate() {
4271                    if i > 0 {
4272                        self.write(", ");
4273                    }
4274                    self.generate_expression(expr)?;
4275                }
4276            } else {
4277                self.generate_expression(&into.this)?;
4278            }
4279        }
4280
4281        // FROM clause
4282        if let Some(from) = &select.from {
4283            if self.config.pretty {
4284                self.write_newline();
4285                self.write_indent();
4286            } else {
4287                self.write_space();
4288            }
4289            self.write_keyword("FROM");
4290            self.write_space();
4291
4292            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4293            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4294            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4295            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4296            let has_tablesample = from
4297                .expressions
4298                .iter()
4299                .any(|e| matches!(e, Expression::TableSample(_)));
4300            let is_cross_join_dialect = matches!(
4301                self.config.dialect,
4302                Some(DialectType::BigQuery)
4303                    | Some(DialectType::Hive)
4304                    | Some(DialectType::Spark)
4305                    | Some(DialectType::Databricks)
4306                    | Some(DialectType::SQLite)
4307                    | Some(DialectType::ClickHouse)
4308            );
4309            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4310            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4311            let source_is_same_as_target = self.config.source_dialect.is_some()
4312                && self.config.source_dialect == self.config.dialect;
4313            let source_is_cross_join_dialect = matches!(
4314                self.config.source_dialect,
4315                Some(DialectType::BigQuery)
4316                    | Some(DialectType::Hive)
4317                    | Some(DialectType::Spark)
4318                    | Some(DialectType::Databricks)
4319                    | Some(DialectType::SQLite)
4320                    | Some(DialectType::ClickHouse)
4321            );
4322            let use_cross_join = !has_tablesample
4323                && is_cross_join_dialect
4324                && (source_is_same_as_target
4325                    || source_is_cross_join_dialect
4326                    || self.config.source_dialect.is_none());
4327
4328            // Snowflake wraps standalone VALUES in FROM clause with parentheses
4329            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
4330
4331            for (i, expr) in from.expressions.iter().enumerate() {
4332                if i > 0 {
4333                    if use_cross_join {
4334                        self.write(" CROSS JOIN ");
4335                    } else {
4336                        self.write(", ");
4337                    }
4338                }
4339                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
4340                    self.write("(");
4341                    self.generate_expression(expr)?;
4342                    self.write(")");
4343                } else {
4344                    self.generate_expression(expr)?;
4345                }
4346                // Output leading comments that were on the table name before FROM
4347                // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
4348                let leading = Self::extract_table_leading_comments(expr);
4349                for comment in &leading {
4350                    self.write_space();
4351                    self.write_formatted_comment(comment);
4352                }
4353            }
4354        }
4355
4356        // JOINs - handle nested join structure for pretty printing
4357        // Deferred-condition joins "own" the non-deferred joins that follow them
4358        // until the next deferred join or end of list
4359        if self.config.pretty {
4360            self.generate_joins_with_nesting(&select.joins)?;
4361        } else {
4362            for join in &select.joins {
4363                self.generate_join(join)?;
4364            }
4365            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4366            for join in select.joins.iter().rev() {
4367                if join.deferred_condition {
4368                    self.generate_join_condition(join)?;
4369                }
4370            }
4371        }
4372
4373        // LATERAL VIEW clauses (Hive/Spark)
4374        for (lv_idx, lateral_view) in select.lateral_views.iter().enumerate() {
4375            self.generate_lateral_view(lateral_view, lv_idx)?;
4376        }
4377
4378        // PREWHERE (ClickHouse)
4379        if let Some(prewhere) = &select.prewhere {
4380            self.write_clause_condition("PREWHERE", prewhere)?;
4381        }
4382
4383        // WHERE
4384        if let Some(where_clause) = &select.where_clause {
4385            self.write_clause_condition("WHERE", &where_clause.this)?;
4386        }
4387
4388        // CONNECT BY (Oracle hierarchical queries)
4389        if let Some(connect) = &select.connect {
4390            self.generate_connect(connect)?;
4391        }
4392
4393        // GROUP BY
4394        if let Some(group_by) = &select.group_by {
4395            if self.config.pretty {
4396                // Output leading comments on their own lines before GROUP BY
4397                for comment in &group_by.comments {
4398                    self.write_newline();
4399                    self.write_indent();
4400                    self.write_formatted_comment(comment);
4401                }
4402                self.write_newline();
4403                self.write_indent();
4404            } else {
4405                self.write_space();
4406                // In non-pretty mode, output comments inline
4407                for comment in &group_by.comments {
4408                    self.write_formatted_comment(comment);
4409                    self.write_space();
4410                }
4411            }
4412            self.write_keyword("GROUP BY");
4413            // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
4414            match group_by.all {
4415                Some(true) => {
4416                    self.write_space();
4417                    self.write_keyword("ALL");
4418                }
4419                Some(false) => {
4420                    self.write_space();
4421                    self.write_keyword("DISTINCT");
4422                }
4423                None => {}
4424            }
4425            if !group_by.expressions.is_empty() {
4426                // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
4427                // These are represented as Cube/Rollup expressions with empty expressions at the end
4428                let mut trailing_cube = false;
4429                let mut trailing_rollup = false;
4430                let mut plain_expressions: Vec<&Expression> = Vec::new();
4431                let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
4432                let mut cube_expressions: Vec<&Expression> = Vec::new();
4433                let mut rollup_expressions: Vec<&Expression> = Vec::new();
4434
4435                for expr in &group_by.expressions {
4436                    match expr {
4437                        Expression::Cube(c) if c.expressions.is_empty() => {
4438                            trailing_cube = true;
4439                        }
4440                        Expression::Rollup(r) if r.expressions.is_empty() => {
4441                            trailing_rollup = true;
4442                        }
4443                        Expression::Function(f) if f.name == "CUBE" => {
4444                            cube_expressions.push(expr);
4445                        }
4446                        Expression::Function(f) if f.name == "ROLLUP" => {
4447                            rollup_expressions.push(expr);
4448                        }
4449                        Expression::Function(f) if f.name == "GROUPING SETS" => {
4450                            grouping_sets_expressions.push(expr);
4451                        }
4452                        _ => {
4453                            plain_expressions.push(expr);
4454                        }
4455                    }
4456                }
4457
4458                // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
4459                let mut regular_expressions: Vec<&Expression> = Vec::new();
4460                regular_expressions.extend(plain_expressions);
4461                regular_expressions.extend(grouping_sets_expressions);
4462                regular_expressions.extend(cube_expressions);
4463                regular_expressions.extend(rollup_expressions);
4464
4465                if self.config.pretty {
4466                    self.write_newline();
4467                    self.indent_level += 1;
4468                    self.write_indent();
4469                } else {
4470                    self.write_space();
4471                }
4472
4473                for (i, expr) in regular_expressions.iter().enumerate() {
4474                    if i > 0 {
4475                        if self.config.pretty {
4476                            self.write(",");
4477                            self.write_newline();
4478                            self.write_indent();
4479                        } else {
4480                            self.write(", ");
4481                        }
4482                    }
4483                    self.generate_expression(expr)?;
4484                }
4485
4486                if self.config.pretty {
4487                    self.indent_level -= 1;
4488                }
4489
4490                // Output trailing WITH CUBE or WITH ROLLUP
4491                if trailing_cube {
4492                    self.write_space();
4493                    self.write_keyword("WITH CUBE");
4494                } else if trailing_rollup {
4495                    self.write_space();
4496                    self.write_keyword("WITH ROLLUP");
4497                }
4498            }
4499
4500            // ClickHouse: WITH TOTALS
4501            if group_by.totals {
4502                self.write_space();
4503                self.write_keyword("WITH TOTALS");
4504            }
4505        }
4506
4507        // HAVING
4508        if let Some(having) = &select.having {
4509            if self.config.pretty {
4510                // Output leading comments on their own lines before HAVING
4511                for comment in &having.comments {
4512                    self.write_newline();
4513                    self.write_indent();
4514                    self.write_formatted_comment(comment);
4515                }
4516            } else {
4517                for comment in &having.comments {
4518                    self.write_space();
4519                    self.write_formatted_comment(comment);
4520                }
4521            }
4522            self.write_clause_condition("HAVING", &having.this)?;
4523        }
4524
4525        // QUALIFY and WINDOW clause ordering depends on input SQL
4526        if select.qualify_after_window {
4527            // WINDOW before QUALIFY (DuckDB style)
4528            if let Some(windows) = &select.windows {
4529                self.write_window_clause(windows)?;
4530            }
4531            if let Some(qualify) = &select.qualify {
4532                self.write_clause_condition("QUALIFY", &qualify.this)?;
4533            }
4534        } else {
4535            // QUALIFY before WINDOW (Snowflake/BigQuery default)
4536            if let Some(qualify) = &select.qualify {
4537                self.write_clause_condition("QUALIFY", &qualify.this)?;
4538            }
4539            if let Some(windows) = &select.windows {
4540                self.write_window_clause(windows)?;
4541            }
4542        }
4543
4544        // DISTRIBUTE BY (Hive/Spark)
4545        if let Some(distribute_by) = &select.distribute_by {
4546            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
4547        }
4548
4549        // CLUSTER BY (Hive/Spark)
4550        if let Some(cluster_by) = &select.cluster_by {
4551            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
4552        }
4553
4554        // SORT BY (Hive/Spark - comes before ORDER BY)
4555        if let Some(sort_by) = &select.sort_by {
4556            self.write_order_clause("SORT BY", &sort_by.expressions)?;
4557        }
4558
4559        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
4560        if let Some(order_by) = &select.order_by {
4561            if self.config.pretty {
4562                // Output leading comments on their own lines before ORDER BY
4563                for comment in &order_by.comments {
4564                    self.write_newline();
4565                    self.write_indent();
4566                    self.write_formatted_comment(comment);
4567                }
4568            } else {
4569                for comment in &order_by.comments {
4570                    self.write_space();
4571                    self.write_formatted_comment(comment);
4572                }
4573            }
4574            let keyword = if order_by.siblings {
4575                "ORDER SIBLINGS BY"
4576            } else {
4577                "ORDER BY"
4578            };
4579            self.write_order_clause(keyword, &order_by.expressions)?;
4580        }
4581
4582        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
4583        if select.order_by.is_none()
4584            && select.fetch.is_some()
4585            && matches!(
4586                self.config.dialect,
4587                Some(DialectType::TSQL) | Some(DialectType::Fabric)
4588            )
4589        {
4590            if self.config.pretty {
4591                self.write_newline();
4592                self.write_indent();
4593            } else {
4594                self.write_space();
4595            }
4596            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
4597        }
4598
4599        // LIMIT and OFFSET
4600        // PostgreSQL and others use: LIMIT count OFFSET offset
4601        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
4602        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
4603        let is_presto_like = matches!(
4604            self.config.dialect,
4605            Some(DialectType::Presto) | Some(DialectType::Trino)
4606        );
4607
4608        if is_presto_like && select.offset.is_some() {
4609            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
4610            if let Some(offset) = &select.offset {
4611                if self.config.pretty {
4612                    self.write_newline();
4613                    self.write_indent();
4614                } else {
4615                    self.write_space();
4616                }
4617                self.write_keyword("OFFSET");
4618                self.write_space();
4619                self.write_limit_expr(&offset.this)?;
4620                if offset.rows == Some(true) {
4621                    self.write_space();
4622                    self.write_keyword("ROWS");
4623                }
4624            }
4625            if let Some(limit) = &select.limit {
4626                if self.config.pretty {
4627                    self.write_newline();
4628                    self.write_indent();
4629                } else {
4630                    self.write_space();
4631                }
4632                self.write_keyword("LIMIT");
4633                self.write_space();
4634                self.write_limit_expr(&limit.this)?;
4635                if limit.percent {
4636                    self.write_space();
4637                    self.write_keyword("PERCENT");
4638                }
4639                // Emit any comments that were captured from before the LIMIT keyword
4640                for comment in &limit.comments {
4641                    self.write(" ");
4642                    self.write_formatted_comment(comment);
4643                }
4644            }
4645        } else {
4646            // Check if FETCH will be converted to LIMIT (used for ordering)
4647            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
4648                !fetch.percent
4649                    && !fetch.with_ties
4650                    && fetch.count.is_some()
4651                    && matches!(
4652                        self.config.dialect,
4653                        Some(DialectType::Spark)
4654                            | Some(DialectType::Hive)
4655                            | Some(DialectType::DuckDB)
4656                            | Some(DialectType::SQLite)
4657                            | Some(DialectType::MySQL)
4658                            | Some(DialectType::BigQuery)
4659                            | Some(DialectType::Databricks)
4660                            | Some(DialectType::StarRocks)
4661                            | Some(DialectType::Doris)
4662                            | Some(DialectType::Athena)
4663                            | Some(DialectType::ClickHouse)
4664                            | Some(DialectType::Redshift)
4665                    )
4666            });
4667
4668            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
4669            if let Some(limit) = &select.limit {
4670                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
4671                if !matches!(self.config.dialect, Some(DialectType::TSQL)) {
4672                    if self.config.pretty {
4673                        self.write_newline();
4674                        self.write_indent();
4675                    } else {
4676                        self.write_space();
4677                    }
4678                    self.write_keyword("LIMIT");
4679                    self.write_space();
4680                    self.write_limit_expr(&limit.this)?;
4681                    if limit.percent {
4682                        self.write_space();
4683                        self.write_keyword("PERCENT");
4684                    }
4685                    // Emit any comments that were captured from before the LIMIT keyword
4686                    for comment in &limit.comments {
4687                        self.write(" ");
4688                        self.write_formatted_comment(comment);
4689                    }
4690                }
4691            }
4692
4693            // Convert TOP to LIMIT for non-TOP dialects
4694            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
4695                if let Some(top) = &select.top {
4696                    if !top.percent && !top.with_ties {
4697                        if self.config.pretty {
4698                            self.write_newline();
4699                            self.write_indent();
4700                        } else {
4701                            self.write_space();
4702                        }
4703                        self.write_keyword("LIMIT");
4704                        self.write_space();
4705                        self.generate_expression(&top.this)?;
4706                    }
4707                }
4708            }
4709
4710            // If FETCH will be converted to LIMIT and there's also OFFSET,
4711            // emit LIMIT from FETCH BEFORE the OFFSET
4712            if fetch_as_limit && select.offset.is_some() {
4713                if let Some(fetch) = &select.fetch {
4714                    if self.config.pretty {
4715                        self.write_newline();
4716                        self.write_indent();
4717                    } else {
4718                        self.write_space();
4719                    }
4720                    self.write_keyword("LIMIT");
4721                    self.write_space();
4722                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4723                }
4724            }
4725
4726            // OFFSET
4727            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
4728            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
4729            if let Some(offset) = &select.offset {
4730                if self.config.pretty {
4731                    self.write_newline();
4732                    self.write_indent();
4733                } else {
4734                    self.write_space();
4735                }
4736                if matches!(self.config.dialect, Some(DialectType::TSQL)) {
4737                    // SQL Server 2012+ OFFSET ... FETCH syntax
4738                    self.write_keyword("OFFSET");
4739                    self.write_space();
4740                    self.write_limit_expr(&offset.this)?;
4741                    self.write_space();
4742                    self.write_keyword("ROWS");
4743                    // If there was a LIMIT, use FETCH NEXT ... ROWS ONLY
4744                    if let Some(limit) = &select.limit {
4745                        self.write_space();
4746                        self.write_keyword("FETCH NEXT");
4747                        self.write_space();
4748                        self.write_limit_expr(&limit.this)?;
4749                        self.write_space();
4750                        self.write_keyword("ROWS ONLY");
4751                    }
4752                } else {
4753                    self.write_keyword("OFFSET");
4754                    self.write_space();
4755                    self.write_limit_expr(&offset.this)?;
4756                    // Output ROWS keyword if it was in the original SQL
4757                    if offset.rows == Some(true) {
4758                        self.write_space();
4759                        self.write_keyword("ROWS");
4760                    }
4761                }
4762            }
4763        }
4764
4765        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
4766        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4767            if let Some(limit_by) = &select.limit_by {
4768                if !limit_by.is_empty() {
4769                    self.write_space();
4770                    self.write_keyword("BY");
4771                    self.write_space();
4772                    for (i, expr) in limit_by.iter().enumerate() {
4773                        if i > 0 {
4774                            self.write(", ");
4775                        }
4776                        self.generate_expression(expr)?;
4777                    }
4778                }
4779            }
4780        }
4781
4782        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
4783        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4784            if let Some(settings) = &select.settings {
4785                if self.config.pretty {
4786                    self.write_newline();
4787                    self.write_indent();
4788                } else {
4789                    self.write_space();
4790                }
4791                self.write_keyword("SETTINGS");
4792                self.write_space();
4793                for (i, expr) in settings.iter().enumerate() {
4794                    if i > 0 {
4795                        self.write(", ");
4796                    }
4797                    self.generate_expression(expr)?;
4798                }
4799            }
4800
4801            if let Some(format_expr) = &select.format {
4802                if self.config.pretty {
4803                    self.write_newline();
4804                    self.write_indent();
4805                } else {
4806                    self.write_space();
4807                }
4808                self.write_keyword("FORMAT");
4809                self.write_space();
4810                self.generate_expression(format_expr)?;
4811            }
4812        }
4813
4814        // FETCH FIRST/NEXT
4815        if let Some(fetch) = &select.fetch {
4816            // Check if we already emitted LIMIT from FETCH before OFFSET
4817            let fetch_already_as_limit = select.offset.is_some()
4818                && !fetch.percent
4819                && !fetch.with_ties
4820                && fetch.count.is_some()
4821                && matches!(
4822                    self.config.dialect,
4823                    Some(DialectType::Spark)
4824                        | Some(DialectType::Hive)
4825                        | Some(DialectType::DuckDB)
4826                        | Some(DialectType::SQLite)
4827                        | Some(DialectType::MySQL)
4828                        | Some(DialectType::BigQuery)
4829                        | Some(DialectType::Databricks)
4830                        | Some(DialectType::StarRocks)
4831                        | Some(DialectType::Doris)
4832                        | Some(DialectType::Athena)
4833                        | Some(DialectType::ClickHouse)
4834                        | Some(DialectType::Redshift)
4835                );
4836
4837            if fetch_already_as_limit {
4838                // Already emitted as LIMIT before OFFSET, skip
4839            } else {
4840                if self.config.pretty {
4841                    self.write_newline();
4842                    self.write_indent();
4843                } else {
4844                    self.write_space();
4845                }
4846
4847                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
4848                let use_limit = !fetch.percent
4849                    && !fetch.with_ties
4850                    && fetch.count.is_some()
4851                    && matches!(
4852                        self.config.dialect,
4853                        Some(DialectType::Spark)
4854                            | Some(DialectType::Hive)
4855                            | Some(DialectType::DuckDB)
4856                            | Some(DialectType::SQLite)
4857                            | Some(DialectType::MySQL)
4858                            | Some(DialectType::BigQuery)
4859                            | Some(DialectType::Databricks)
4860                            | Some(DialectType::StarRocks)
4861                            | Some(DialectType::Doris)
4862                            | Some(DialectType::Athena)
4863                            | Some(DialectType::ClickHouse)
4864                            | Some(DialectType::Redshift)
4865                    );
4866
4867                if use_limit {
4868                    self.write_keyword("LIMIT");
4869                    self.write_space();
4870                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4871                } else {
4872                    self.write_keyword("FETCH");
4873                    self.write_space();
4874                    self.write_keyword(&fetch.direction);
4875                    if let Some(ref count) = fetch.count {
4876                        self.write_space();
4877                        self.generate_expression(count)?;
4878                    }
4879                    if fetch.percent {
4880                        self.write_space();
4881                        self.write_keyword("PERCENT");
4882                    }
4883                    if fetch.rows {
4884                        self.write_space();
4885                        self.write_keyword("ROWS");
4886                    }
4887                    if fetch.with_ties {
4888                        self.write_space();
4889                        self.write_keyword("WITH TIES");
4890                    } else {
4891                        self.write_space();
4892                        self.write_keyword("ONLY");
4893                    }
4894                }
4895            } // close fetch_already_as_limit else
4896        }
4897
4898        // SAMPLE / TABLESAMPLE
4899        if let Some(sample) = &select.sample {
4900            use crate::dialects::DialectType;
4901            if self.config.pretty {
4902                self.write_newline();
4903            } else {
4904                self.write_space();
4905            }
4906
4907            if sample.is_using_sample {
4908                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
4909                self.write_keyword("USING SAMPLE");
4910                self.generate_sample_body(sample)?;
4911            } else {
4912                self.write_keyword("TABLESAMPLE");
4913
4914                // Snowflake defaults to BERNOULLI when no explicit method is given
4915                let snowflake_bernoulli =
4916                    matches!(self.config.dialect, Some(DialectType::Snowflake))
4917                        && !sample.explicit_method;
4918                if snowflake_bernoulli {
4919                    self.write_space();
4920                    self.write_keyword("BERNOULLI");
4921                }
4922
4923                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
4924                if matches!(sample.method, SampleMethod::Bucket) {
4925                    self.write_space();
4926                    self.write("(");
4927                    self.write_keyword("BUCKET");
4928                    self.write_space();
4929                    if let Some(ref num) = sample.bucket_numerator {
4930                        self.generate_expression(num)?;
4931                    }
4932                    self.write_space();
4933                    self.write_keyword("OUT OF");
4934                    self.write_space();
4935                    if let Some(ref denom) = sample.bucket_denominator {
4936                        self.generate_expression(denom)?;
4937                    }
4938                    if let Some(ref field) = sample.bucket_field {
4939                        self.write_space();
4940                        self.write_keyword("ON");
4941                        self.write_space();
4942                        self.generate_expression(field)?;
4943                    }
4944                    self.write(")");
4945                } else if sample.unit_after_size {
4946                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
4947                    if sample.explicit_method && sample.method_before_size {
4948                        self.write_space();
4949                        match sample.method {
4950                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4951                            SampleMethod::System => self.write_keyword("SYSTEM"),
4952                            SampleMethod::Block => self.write_keyword("BLOCK"),
4953                            SampleMethod::Row => self.write_keyword("ROW"),
4954                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4955                            _ => {}
4956                        }
4957                    }
4958                    self.write(" (");
4959                    self.generate_expression(&sample.size)?;
4960                    self.write_space();
4961                    match sample.method {
4962                        SampleMethod::Percent => self.write_keyword("PERCENT"),
4963                        SampleMethod::Row => self.write_keyword("ROWS"),
4964                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
4965                        _ => {
4966                            self.write_keyword("PERCENT");
4967                        }
4968                    }
4969                    self.write(")");
4970                } else {
4971                    // Syntax: TABLESAMPLE METHOD (size)
4972                    self.write_space();
4973                    match sample.method {
4974                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4975                        SampleMethod::System => self.write_keyword("SYSTEM"),
4976                        SampleMethod::Block => self.write_keyword("BLOCK"),
4977                        SampleMethod::Row => self.write_keyword("ROW"),
4978                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
4979                        SampleMethod::Bucket => {}
4980                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4981                    }
4982                    self.write(" (");
4983                    self.generate_expression(&sample.size)?;
4984                    if matches!(sample.method, SampleMethod::Percent) {
4985                        self.write_space();
4986                        self.write_keyword("PERCENT");
4987                    }
4988                    self.write(")");
4989                }
4990            }
4991
4992            if let Some(seed) = &sample.seed {
4993                self.write_space();
4994                // Databricks/Spark use REPEATABLE, not SEED
4995                let use_seed = sample.use_seed_keyword
4996                    && !matches!(
4997                        self.config.dialect,
4998                        Some(crate::dialects::DialectType::Databricks)
4999                            | Some(crate::dialects::DialectType::Spark)
5000                    );
5001                if use_seed {
5002                    self.write_keyword("SEED");
5003                } else {
5004                    self.write_keyword("REPEATABLE");
5005                }
5006                self.write(" (");
5007                self.generate_expression(seed)?;
5008                self.write(")");
5009            }
5010        }
5011
5012        // FOR UPDATE/SHARE locks
5013        // Skip locking clauses for dialects that don't support them
5014        if self.config.locking_reads_supported {
5015            for lock in &select.locks {
5016                if self.config.pretty {
5017                    self.write_newline();
5018                    self.write_indent();
5019                } else {
5020                    self.write_space();
5021                }
5022                self.generate_lock(lock)?;
5023            }
5024        }
5025
5026        // FOR XML clause (T-SQL)
5027        if !select.for_xml.is_empty() {
5028            if self.config.pretty {
5029                self.write_newline();
5030                self.write_indent();
5031            } else {
5032                self.write_space();
5033            }
5034            self.write_keyword("FOR XML");
5035            for (i, opt) in select.for_xml.iter().enumerate() {
5036                if self.config.pretty {
5037                    if i > 0 {
5038                        self.write(",");
5039                    }
5040                    self.write_newline();
5041                    self.write_indent();
5042                    self.write("  "); // extra indent for options
5043                } else {
5044                    if i > 0 {
5045                        self.write(",");
5046                    }
5047                    self.write_space();
5048                }
5049                self.generate_for_xml_option(opt)?;
5050            }
5051        }
5052
5053        // FOR JSON clause (T-SQL)
5054        if !select.for_json.is_empty() {
5055            if self.config.pretty {
5056                self.write_newline();
5057                self.write_indent();
5058            } else {
5059                self.write_space();
5060            }
5061            self.write_keyword("FOR JSON");
5062            for (i, opt) in select.for_json.iter().enumerate() {
5063                if self.config.pretty {
5064                    if i > 0 {
5065                        self.write(",");
5066                    }
5067                    self.write_newline();
5068                    self.write_indent();
5069                    self.write("  "); // extra indent for options
5070                } else {
5071                    if i > 0 {
5072                        self.write(",");
5073                    }
5074                    self.write_space();
5075                }
5076                self.generate_for_xml_option(opt)?;
5077            }
5078        }
5079
5080        // TSQL: OPTION clause
5081        if let Some(ref option) = select.option {
5082            if matches!(
5083                self.config.dialect,
5084                Some(crate::dialects::DialectType::TSQL)
5085                    | Some(crate::dialects::DialectType::Fabric)
5086            ) {
5087                self.write_space();
5088                self.write(option);
5089            }
5090        }
5091
5092        Ok(())
5093    }
5094
5095    /// Generate a single FOR XML option
5096    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
5097        match opt {
5098            Expression::QueryOption(qo) => {
5099                // Extract the option name from Var
5100                if let Expression::Var(var) = &*qo.this {
5101                    self.write(&var.this);
5102                } else {
5103                    self.generate_expression(&qo.this)?;
5104                }
5105                // If there's an expression (like PATH('element')), output it in parens
5106                if let Some(expr) = &qo.expression {
5107                    self.write("(");
5108                    self.generate_expression(expr)?;
5109                    self.write(")");
5110                }
5111            }
5112            _ => {
5113                self.generate_expression(opt)?;
5114            }
5115        }
5116        Ok(())
5117    }
5118
5119    fn generate_with(&mut self, with: &With) -> Result<()> {
5120        use crate::dialects::DialectType;
5121
5122        // Output leading comments before WITH
5123        for comment in &with.leading_comments {
5124            self.write_formatted_comment(comment);
5125            self.write(" ");
5126        }
5127        self.write_keyword("WITH");
5128        if with.recursive && self.config.cte_recursive_keyword_required {
5129            self.write_space();
5130            self.write_keyword("RECURSIVE");
5131        }
5132        self.write_space();
5133
5134        // BigQuery doesn't support column aliases in CTE definitions
5135        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
5136
5137        for (i, cte) in with.ctes.iter().enumerate() {
5138            if i > 0 {
5139                self.write(",");
5140                if self.config.pretty {
5141                    self.write_space();
5142                } else {
5143                    self.write(" ");
5144                }
5145            }
5146            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
5147                self.generate_expression(&cte.this)?;
5148                self.write_space();
5149                self.write_keyword("AS");
5150                self.write_space();
5151                self.generate_identifier(&cte.alias)?;
5152                continue;
5153            }
5154            self.generate_identifier(&cte.alias)?;
5155            // Output CTE comments after alias name, before AS
5156            for comment in &cte.comments {
5157                self.write_space();
5158                self.write_formatted_comment(comment);
5159            }
5160            if !cte.columns.is_empty() && !skip_cte_columns {
5161                self.write("(");
5162                for (j, col) in cte.columns.iter().enumerate() {
5163                    if j > 0 {
5164                        self.write(", ");
5165                    }
5166                    self.generate_identifier(col)?;
5167                }
5168                self.write(")");
5169            }
5170            // USING KEY (columns) for DuckDB recursive CTEs
5171            if !cte.key_expressions.is_empty() {
5172                self.write_space();
5173                self.write_keyword("USING KEY");
5174                self.write(" (");
5175                for (i, key) in cte.key_expressions.iter().enumerate() {
5176                    if i > 0 {
5177                        self.write(", ");
5178                    }
5179                    self.generate_identifier(key)?;
5180                }
5181                self.write(")");
5182            }
5183            self.write_space();
5184            self.write_keyword("AS");
5185            // MATERIALIZED / NOT MATERIALIZED
5186            if let Some(materialized) = cte.materialized {
5187                self.write_space();
5188                if materialized {
5189                    self.write_keyword("MATERIALIZED");
5190                } else {
5191                    self.write_keyword("NOT MATERIALIZED");
5192                }
5193            }
5194            self.write(" (");
5195            if self.config.pretty {
5196                self.write_newline();
5197                self.indent_level += 1;
5198                self.write_indent();
5199            }
5200            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
5201            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
5202            let wrap_values_in_select = matches!(
5203                self.config.dialect,
5204                Some(DialectType::Spark) | Some(DialectType::Databricks)
5205            ) && matches!(&cte.this, Expression::Values(_));
5206
5207            if wrap_values_in_select {
5208                self.write_keyword("SELECT");
5209                self.write(" * ");
5210                self.write_keyword("FROM");
5211                self.write_space();
5212            }
5213            self.generate_expression(&cte.this)?;
5214            if self.config.pretty {
5215                self.write_newline();
5216                self.indent_level -= 1;
5217                self.write_indent();
5218            }
5219            self.write(")");
5220        }
5221
5222        // Generate SEARCH/CYCLE clause if present
5223        if let Some(search) = &with.search {
5224            self.write_space();
5225            self.generate_expression(search)?;
5226        }
5227
5228        Ok(())
5229    }
5230
5231    /// Generate joins with proper nesting structure for pretty printing.
5232    /// Deferred-condition joins "own" the non-deferred joins that follow them
5233    /// within the same nesting_group.
5234    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
5235        let mut i = 0;
5236        while i < joins.len() {
5237            if joins[i].deferred_condition {
5238                let parent_group = joins[i].nesting_group;
5239
5240                // This join owns the following non-deferred joins in the same nesting_group
5241                // First output the join keyword and table (without condition)
5242                self.generate_join_without_condition(&joins[i])?;
5243
5244                // Find the range of child joins: same nesting_group and not deferred
5245                let child_start = i + 1;
5246                let mut child_end = child_start;
5247                while child_end < joins.len()
5248                    && !joins[child_end].deferred_condition
5249                    && joins[child_end].nesting_group == parent_group
5250                {
5251                    child_end += 1;
5252                }
5253
5254                // Output child joins with extra indentation
5255                if child_start < child_end {
5256                    self.indent_level += 1;
5257                    for j in child_start..child_end {
5258                        self.generate_join(&joins[j])?;
5259                    }
5260                    self.indent_level -= 1;
5261                }
5262
5263                // Output the deferred condition at the parent level
5264                self.generate_join_condition(&joins[i])?;
5265
5266                i = child_end;
5267            } else {
5268                // Regular join (no nesting)
5269                self.generate_join(&joins[i])?;
5270                i += 1;
5271            }
5272        }
5273        Ok(())
5274    }
5275
5276    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5277    /// Used for deferred-condition joins where the condition is output after child joins.
5278    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5279        // Save and temporarily clear the condition to prevent generate_join from outputting it
5280        // We achieve this by creating a modified copy
5281        let mut join_copy = join.clone();
5282        join_copy.on = None;
5283        join_copy.using = Vec::new();
5284        join_copy.deferred_condition = false;
5285        self.generate_join(&join_copy)
5286    }
5287
5288    fn generate_join(&mut self, join: &Join) -> Result<()> {
5289        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5290        if join.kind == JoinKind::Implicit {
5291            self.write(",");
5292            if self.config.pretty {
5293                self.write_newline();
5294                self.write_indent();
5295            } else {
5296                self.write_space();
5297            }
5298            self.generate_expression(&join.this)?;
5299            return Ok(());
5300        }
5301
5302        if self.config.pretty {
5303            self.write_newline();
5304            self.write_indent();
5305        } else {
5306            self.write_space();
5307        }
5308
5309        // Helper: format hint suffix (e.g., " LOOP" or "")
5310        // Only include join hints for dialects that support them
5311        let hint_str = if self.config.join_hints {
5312            join.join_hint
5313                .as_ref()
5314                .map(|h| format!(" {}", h))
5315                .unwrap_or_default()
5316        } else {
5317            String::new()
5318        };
5319
5320        let clickhouse_join_keyword =
5321            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5322                if let Some(hint) = &join.join_hint {
5323                    let mut global = false;
5324                    let mut strictness: Option<&'static str> = None;
5325                    for part in hint.split_whitespace() {
5326                        if part.eq_ignore_ascii_case("GLOBAL") {
5327                            global = true;
5328                        } else if part.eq_ignore_ascii_case("ANY") {
5329                            strictness = Some("ANY");
5330                        } else if part.eq_ignore_ascii_case("ASOF") {
5331                            strictness = Some("ASOF");
5332                        } else if part.eq_ignore_ascii_case("SEMI") {
5333                            strictness = Some("SEMI");
5334                        } else if part.eq_ignore_ascii_case("ANTI") {
5335                            strictness = Some("ANTI");
5336                        }
5337                    }
5338
5339                    if global || strictness.is_some() {
5340                        let join_type = match join.kind {
5341                            JoinKind::Left => {
5342                                if join.use_outer_keyword {
5343                                    "LEFT OUTER"
5344                                } else if join.use_inner_keyword {
5345                                    "LEFT INNER"
5346                                } else {
5347                                    "LEFT"
5348                                }
5349                            }
5350                            JoinKind::Right => {
5351                                if join.use_outer_keyword {
5352                                    "RIGHT OUTER"
5353                                } else if join.use_inner_keyword {
5354                                    "RIGHT INNER"
5355                                } else {
5356                                    "RIGHT"
5357                                }
5358                            }
5359                            JoinKind::Full => {
5360                                if join.use_outer_keyword {
5361                                    "FULL OUTER"
5362                                } else {
5363                                    "FULL"
5364                                }
5365                            }
5366                            JoinKind::Inner => {
5367                                if join.use_inner_keyword {
5368                                    "INNER"
5369                                } else {
5370                                    ""
5371                                }
5372                            }
5373                            _ => "",
5374                        };
5375
5376                        let mut parts = Vec::new();
5377                        if global {
5378                            parts.push("GLOBAL");
5379                        }
5380                        if !join_type.is_empty() {
5381                            parts.push(join_type);
5382                        }
5383                        if let Some(strict) = strictness {
5384                            parts.push(strict);
5385                        }
5386                        parts.push("JOIN");
5387                        Some(parts.join(" "))
5388                    } else {
5389                        None
5390                    }
5391                } else {
5392                    None
5393                }
5394            } else {
5395                None
5396            };
5397
5398        // Output any comments associated with this join
5399        // In pretty mode, comments go on their own line before the join keyword
5400        // In non-pretty mode, comments go inline before the join keyword
5401        if !join.comments.is_empty() {
5402            if self.config.pretty {
5403                // In pretty mode, go back before the newline+indent we just wrote
5404                // and output comments on their own lines
5405                // We need to output comments BEFORE the join keyword on separate lines
5406                // Trim the trailing newline+indent we already wrote
5407                let trimmed = self.output.trim_end().len();
5408                self.output.truncate(trimmed);
5409                for comment in &join.comments {
5410                    self.write_newline();
5411                    self.write_indent();
5412                    self.write_formatted_comment(comment);
5413                }
5414                self.write_newline();
5415                self.write_indent();
5416            } else {
5417                for comment in &join.comments {
5418                    self.write_formatted_comment(comment);
5419                    self.write_space();
5420                }
5421            }
5422        }
5423
5424        let directed_str = if join.directed { " DIRECTED" } else { "" };
5425
5426        if let Some(keyword) = clickhouse_join_keyword {
5427            self.write_keyword(&keyword);
5428        } else {
5429            match join.kind {
5430                JoinKind::Inner => {
5431                    if join.use_inner_keyword {
5432                        if hint_str.is_empty() && directed_str.is_empty() {
5433                            self.write_keyword("INNER JOIN");
5434                        } else {
5435                            self.write_keyword("INNER");
5436                            if !hint_str.is_empty() {
5437                                self.write_keyword(&hint_str);
5438                            }
5439                            if !directed_str.is_empty() {
5440                                self.write_keyword(directed_str);
5441                            }
5442                            self.write_keyword(" JOIN");
5443                        }
5444                    } else {
5445                        if !hint_str.is_empty() {
5446                            self.write_keyword(hint_str.trim());
5447                            self.write_keyword(" ");
5448                        }
5449                        if !directed_str.is_empty() {
5450                            self.write_keyword("DIRECTED ");
5451                        }
5452                        self.write_keyword("JOIN");
5453                    }
5454                }
5455                JoinKind::Left => {
5456                    if join.use_outer_keyword {
5457                        if hint_str.is_empty() && directed_str.is_empty() {
5458                            self.write_keyword("LEFT OUTER JOIN");
5459                        } else {
5460                            self.write_keyword("LEFT OUTER");
5461                            if !hint_str.is_empty() {
5462                                self.write_keyword(&hint_str);
5463                            }
5464                            if !directed_str.is_empty() {
5465                                self.write_keyword(directed_str);
5466                            }
5467                            self.write_keyword(" JOIN");
5468                        }
5469                    } else if join.use_inner_keyword {
5470                        if hint_str.is_empty() && directed_str.is_empty() {
5471                            self.write_keyword("LEFT INNER JOIN");
5472                        } else {
5473                            self.write_keyword("LEFT INNER");
5474                            if !hint_str.is_empty() {
5475                                self.write_keyword(&hint_str);
5476                            }
5477                            if !directed_str.is_empty() {
5478                                self.write_keyword(directed_str);
5479                            }
5480                            self.write_keyword(" JOIN");
5481                        }
5482                    } else {
5483                        if hint_str.is_empty() && directed_str.is_empty() {
5484                            self.write_keyword("LEFT JOIN");
5485                        } else {
5486                            self.write_keyword("LEFT");
5487                            if !hint_str.is_empty() {
5488                                self.write_keyword(&hint_str);
5489                            }
5490                            if !directed_str.is_empty() {
5491                                self.write_keyword(directed_str);
5492                            }
5493                            self.write_keyword(" JOIN");
5494                        }
5495                    }
5496                }
5497                JoinKind::Right => {
5498                    if join.use_outer_keyword {
5499                        if hint_str.is_empty() && directed_str.is_empty() {
5500                            self.write_keyword("RIGHT OUTER JOIN");
5501                        } else {
5502                            self.write_keyword("RIGHT OUTER");
5503                            if !hint_str.is_empty() {
5504                                self.write_keyword(&hint_str);
5505                            }
5506                            if !directed_str.is_empty() {
5507                                self.write_keyword(directed_str);
5508                            }
5509                            self.write_keyword(" JOIN");
5510                        }
5511                    } else if join.use_inner_keyword {
5512                        if hint_str.is_empty() && directed_str.is_empty() {
5513                            self.write_keyword("RIGHT INNER JOIN");
5514                        } else {
5515                            self.write_keyword("RIGHT INNER");
5516                            if !hint_str.is_empty() {
5517                                self.write_keyword(&hint_str);
5518                            }
5519                            if !directed_str.is_empty() {
5520                                self.write_keyword(directed_str);
5521                            }
5522                            self.write_keyword(" JOIN");
5523                        }
5524                    } else {
5525                        if hint_str.is_empty() && directed_str.is_empty() {
5526                            self.write_keyword("RIGHT JOIN");
5527                        } else {
5528                            self.write_keyword("RIGHT");
5529                            if !hint_str.is_empty() {
5530                                self.write_keyword(&hint_str);
5531                            }
5532                            if !directed_str.is_empty() {
5533                                self.write_keyword(directed_str);
5534                            }
5535                            self.write_keyword(" JOIN");
5536                        }
5537                    }
5538                }
5539                JoinKind::Full => {
5540                    if join.use_outer_keyword {
5541                        if hint_str.is_empty() && directed_str.is_empty() {
5542                            self.write_keyword("FULL OUTER JOIN");
5543                        } else {
5544                            self.write_keyword("FULL OUTER");
5545                            if !hint_str.is_empty() {
5546                                self.write_keyword(&hint_str);
5547                            }
5548                            if !directed_str.is_empty() {
5549                                self.write_keyword(directed_str);
5550                            }
5551                            self.write_keyword(" JOIN");
5552                        }
5553                    } else {
5554                        if hint_str.is_empty() && directed_str.is_empty() {
5555                            self.write_keyword("FULL JOIN");
5556                        } else {
5557                            self.write_keyword("FULL");
5558                            if !hint_str.is_empty() {
5559                                self.write_keyword(&hint_str);
5560                            }
5561                            if !directed_str.is_empty() {
5562                                self.write_keyword(directed_str);
5563                            }
5564                            self.write_keyword(" JOIN");
5565                        }
5566                    }
5567                }
5568                JoinKind::Outer => {
5569                    if directed_str.is_empty() {
5570                        self.write_keyword("OUTER JOIN");
5571                    } else {
5572                        self.write_keyword("OUTER");
5573                        self.write_keyword(directed_str);
5574                        self.write_keyword(" JOIN");
5575                    }
5576                }
5577                JoinKind::Cross => {
5578                    if directed_str.is_empty() {
5579                        self.write_keyword("CROSS JOIN");
5580                    } else {
5581                        self.write_keyword("CROSS");
5582                        self.write_keyword(directed_str);
5583                        self.write_keyword(" JOIN");
5584                    }
5585                }
5586                JoinKind::Natural => {
5587                    if join.use_inner_keyword {
5588                        if directed_str.is_empty() {
5589                            self.write_keyword("NATURAL INNER JOIN");
5590                        } else {
5591                            self.write_keyword("NATURAL INNER");
5592                            self.write_keyword(directed_str);
5593                            self.write_keyword(" JOIN");
5594                        }
5595                    } else {
5596                        if directed_str.is_empty() {
5597                            self.write_keyword("NATURAL JOIN");
5598                        } else {
5599                            self.write_keyword("NATURAL");
5600                            self.write_keyword(directed_str);
5601                            self.write_keyword(" JOIN");
5602                        }
5603                    }
5604                }
5605                JoinKind::NaturalLeft => {
5606                    if join.use_outer_keyword {
5607                        if directed_str.is_empty() {
5608                            self.write_keyword("NATURAL LEFT OUTER JOIN");
5609                        } else {
5610                            self.write_keyword("NATURAL LEFT OUTER");
5611                            self.write_keyword(directed_str);
5612                            self.write_keyword(" JOIN");
5613                        }
5614                    } else {
5615                        if directed_str.is_empty() {
5616                            self.write_keyword("NATURAL LEFT JOIN");
5617                        } else {
5618                            self.write_keyword("NATURAL LEFT");
5619                            self.write_keyword(directed_str);
5620                            self.write_keyword(" JOIN");
5621                        }
5622                    }
5623                }
5624                JoinKind::NaturalRight => {
5625                    if join.use_outer_keyword {
5626                        if directed_str.is_empty() {
5627                            self.write_keyword("NATURAL RIGHT OUTER JOIN");
5628                        } else {
5629                            self.write_keyword("NATURAL RIGHT OUTER");
5630                            self.write_keyword(directed_str);
5631                            self.write_keyword(" JOIN");
5632                        }
5633                    } else {
5634                        if directed_str.is_empty() {
5635                            self.write_keyword("NATURAL RIGHT JOIN");
5636                        } else {
5637                            self.write_keyword("NATURAL RIGHT");
5638                            self.write_keyword(directed_str);
5639                            self.write_keyword(" JOIN");
5640                        }
5641                    }
5642                }
5643                JoinKind::NaturalFull => {
5644                    if join.use_outer_keyword {
5645                        if directed_str.is_empty() {
5646                            self.write_keyword("NATURAL FULL OUTER JOIN");
5647                        } else {
5648                            self.write_keyword("NATURAL FULL OUTER");
5649                            self.write_keyword(directed_str);
5650                            self.write_keyword(" JOIN");
5651                        }
5652                    } else {
5653                        if directed_str.is_empty() {
5654                            self.write_keyword("NATURAL FULL JOIN");
5655                        } else {
5656                            self.write_keyword("NATURAL FULL");
5657                            self.write_keyword(directed_str);
5658                            self.write_keyword(" JOIN");
5659                        }
5660                    }
5661                }
5662                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
5663                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
5664                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
5665                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
5666                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
5667                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
5668                JoinKind::CrossApply => {
5669                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL dialects
5670                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5671                        self.write_keyword("CROSS APPLY");
5672                    } else {
5673                        self.write_keyword("INNER JOIN LATERAL");
5674                    }
5675                }
5676                JoinKind::OuterApply => {
5677                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL dialects
5678                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5679                        self.write_keyword("OUTER APPLY");
5680                    } else {
5681                        self.write_keyword("LEFT JOIN LATERAL");
5682                    }
5683                }
5684                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
5685                JoinKind::AsOfLeft => {
5686                    if join.use_outer_keyword {
5687                        self.write_keyword("ASOF LEFT OUTER JOIN");
5688                    } else {
5689                        self.write_keyword("ASOF LEFT JOIN");
5690                    }
5691                }
5692                JoinKind::AsOfRight => {
5693                    if join.use_outer_keyword {
5694                        self.write_keyword("ASOF RIGHT OUTER JOIN");
5695                    } else {
5696                        self.write_keyword("ASOF RIGHT JOIN");
5697                    }
5698                }
5699                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
5700                JoinKind::LeftLateral => {
5701                    if join.use_outer_keyword {
5702                        self.write_keyword("LEFT OUTER LATERAL JOIN");
5703                    } else {
5704                        self.write_keyword("LEFT LATERAL JOIN");
5705                    }
5706                }
5707                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
5708                JoinKind::Implicit => {
5709                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
5710                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
5711                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
5712                    use crate::dialects::DialectType;
5713                    let is_cj_dialect = matches!(
5714                        self.config.dialect,
5715                        Some(DialectType::BigQuery)
5716                            | Some(DialectType::Hive)
5717                            | Some(DialectType::Spark)
5718                            | Some(DialectType::Databricks)
5719                    );
5720                    let source_is_same = self.config.source_dialect.is_some()
5721                        && self.config.source_dialect == self.config.dialect;
5722                    let source_is_cj = matches!(
5723                        self.config.source_dialect,
5724                        Some(DialectType::BigQuery)
5725                            | Some(DialectType::Hive)
5726                            | Some(DialectType::Spark)
5727                            | Some(DialectType::Databricks)
5728                    );
5729                    if is_cj_dialect
5730                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
5731                    {
5732                        self.write_keyword("CROSS JOIN");
5733                    } else {
5734                        // Implicit join uses comma: FROM a, b
5735                        // We already wrote a space before the match, so replace with comma
5736                        // by removing trailing space and writing ", "
5737                        self.output.truncate(self.output.trim_end().len());
5738                        self.write(",");
5739                    }
5740                }
5741                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
5742                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
5743                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
5744                JoinKind::Positional => self.write_keyword("POSITIONAL JOIN"),
5745            }
5746        }
5747
5748        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
5749        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
5750            self.write_space();
5751            match &join.this {
5752                Expression::Tuple(t) => {
5753                    for (i, item) in t.expressions.iter().enumerate() {
5754                        if i > 0 {
5755                            self.write(", ");
5756                        }
5757                        self.generate_expression(item)?;
5758                    }
5759                }
5760                other => {
5761                    self.generate_expression(other)?;
5762                }
5763            }
5764        } else {
5765            self.write_space();
5766            self.generate_expression(&join.this)?;
5767        }
5768
5769        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
5770        if !join.deferred_condition {
5771            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
5772            if let Some(match_cond) = &join.match_condition {
5773                self.write_space();
5774                self.write_keyword("MATCH_CONDITION");
5775                self.write(" (");
5776                self.generate_expression(match_cond)?;
5777                self.write(")");
5778            }
5779
5780            if let Some(on) = &join.on {
5781                if self.config.pretty {
5782                    self.write_newline();
5783                    self.indent_level += 1;
5784                    self.write_indent();
5785                    self.write_keyword("ON");
5786                    self.write_space();
5787                    self.generate_join_on_condition(on)?;
5788                    self.indent_level -= 1;
5789                } else {
5790                    self.write_space();
5791                    self.write_keyword("ON");
5792                    self.write_space();
5793                    self.generate_expression(on)?;
5794                }
5795            }
5796
5797            if !join.using.is_empty() {
5798                if self.config.pretty {
5799                    self.write_newline();
5800                    self.indent_level += 1;
5801                    self.write_indent();
5802                    self.write_keyword("USING");
5803                    self.write(" (");
5804                    for (i, col) in join.using.iter().enumerate() {
5805                        if i > 0 {
5806                            self.write(", ");
5807                        }
5808                        self.generate_identifier(col)?;
5809                    }
5810                    self.write(")");
5811                    self.indent_level -= 1;
5812                } else {
5813                    self.write_space();
5814                    self.write_keyword("USING");
5815                    self.write(" (");
5816                    for (i, col) in join.using.iter().enumerate() {
5817                        if i > 0 {
5818                            self.write(", ");
5819                        }
5820                        self.generate_identifier(col)?;
5821                    }
5822                    self.write(")");
5823                }
5824            }
5825        }
5826
5827        // Generate PIVOT/UNPIVOT expressions that follow this join
5828        for pivot in &join.pivots {
5829            self.write_space();
5830            self.generate_expression(pivot)?;
5831        }
5832
5833        Ok(())
5834    }
5835
5836    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
5837    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
5838        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
5839        if let Some(match_cond) = &join.match_condition {
5840            self.write_space();
5841            self.write_keyword("MATCH_CONDITION");
5842            self.write(" (");
5843            self.generate_expression(match_cond)?;
5844            self.write(")");
5845        }
5846
5847        if let Some(on) = &join.on {
5848            if self.config.pretty {
5849                self.write_newline();
5850                self.indent_level += 1;
5851                self.write_indent();
5852                self.write_keyword("ON");
5853                self.write_space();
5854                // In pretty mode, split AND conditions onto separate lines
5855                self.generate_join_on_condition(on)?;
5856                self.indent_level -= 1;
5857            } else {
5858                self.write_space();
5859                self.write_keyword("ON");
5860                self.write_space();
5861                self.generate_expression(on)?;
5862            }
5863        }
5864
5865        if !join.using.is_empty() {
5866            if self.config.pretty {
5867                self.write_newline();
5868                self.indent_level += 1;
5869                self.write_indent();
5870                self.write_keyword("USING");
5871                self.write(" (");
5872                for (i, col) in join.using.iter().enumerate() {
5873                    if i > 0 {
5874                        self.write(", ");
5875                    }
5876                    self.generate_identifier(col)?;
5877                }
5878                self.write(")");
5879                self.indent_level -= 1;
5880            } else {
5881                self.write_space();
5882                self.write_keyword("USING");
5883                self.write(" (");
5884                for (i, col) in join.using.iter().enumerate() {
5885                    if i > 0 {
5886                        self.write(", ");
5887                    }
5888                    self.generate_identifier(col)?;
5889                }
5890                self.write(")");
5891            }
5892        }
5893
5894        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
5895        for pivot in &join.pivots {
5896            self.write_space();
5897            self.generate_expression(pivot)?;
5898        }
5899
5900        Ok(())
5901    }
5902
5903    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
5904    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
5905        if let Expression::And(and_op) = expr {
5906            if let Some(conditions) = self.flatten_connector_terms(and_op, ConnectorOperator::And) {
5907                self.generate_expression(conditions[0])?;
5908                for condition in conditions.iter().skip(1) {
5909                    self.write_newline();
5910                    self.write_indent();
5911                    self.write_keyword("AND");
5912                    self.write_space();
5913                    self.generate_expression(condition)?;
5914                }
5915                return Ok(());
5916            }
5917        }
5918
5919        self.generate_expression(expr)
5920    }
5921
5922    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
5923        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
5924        self.write("(");
5925        self.generate_expression(&jt.left)?;
5926
5927        // Generate all joins
5928        for join in &jt.joins {
5929            self.generate_join(join)?;
5930        }
5931
5932        // Generate LATERAL VIEW clauses (Hive/Spark)
5933        for (lv_idx, lv) in jt.lateral_views.iter().enumerate() {
5934            self.generate_lateral_view(lv, lv_idx)?;
5935        }
5936
5937        self.write(")");
5938
5939        // Alias
5940        if let Some(alias) = &jt.alias {
5941            self.write_space();
5942            self.write_keyword("AS");
5943            self.write_space();
5944            self.generate_identifier(alias)?;
5945        }
5946
5947        Ok(())
5948    }
5949
5950    fn generate_lateral_view(&mut self, lv: &LateralView, lv_index: usize) -> Result<()> {
5951        use crate::dialects::DialectType;
5952
5953        if self.config.pretty {
5954            self.write_newline();
5955            self.write_indent();
5956        } else {
5957            self.write_space();
5958        }
5959
5960        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
5961        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
5962        let use_lateral_join = matches!(
5963            self.config.dialect,
5964            Some(DialectType::PostgreSQL)
5965                | Some(DialectType::DuckDB)
5966                | Some(DialectType::Snowflake)
5967                | Some(DialectType::TSQL)
5968                | Some(DialectType::Presto)
5969                | Some(DialectType::Trino)
5970                | Some(DialectType::Athena)
5971        );
5972
5973        // Check if target dialect should use UNNEST instead of EXPLODE
5974        let use_unnest = matches!(
5975            self.config.dialect,
5976            Some(DialectType::DuckDB)
5977                | Some(DialectType::Presto)
5978                | Some(DialectType::Trino)
5979                | Some(DialectType::Athena)
5980        );
5981
5982        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
5983        let (is_posexplode, is_inline, func_args) = match &lv.this {
5984            Expression::Explode(uf) => {
5985                // Expression::Explode is the dedicated EXPLODE expression type
5986                (false, false, vec![uf.this.clone()])
5987            }
5988            Expression::Unnest(uf) => {
5989                let mut args = vec![uf.this.clone()];
5990                args.extend(uf.expressions.clone());
5991                (false, false, args)
5992            }
5993            Expression::Function(func) => {
5994                if func.name.eq_ignore_ascii_case("POSEXPLODE")
5995                    || func.name.eq_ignore_ascii_case("POSEXPLODE_OUTER")
5996                {
5997                    (true, false, func.args.clone())
5998                } else if func.name.eq_ignore_ascii_case("INLINE") {
5999                    (false, true, func.args.clone())
6000                } else if func.name.eq_ignore_ascii_case("EXPLODE")
6001                    || func.name.eq_ignore_ascii_case("EXPLODE_OUTER")
6002                {
6003                    (false, false, func.args.clone())
6004                } else {
6005                    (false, false, vec![])
6006                }
6007            }
6008            _ => (false, false, vec![]),
6009        };
6010
6011        if use_lateral_join {
6012            // Convert to CROSS JOIN for PostgreSQL-like dialects
6013            if lv.outer {
6014                self.write_keyword("LEFT JOIN LATERAL");
6015            } else {
6016                self.write_keyword("CROSS JOIN");
6017            }
6018            self.write_space();
6019
6020            if use_unnest && !func_args.is_empty() {
6021                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
6022                // For DuckDB, also convert ARRAY(y) -> [y]
6023                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6024                    // DuckDB: ARRAY(y) -> [y]
6025                    func_args
6026                        .iter()
6027                        .map(|a| {
6028                            if let Expression::Function(ref f) = a {
6029                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() == 1 {
6030                                    return Expression::ArrayFunc(Box::new(
6031                                        crate::expressions::ArrayConstructor {
6032                                            expressions: f.args.clone(),
6033                                            bracket_notation: true,
6034                                            use_list_keyword: false,
6035                                        },
6036                                    ));
6037                                }
6038                            }
6039                            a.clone()
6040                        })
6041                        .collect::<Vec<_>>()
6042                } else if matches!(
6043                    self.config.dialect,
6044                    Some(DialectType::Presto)
6045                        | Some(DialectType::Trino)
6046                        | Some(DialectType::Athena)
6047                ) {
6048                    // Presto: ARRAY(y) -> ARRAY[y]
6049                    func_args
6050                        .iter()
6051                        .map(|a| {
6052                            if let Expression::Function(ref f) = a {
6053                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() >= 1 {
6054                                    return Expression::ArrayFunc(Box::new(
6055                                        crate::expressions::ArrayConstructor {
6056                                            expressions: f.args.clone(),
6057                                            bracket_notation: true,
6058                                            use_list_keyword: false,
6059                                        },
6060                                    ));
6061                                }
6062                            }
6063                            a.clone()
6064                        })
6065                        .collect::<Vec<_>>()
6066                } else {
6067                    func_args
6068                };
6069
6070                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
6071                if is_posexplode {
6072                    self.write_keyword("LATERAL");
6073                    self.write(" (");
6074                    self.write_keyword("SELECT");
6075                    self.write_space();
6076
6077                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
6078                    // column_aliases[0] is the position column, rest are data columns
6079                    let pos_alias = if !lv.column_aliases.is_empty() {
6080                        lv.column_aliases[0].clone()
6081                    } else {
6082                        Identifier::new("pos")
6083                    };
6084                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
6085                        lv.column_aliases[1..].to_vec()
6086                    } else {
6087                        vec![Identifier::new("col")]
6088                    };
6089
6090                    // pos - 1 AS pos
6091                    self.generate_identifier(&pos_alias)?;
6092                    self.write(" - 1");
6093                    self.write_space();
6094                    self.write_keyword("AS");
6095                    self.write_space();
6096                    self.generate_identifier(&pos_alias)?;
6097
6098                    // , col [, key, value ...]
6099                    for data_col in &data_aliases {
6100                        self.write(", ");
6101                        self.generate_identifier(data_col)?;
6102                    }
6103
6104                    self.write_space();
6105                    self.write_keyword("FROM");
6106                    self.write_space();
6107                    self.write_keyword("UNNEST");
6108                    self.write("(");
6109                    for (i, arg) in unnest_args.iter().enumerate() {
6110                        if i > 0 {
6111                            self.write(", ");
6112                        }
6113                        self.generate_expression(arg)?;
6114                    }
6115                    self.write(")");
6116                    self.write_space();
6117                    self.write_keyword("WITH ORDINALITY");
6118                    self.write_space();
6119                    self.write_keyword("AS");
6120                    self.write_space();
6121
6122                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
6123                    let table_alias_ident = lv
6124                        .table_alias
6125                        .clone()
6126                        .unwrap_or_else(|| Identifier::new("t"));
6127                    self.generate_identifier(&table_alias_ident)?;
6128                    self.write("(");
6129                    for (i, data_col) in data_aliases.iter().enumerate() {
6130                        if i > 0 {
6131                            self.write(", ");
6132                        }
6133                        self.generate_identifier(data_col)?;
6134                    }
6135                    self.write(", ");
6136                    self.generate_identifier(&pos_alias)?;
6137                    self.write("))");
6138                } else if is_inline && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6139                    // INLINE -> LATERAL (SELECT UNNEST(arg, max_depth => 2)) AS alias
6140                    self.write_keyword("LATERAL");
6141                    self.write(" (");
6142                    self.write_keyword("SELECT");
6143                    self.write_space();
6144                    self.write_keyword("UNNEST");
6145                    self.write("(");
6146                    for (i, arg) in unnest_args.iter().enumerate() {
6147                        if i > 0 {
6148                            self.write(", ");
6149                        }
6150                        self.generate_expression(arg)?;
6151                    }
6152                    self.write(", ");
6153                    self.write_keyword("max_depth");
6154                    self.write(" => 2))");
6155
6156                    // Add table and column aliases
6157                    if let Some(alias) = &lv.table_alias {
6158                        self.write_space();
6159                        self.write_keyword("AS");
6160                        self.write_space();
6161                        self.generate_identifier(alias)?;
6162                        if !lv.column_aliases.is_empty() {
6163                            self.write("(");
6164                            for (i, col) in lv.column_aliases.iter().enumerate() {
6165                                if i > 0 {
6166                                    self.write(", ");
6167                                }
6168                                self.generate_identifier(col)?;
6169                            }
6170                            self.write(")");
6171                        }
6172                    } else if !lv.column_aliases.is_empty() {
6173                        // Auto-generate alias like _u_N
6174                        self.write_space();
6175                        self.write_keyword("AS");
6176                        self.write_space();
6177                        self.write(&format!("_u_{}", lv_index));
6178                        self.write("(");
6179                        for (i, col) in lv.column_aliases.iter().enumerate() {
6180                            if i > 0 {
6181                                self.write(", ");
6182                            }
6183                            self.generate_identifier(col)?;
6184                        }
6185                        self.write(")");
6186                    }
6187                } else {
6188                    self.write_keyword("UNNEST");
6189                    self.write("(");
6190                    for (i, arg) in unnest_args.iter().enumerate() {
6191                        if i > 0 {
6192                            self.write(", ");
6193                        }
6194                        self.generate_expression(arg)?;
6195                    }
6196                    self.write(")");
6197
6198                    // Add table and column aliases for non-POSEXPLODE
6199                    if let Some(alias) = &lv.table_alias {
6200                        self.write_space();
6201                        self.write_keyword("AS");
6202                        self.write_space();
6203                        self.generate_identifier(alias)?;
6204                        if !lv.column_aliases.is_empty() {
6205                            self.write("(");
6206                            for (i, col) in lv.column_aliases.iter().enumerate() {
6207                                if i > 0 {
6208                                    self.write(", ");
6209                                }
6210                                self.generate_identifier(col)?;
6211                            }
6212                            self.write(")");
6213                        }
6214                    } else if !lv.column_aliases.is_empty() {
6215                        self.write_space();
6216                        self.write_keyword("AS");
6217                        self.write(" t(");
6218                        for (i, col) in lv.column_aliases.iter().enumerate() {
6219                            if i > 0 {
6220                                self.write(", ");
6221                            }
6222                            self.generate_identifier(col)?;
6223                        }
6224                        self.write(")");
6225                    }
6226                }
6227            } else {
6228                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
6229                if !lv.outer {
6230                    self.write_keyword("LATERAL");
6231                    self.write_space();
6232                }
6233                self.generate_expression(&lv.this)?;
6234
6235                // Add table and column aliases
6236                if let Some(alias) = &lv.table_alias {
6237                    self.write_space();
6238                    self.write_keyword("AS");
6239                    self.write_space();
6240                    self.generate_identifier(alias)?;
6241                    if !lv.column_aliases.is_empty() {
6242                        self.write("(");
6243                        for (i, col) in lv.column_aliases.iter().enumerate() {
6244                            if i > 0 {
6245                                self.write(", ");
6246                            }
6247                            self.generate_identifier(col)?;
6248                        }
6249                        self.write(")");
6250                    }
6251                } else if !lv.column_aliases.is_empty() {
6252                    self.write_space();
6253                    self.write_keyword("AS");
6254                    self.write(" t(");
6255                    for (i, col) in lv.column_aliases.iter().enumerate() {
6256                        if i > 0 {
6257                            self.write(", ");
6258                        }
6259                        self.generate_identifier(col)?;
6260                    }
6261                    self.write(")");
6262                }
6263            }
6264
6265            // For LEFT JOIN LATERAL, need ON TRUE
6266            if lv.outer {
6267                self.write_space();
6268                self.write_keyword("ON TRUE");
6269            }
6270        } else {
6271            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
6272            self.write_keyword("LATERAL VIEW");
6273            if lv.outer {
6274                self.write_space();
6275                self.write_keyword("OUTER");
6276            }
6277            if self.config.pretty {
6278                self.write_newline();
6279                self.write_indent();
6280            } else {
6281                self.write_space();
6282            }
6283            self.generate_expression(&lv.this)?;
6284
6285            // Table alias
6286            if let Some(alias) = &lv.table_alias {
6287                self.write_space();
6288                self.generate_identifier(alias)?;
6289            }
6290
6291            // Column aliases
6292            if !lv.column_aliases.is_empty() {
6293                self.write_space();
6294                self.write_keyword("AS");
6295                self.write_space();
6296                for (i, col) in lv.column_aliases.iter().enumerate() {
6297                    if i > 0 {
6298                        self.write(", ");
6299                    }
6300                    self.generate_identifier(col)?;
6301                }
6302            }
6303        }
6304
6305        Ok(())
6306    }
6307
6308    fn generate_union(&mut self, outermost: &Union) -> Result<()> {
6309        // Collect the left-recursive chain of Union nodes iteratively.
6310        // This avoids stack overflow for deeply nested chains like
6311        // SELECT 1 UNION ALL SELECT 2 UNION ALL ... UNION ALL SELECT N
6312        // where the parser builds: Union(Union(Union(A, B), C), D)
6313        let mut chain: Vec<&Union> = vec![outermost];
6314        let mut leftmost: &Expression = &outermost.left;
6315        while let Expression::Union(inner) = leftmost {
6316            chain.push(inner);
6317            leftmost = &inner.left;
6318        }
6319        // chain[0] = outermost, chain[last] = innermost
6320        // leftmost = innermost.left (a non-Union expression, typically Select)
6321
6322        // WITH clause (only on outermost)
6323        if let Some(with) = &outermost.with {
6324            self.generate_with(with)?;
6325            self.write_space();
6326        }
6327
6328        // Generate the base (leftmost) expression
6329        self.generate_expression(leftmost)?;
6330
6331        // Generate each union step from innermost to outermost
6332        for union in chain.iter().rev() {
6333            self.generate_union_step(union)?;
6334        }
6335        Ok(())
6336    }
6337
6338    /// Generate a single UNION step: keyword, right expression, and trailing modifiers.
6339    fn generate_union_step(&mut self, union: &Union) -> Result<()> {
6340        if self.config.pretty {
6341            self.write_newline();
6342            self.write_indent();
6343        } else {
6344            self.write_space();
6345        }
6346
6347        // BigQuery set operation modifiers: [side] [kind] UNION
6348        if let Some(side) = &union.side {
6349            self.write_keyword(side);
6350            self.write_space();
6351        }
6352        if let Some(kind) = &union.kind {
6353            self.write_keyword(kind);
6354            self.write_space();
6355        }
6356
6357        self.write_keyword("UNION");
6358        if union.all {
6359            self.write_space();
6360            self.write_keyword("ALL");
6361        } else if union.distinct {
6362            self.write_space();
6363            self.write_keyword("DISTINCT");
6364        }
6365
6366        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6367        // DuckDB: BY NAME
6368        if union.corresponding || union.by_name {
6369            self.write_space();
6370            self.write_keyword("BY NAME");
6371        }
6372        if !union.on_columns.is_empty() {
6373            self.write_space();
6374            self.write_keyword("ON");
6375            self.write(" (");
6376            for (i, col) in union.on_columns.iter().enumerate() {
6377                if i > 0 {
6378                    self.write(", ");
6379                }
6380                self.generate_expression(col)?;
6381            }
6382            self.write(")");
6383        }
6384
6385        if self.config.pretty {
6386            self.write_newline();
6387            self.write_indent();
6388        } else {
6389            self.write_space();
6390        }
6391        self.generate_expression(&union.right)?;
6392        // ORDER BY, LIMIT, OFFSET for the set operation
6393        if let Some(order_by) = &union.order_by {
6394            if self.config.pretty {
6395                self.write_newline();
6396            } else {
6397                self.write_space();
6398            }
6399            self.write_keyword("ORDER BY");
6400            self.write_space();
6401            for (i, ordered) in order_by.expressions.iter().enumerate() {
6402                if i > 0 {
6403                    self.write(", ");
6404                }
6405                self.generate_ordered(ordered)?;
6406            }
6407        }
6408        if let Some(limit) = &union.limit {
6409            if self.config.pretty {
6410                self.write_newline();
6411            } else {
6412                self.write_space();
6413            }
6414            self.write_keyword("LIMIT");
6415            self.write_space();
6416            self.generate_expression(limit)?;
6417        }
6418        if let Some(offset) = &union.offset {
6419            if self.config.pretty {
6420                self.write_newline();
6421            } else {
6422                self.write_space();
6423            }
6424            self.write_keyword("OFFSET");
6425            self.write_space();
6426            self.generate_expression(offset)?;
6427        }
6428        // DISTRIBUTE BY (Hive/Spark)
6429        if let Some(distribute_by) = &union.distribute_by {
6430            self.write_space();
6431            self.write_keyword("DISTRIBUTE BY");
6432            self.write_space();
6433            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6434                if i > 0 {
6435                    self.write(", ");
6436                }
6437                self.generate_expression(expr)?;
6438            }
6439        }
6440        // SORT BY (Hive/Spark)
6441        if let Some(sort_by) = &union.sort_by {
6442            self.write_space();
6443            self.write_keyword("SORT BY");
6444            self.write_space();
6445            for (i, ord) in sort_by.expressions.iter().enumerate() {
6446                if i > 0 {
6447                    self.write(", ");
6448                }
6449                self.generate_ordered(ord)?;
6450            }
6451        }
6452        // CLUSTER BY (Hive/Spark)
6453        if let Some(cluster_by) = &union.cluster_by {
6454            self.write_space();
6455            self.write_keyword("CLUSTER BY");
6456            self.write_space();
6457            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6458                if i > 0 {
6459                    self.write(", ");
6460                }
6461                self.generate_ordered(ord)?;
6462            }
6463        }
6464        Ok(())
6465    }
6466
6467    fn generate_intersect(&mut self, outermost: &Intersect) -> Result<()> {
6468        // Collect the left-recursive chain iteratively to avoid stack overflow
6469        let mut chain: Vec<&Intersect> = vec![outermost];
6470        let mut leftmost: &Expression = &outermost.left;
6471        while let Expression::Intersect(inner) = leftmost {
6472            chain.push(inner);
6473            leftmost = &inner.left;
6474        }
6475
6476        if let Some(with) = &outermost.with {
6477            self.generate_with(with)?;
6478            self.write_space();
6479        }
6480
6481        self.generate_expression(leftmost)?;
6482
6483        for intersect in chain.iter().rev() {
6484            self.generate_intersect_step(intersect)?;
6485        }
6486        Ok(())
6487    }
6488
6489    /// Generate a single INTERSECT step: keyword, right expression, and trailing modifiers.
6490    fn generate_intersect_step(&mut self, intersect: &Intersect) -> Result<()> {
6491        if self.config.pretty {
6492            self.write_newline();
6493            self.write_indent();
6494        } else {
6495            self.write_space();
6496        }
6497
6498        // BigQuery set operation modifiers: [side] [kind] INTERSECT
6499        if let Some(side) = &intersect.side {
6500            self.write_keyword(side);
6501            self.write_space();
6502        }
6503        if let Some(kind) = &intersect.kind {
6504            self.write_keyword(kind);
6505            self.write_space();
6506        }
6507
6508        self.write_keyword("INTERSECT");
6509        if intersect.all {
6510            self.write_space();
6511            self.write_keyword("ALL");
6512        } else if intersect.distinct {
6513            self.write_space();
6514            self.write_keyword("DISTINCT");
6515        }
6516
6517        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6518        // DuckDB: BY NAME
6519        if intersect.corresponding || intersect.by_name {
6520            self.write_space();
6521            self.write_keyword("BY NAME");
6522        }
6523        if !intersect.on_columns.is_empty() {
6524            self.write_space();
6525            self.write_keyword("ON");
6526            self.write(" (");
6527            for (i, col) in intersect.on_columns.iter().enumerate() {
6528                if i > 0 {
6529                    self.write(", ");
6530                }
6531                self.generate_expression(col)?;
6532            }
6533            self.write(")");
6534        }
6535
6536        if self.config.pretty {
6537            self.write_newline();
6538            self.write_indent();
6539        } else {
6540            self.write_space();
6541        }
6542        self.generate_expression(&intersect.right)?;
6543        // ORDER BY, LIMIT, OFFSET for the set operation
6544        if let Some(order_by) = &intersect.order_by {
6545            if self.config.pretty {
6546                self.write_newline();
6547            } else {
6548                self.write_space();
6549            }
6550            self.write_keyword("ORDER BY");
6551            self.write_space();
6552            for (i, ordered) in order_by.expressions.iter().enumerate() {
6553                if i > 0 {
6554                    self.write(", ");
6555                }
6556                self.generate_ordered(ordered)?;
6557            }
6558        }
6559        if let Some(limit) = &intersect.limit {
6560            if self.config.pretty {
6561                self.write_newline();
6562            } else {
6563                self.write_space();
6564            }
6565            self.write_keyword("LIMIT");
6566            self.write_space();
6567            self.generate_expression(limit)?;
6568        }
6569        if let Some(offset) = &intersect.offset {
6570            if self.config.pretty {
6571                self.write_newline();
6572            } else {
6573                self.write_space();
6574            }
6575            self.write_keyword("OFFSET");
6576            self.write_space();
6577            self.generate_expression(offset)?;
6578        }
6579        // DISTRIBUTE BY (Hive/Spark)
6580        if let Some(distribute_by) = &intersect.distribute_by {
6581            self.write_space();
6582            self.write_keyword("DISTRIBUTE BY");
6583            self.write_space();
6584            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6585                if i > 0 {
6586                    self.write(", ");
6587                }
6588                self.generate_expression(expr)?;
6589            }
6590        }
6591        // SORT BY (Hive/Spark)
6592        if let Some(sort_by) = &intersect.sort_by {
6593            self.write_space();
6594            self.write_keyword("SORT BY");
6595            self.write_space();
6596            for (i, ord) in sort_by.expressions.iter().enumerate() {
6597                if i > 0 {
6598                    self.write(", ");
6599                }
6600                self.generate_ordered(ord)?;
6601            }
6602        }
6603        // CLUSTER BY (Hive/Spark)
6604        if let Some(cluster_by) = &intersect.cluster_by {
6605            self.write_space();
6606            self.write_keyword("CLUSTER BY");
6607            self.write_space();
6608            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6609                if i > 0 {
6610                    self.write(", ");
6611                }
6612                self.generate_ordered(ord)?;
6613            }
6614        }
6615        Ok(())
6616    }
6617
6618    fn generate_except(&mut self, outermost: &Except) -> Result<()> {
6619        // Collect the left-recursive chain iteratively to avoid stack overflow
6620        let mut chain: Vec<&Except> = vec![outermost];
6621        let mut leftmost: &Expression = &outermost.left;
6622        while let Expression::Except(inner) = leftmost {
6623            chain.push(inner);
6624            leftmost = &inner.left;
6625        }
6626
6627        if let Some(with) = &outermost.with {
6628            self.generate_with(with)?;
6629            self.write_space();
6630        }
6631
6632        self.generate_expression(leftmost)?;
6633
6634        for except in chain.iter().rev() {
6635            self.generate_except_step(except)?;
6636        }
6637        Ok(())
6638    }
6639
6640    /// Generate a single EXCEPT step: keyword, right expression, and trailing modifiers.
6641    fn generate_except_step(&mut self, except: &Except) -> Result<()> {
6642        use crate::dialects::DialectType;
6643
6644        if self.config.pretty {
6645            self.write_newline();
6646            self.write_indent();
6647        } else {
6648            self.write_space();
6649        }
6650
6651        // BigQuery set operation modifiers: [side] [kind] EXCEPT
6652        if let Some(side) = &except.side {
6653            self.write_keyword(side);
6654            self.write_space();
6655        }
6656        if let Some(kind) = &except.kind {
6657            self.write_keyword(kind);
6658            self.write_space();
6659        }
6660
6661        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
6662        match self.config.dialect {
6663            Some(DialectType::Oracle) if !except.all => {
6664                self.write_keyword("MINUS");
6665            }
6666            Some(DialectType::ClickHouse) => {
6667                // ClickHouse: drop ALL from EXCEPT ALL
6668                self.write_keyword("EXCEPT");
6669                if except.distinct {
6670                    self.write_space();
6671                    self.write_keyword("DISTINCT");
6672                }
6673            }
6674            Some(DialectType::BigQuery) => {
6675                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
6676                self.write_keyword("EXCEPT");
6677                if except.all {
6678                    self.write_space();
6679                    self.write_keyword("ALL");
6680                } else {
6681                    self.write_space();
6682                    self.write_keyword("DISTINCT");
6683                }
6684            }
6685            _ => {
6686                self.write_keyword("EXCEPT");
6687                if except.all {
6688                    self.write_space();
6689                    self.write_keyword("ALL");
6690                } else if except.distinct {
6691                    self.write_space();
6692                    self.write_keyword("DISTINCT");
6693                }
6694            }
6695        }
6696
6697        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6698        // DuckDB: BY NAME
6699        if except.corresponding || except.by_name {
6700            self.write_space();
6701            self.write_keyword("BY NAME");
6702        }
6703        if !except.on_columns.is_empty() {
6704            self.write_space();
6705            self.write_keyword("ON");
6706            self.write(" (");
6707            for (i, col) in except.on_columns.iter().enumerate() {
6708                if i > 0 {
6709                    self.write(", ");
6710                }
6711                self.generate_expression(col)?;
6712            }
6713            self.write(")");
6714        }
6715
6716        if self.config.pretty {
6717            self.write_newline();
6718            self.write_indent();
6719        } else {
6720            self.write_space();
6721        }
6722        self.generate_expression(&except.right)?;
6723        // ORDER BY, LIMIT, OFFSET for the set operation
6724        if let Some(order_by) = &except.order_by {
6725            if self.config.pretty {
6726                self.write_newline();
6727            } else {
6728                self.write_space();
6729            }
6730            self.write_keyword("ORDER BY");
6731            self.write_space();
6732            for (i, ordered) in order_by.expressions.iter().enumerate() {
6733                if i > 0 {
6734                    self.write(", ");
6735                }
6736                self.generate_ordered(ordered)?;
6737            }
6738        }
6739        if let Some(limit) = &except.limit {
6740            if self.config.pretty {
6741                self.write_newline();
6742            } else {
6743                self.write_space();
6744            }
6745            self.write_keyword("LIMIT");
6746            self.write_space();
6747            self.generate_expression(limit)?;
6748        }
6749        if let Some(offset) = &except.offset {
6750            if self.config.pretty {
6751                self.write_newline();
6752            } else {
6753                self.write_space();
6754            }
6755            self.write_keyword("OFFSET");
6756            self.write_space();
6757            self.generate_expression(offset)?;
6758        }
6759        // DISTRIBUTE BY (Hive/Spark)
6760        if let Some(distribute_by) = &except.distribute_by {
6761            self.write_space();
6762            self.write_keyword("DISTRIBUTE BY");
6763            self.write_space();
6764            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6765                if i > 0 {
6766                    self.write(", ");
6767                }
6768                self.generate_expression(expr)?;
6769            }
6770        }
6771        // SORT BY (Hive/Spark)
6772        if let Some(sort_by) = &except.sort_by {
6773            self.write_space();
6774            self.write_keyword("SORT BY");
6775            self.write_space();
6776            for (i, ord) in sort_by.expressions.iter().enumerate() {
6777                if i > 0 {
6778                    self.write(", ");
6779                }
6780                self.generate_ordered(ord)?;
6781            }
6782        }
6783        // CLUSTER BY (Hive/Spark)
6784        if let Some(cluster_by) = &except.cluster_by {
6785            self.write_space();
6786            self.write_keyword("CLUSTER BY");
6787            self.write_space();
6788            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6789                if i > 0 {
6790                    self.write(", ");
6791                }
6792                self.generate_ordered(ord)?;
6793            }
6794        }
6795        Ok(())
6796    }
6797
6798    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
6799        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
6800        let prepend_query_cte = if insert.with.is_none() {
6801            use crate::dialects::DialectType;
6802            let should_prepend = matches!(
6803                self.config.dialect,
6804                Some(DialectType::TSQL)
6805                    | Some(DialectType::Fabric)
6806                    | Some(DialectType::Spark)
6807                    | Some(DialectType::Databricks)
6808                    | Some(DialectType::Hive)
6809            );
6810            if should_prepend {
6811                if let Some(Expression::Select(select)) = &insert.query {
6812                    select.with.clone()
6813                } else {
6814                    None
6815                }
6816            } else {
6817                None
6818            }
6819        } else {
6820            None
6821        };
6822
6823        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
6824        if let Some(with) = &insert.with {
6825            self.generate_with(with)?;
6826            self.write_space();
6827        } else if let Some(with) = &prepend_query_cte {
6828            self.generate_with(with)?;
6829            self.write_space();
6830        }
6831
6832        // Output leading comments before INSERT
6833        for comment in &insert.leading_comments {
6834            self.write_formatted_comment(comment);
6835            self.write(" ");
6836        }
6837
6838        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
6839        if let Some(dir) = &insert.directory {
6840            self.write_keyword("INSERT OVERWRITE");
6841            if dir.local {
6842                self.write_space();
6843                self.write_keyword("LOCAL");
6844            }
6845            self.write_space();
6846            self.write_keyword("DIRECTORY");
6847            self.write_space();
6848            self.write("'");
6849            self.write(&dir.path);
6850            self.write("'");
6851
6852            // ROW FORMAT clause
6853            if let Some(row_format) = &dir.row_format {
6854                self.write_space();
6855                self.write_keyword("ROW FORMAT");
6856                if row_format.delimited {
6857                    self.write_space();
6858                    self.write_keyword("DELIMITED");
6859                }
6860                if let Some(val) = &row_format.fields_terminated_by {
6861                    self.write_space();
6862                    self.write_keyword("FIELDS TERMINATED BY");
6863                    self.write_space();
6864                    self.write("'");
6865                    self.write(val);
6866                    self.write("'");
6867                }
6868                if let Some(val) = &row_format.collection_items_terminated_by {
6869                    self.write_space();
6870                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
6871                    self.write_space();
6872                    self.write("'");
6873                    self.write(val);
6874                    self.write("'");
6875                }
6876                if let Some(val) = &row_format.map_keys_terminated_by {
6877                    self.write_space();
6878                    self.write_keyword("MAP KEYS TERMINATED BY");
6879                    self.write_space();
6880                    self.write("'");
6881                    self.write(val);
6882                    self.write("'");
6883                }
6884                if let Some(val) = &row_format.lines_terminated_by {
6885                    self.write_space();
6886                    self.write_keyword("LINES TERMINATED BY");
6887                    self.write_space();
6888                    self.write("'");
6889                    self.write(val);
6890                    self.write("'");
6891                }
6892                if let Some(val) = &row_format.null_defined_as {
6893                    self.write_space();
6894                    self.write_keyword("NULL DEFINED AS");
6895                    self.write_space();
6896                    self.write("'");
6897                    self.write(val);
6898                    self.write("'");
6899                }
6900            }
6901
6902            // STORED AS clause
6903            if let Some(format) = &dir.stored_as {
6904                self.write_space();
6905                self.write_keyword("STORED AS");
6906                self.write_space();
6907                self.write_keyword(format);
6908            }
6909
6910            // Query (SELECT statement)
6911            if let Some(query) = &insert.query {
6912                self.write_space();
6913                self.generate_expression(query)?;
6914            }
6915
6916            return Ok(());
6917        }
6918
6919        if insert.is_replace {
6920            // MySQL/SQLite REPLACE INTO statement
6921            self.write_keyword("REPLACE INTO");
6922        } else if insert.overwrite {
6923            // Use dialect-specific INSERT OVERWRITE format
6924            self.write_keyword("INSERT");
6925            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6926            if let Some(ref hint) = insert.hint {
6927                self.generate_hint(hint)?;
6928            }
6929            self.write(&self.config.insert_overwrite.to_ascii_uppercase());
6930        } else if let Some(ref action) = insert.conflict_action {
6931            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
6932            self.write_keyword("INSERT OR");
6933            self.write_space();
6934            self.write_keyword(action);
6935            self.write_space();
6936            self.write_keyword("INTO");
6937        } else if insert.ignore {
6938            // MySQL INSERT IGNORE syntax
6939            self.write_keyword("INSERT IGNORE INTO");
6940        } else {
6941            self.write_keyword("INSERT");
6942            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6943            if let Some(ref hint) = insert.hint {
6944                self.generate_hint(hint)?;
6945            }
6946            self.write_space();
6947            self.write_keyword("INTO");
6948        }
6949        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
6950        if let Some(ref func) = insert.function_target {
6951            self.write_space();
6952            self.write_keyword("FUNCTION");
6953            self.write_space();
6954            self.generate_expression(func)?;
6955        } else {
6956            self.write_space();
6957            self.generate_table(&insert.table)?;
6958        }
6959
6960        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
6961        if let Some(ref alias) = insert.alias {
6962            self.write_space();
6963            if insert.alias_explicit_as {
6964                self.write_keyword("AS");
6965                self.write_space();
6966            }
6967            self.generate_identifier(alias)?;
6968        }
6969
6970        // IF EXISTS clause (Hive)
6971        if insert.if_exists {
6972            self.write_space();
6973            self.write_keyword("IF EXISTS");
6974        }
6975
6976        // REPLACE WHERE clause (Databricks)
6977        if let Some(ref replace_where) = insert.replace_where {
6978            if self.config.pretty {
6979                self.write_newline();
6980                self.write_indent();
6981            } else {
6982                self.write_space();
6983            }
6984            self.write_keyword("REPLACE WHERE");
6985            self.write_space();
6986            self.generate_expression(replace_where)?;
6987        }
6988
6989        // Generate PARTITION clause if present
6990        if !insert.partition.is_empty() {
6991            self.write_space();
6992            self.write_keyword("PARTITION");
6993            self.write("(");
6994            for (i, (col, val)) in insert.partition.iter().enumerate() {
6995                if i > 0 {
6996                    self.write(", ");
6997                }
6998                self.generate_identifier(col)?;
6999                if let Some(v) = val {
7000                    self.write(" = ");
7001                    self.generate_expression(v)?;
7002                }
7003            }
7004            self.write(")");
7005        }
7006
7007        // ClickHouse: PARTITION BY expr
7008        if let Some(ref partition_by) = insert.partition_by {
7009            self.write_space();
7010            self.write_keyword("PARTITION BY");
7011            self.write_space();
7012            self.generate_expression(partition_by)?;
7013        }
7014
7015        // ClickHouse: SETTINGS key = val, ...
7016        if !insert.settings.is_empty() {
7017            self.write_space();
7018            self.write_keyword("SETTINGS");
7019            self.write_space();
7020            for (i, setting) in insert.settings.iter().enumerate() {
7021                if i > 0 {
7022                    self.write(", ");
7023                }
7024                self.generate_expression(setting)?;
7025            }
7026        }
7027
7028        if !insert.columns.is_empty() {
7029            if insert.alias.is_some() && insert.alias_explicit_as {
7030                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
7031                self.write("(");
7032            } else {
7033                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
7034                self.write(" (");
7035            }
7036            for (i, col) in insert.columns.iter().enumerate() {
7037                if i > 0 {
7038                    self.write(", ");
7039                }
7040                self.generate_identifier(col)?;
7041            }
7042            self.write(")");
7043        }
7044
7045        // OUTPUT clause (TSQL)
7046        if let Some(ref output) = insert.output {
7047            self.generate_output_clause(output)?;
7048        }
7049
7050        // BY NAME modifier (DuckDB)
7051        if insert.by_name {
7052            self.write_space();
7053            self.write_keyword("BY NAME");
7054        }
7055
7056        if insert.default_values {
7057            self.write_space();
7058            self.write_keyword("DEFAULT VALUES");
7059        } else if let Some(query) = &insert.query {
7060            if self.config.pretty {
7061                self.write_newline();
7062            } else {
7063                self.write_space();
7064            }
7065            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
7066            if prepend_query_cte.is_some() {
7067                if let Expression::Select(select) = query {
7068                    let mut select_no_with = select.clone();
7069                    select_no_with.with = None;
7070                    self.generate_select(&select_no_with)?;
7071                } else {
7072                    self.generate_expression(query)?;
7073                }
7074            } else {
7075                self.generate_expression(query)?;
7076            }
7077        } else if !insert.values.is_empty() {
7078            if self.config.pretty {
7079                // Pretty printing: VALUES on new line, each tuple indented
7080                self.write_newline();
7081                self.write_keyword("VALUES");
7082                self.write_newline();
7083                self.indent_level += 1;
7084                for (i, row) in insert.values.iter().enumerate() {
7085                    if i > 0 {
7086                        self.write(",");
7087                        self.write_newline();
7088                    }
7089                    self.write_indent();
7090                    self.write("(");
7091                    for (j, val) in row.iter().enumerate() {
7092                        if j > 0 {
7093                            self.write(", ");
7094                        }
7095                        self.generate_expression(val)?;
7096                    }
7097                    self.write(")");
7098                }
7099                self.indent_level -= 1;
7100            } else {
7101                // Non-pretty: single line
7102                self.write_space();
7103                self.write_keyword("VALUES");
7104                for (i, row) in insert.values.iter().enumerate() {
7105                    if i > 0 {
7106                        self.write(",");
7107                    }
7108                    self.write(" (");
7109                    for (j, val) in row.iter().enumerate() {
7110                        if j > 0 {
7111                            self.write(", ");
7112                        }
7113                        self.generate_expression(val)?;
7114                    }
7115                    self.write(")");
7116                }
7117            }
7118        }
7119
7120        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
7121        if let Some(ref source) = insert.source {
7122            self.write_space();
7123            self.write_keyword("TABLE");
7124            self.write_space();
7125            self.generate_expression(source)?;
7126        }
7127
7128        // Source alias (MySQL: VALUES (...) AS new_data)
7129        if let Some(alias) = &insert.source_alias {
7130            self.write_space();
7131            self.write_keyword("AS");
7132            self.write_space();
7133            self.generate_identifier(alias)?;
7134        }
7135
7136        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
7137        if let Some(on_conflict) = &insert.on_conflict {
7138            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
7139                self.write_space();
7140                self.generate_expression(on_conflict)?;
7141            }
7142        }
7143
7144        // RETURNING clause
7145        if !insert.returning.is_empty() {
7146            self.write_space();
7147            self.write_keyword("RETURNING");
7148            self.write_space();
7149            for (i, expr) in insert.returning.iter().enumerate() {
7150                if i > 0 {
7151                    self.write(", ");
7152                }
7153                self.generate_expression(expr)?;
7154            }
7155        }
7156
7157        Ok(())
7158    }
7159
7160    fn generate_update(&mut self, update: &Update) -> Result<()> {
7161        // Output leading comments before UPDATE
7162        for comment in &update.leading_comments {
7163            self.write_formatted_comment(comment);
7164            self.write(" ");
7165        }
7166
7167        // WITH clause (CTEs)
7168        if let Some(ref with) = update.with {
7169            self.generate_with(with)?;
7170            self.write_space();
7171        }
7172
7173        self.write_keyword("UPDATE");
7174        self.write_space();
7175        self.generate_table(&update.table)?;
7176
7177        let mysql_like_update_from = matches!(
7178            self.config.dialect,
7179            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
7180        ) && update.from_clause.is_some();
7181
7182        let mut set_pairs = update.set.clone();
7183
7184        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
7185        let mut pre_set_joins = update.table_joins.clone();
7186        if mysql_like_update_from {
7187            let target_name = update
7188                .table
7189                .alias
7190                .as_ref()
7191                .map(|a| a.name.clone())
7192                .unwrap_or_else(|| update.table.name.name.clone());
7193
7194            for (col, _) in &mut set_pairs {
7195                if !col.name.contains('.') {
7196                    col.name = format!("{}.{}", target_name, col.name);
7197                }
7198            }
7199
7200            if let Some(from_clause) = &update.from_clause {
7201                for table_expr in &from_clause.expressions {
7202                    pre_set_joins.push(crate::expressions::Join {
7203                        this: table_expr.clone(),
7204                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7205                            value: true,
7206                        })),
7207                        using: Vec::new(),
7208                        kind: crate::expressions::JoinKind::Inner,
7209                        use_inner_keyword: false,
7210                        use_outer_keyword: false,
7211                        deferred_condition: false,
7212                        join_hint: None,
7213                        match_condition: None,
7214                        pivots: Vec::new(),
7215                        comments: Vec::new(),
7216                        nesting_group: 0,
7217                        directed: false,
7218                    });
7219                }
7220            }
7221            for join in &update.from_joins {
7222                let mut join = join.clone();
7223                if join.on.is_none() && join.using.is_empty() {
7224                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7225                        value: true,
7226                    }));
7227                }
7228                pre_set_joins.push(join);
7229            }
7230        }
7231
7232        // Extra tables for multi-table UPDATE (MySQL syntax)
7233        for extra_table in &update.extra_tables {
7234            self.write(", ");
7235            self.generate_table(extra_table)?;
7236        }
7237
7238        // JOINs attached to the table list (MySQL multi-table syntax)
7239        for join in &pre_set_joins {
7240            // generate_join already adds a leading space
7241            self.generate_join(join)?;
7242        }
7243
7244        // Teradata: FROM clause comes before SET
7245        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
7246        if teradata_from_before_set && !mysql_like_update_from {
7247            if let Some(ref from_clause) = update.from_clause {
7248                self.write_space();
7249                self.write_keyword("FROM");
7250                self.write_space();
7251                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7252                    if i > 0 {
7253                        self.write(", ");
7254                    }
7255                    self.generate_expression(table_expr)?;
7256                }
7257            }
7258            for join in &update.from_joins {
7259                self.generate_join(join)?;
7260            }
7261        }
7262
7263        self.write_space();
7264        self.write_keyword("SET");
7265        self.write_space();
7266
7267        for (i, (col, val)) in set_pairs.iter().enumerate() {
7268            if i > 0 {
7269                self.write(", ");
7270            }
7271            self.generate_identifier(col)?;
7272            self.write(" = ");
7273            self.generate_expression(val)?;
7274        }
7275
7276        // OUTPUT clause (TSQL)
7277        if let Some(ref output) = update.output {
7278            self.generate_output_clause(output)?;
7279        }
7280
7281        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
7282        if !mysql_like_update_from && !teradata_from_before_set {
7283            if let Some(ref from_clause) = update.from_clause {
7284                self.write_space();
7285                self.write_keyword("FROM");
7286                self.write_space();
7287                // Generate each table in the FROM clause
7288                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7289                    if i > 0 {
7290                        self.write(", ");
7291                    }
7292                    self.generate_expression(table_expr)?;
7293                }
7294            }
7295        }
7296
7297        if !mysql_like_update_from && !teradata_from_before_set {
7298            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
7299            for join in &update.from_joins {
7300                self.generate_join(join)?;
7301            }
7302        }
7303
7304        if let Some(where_clause) = &update.where_clause {
7305            self.write_space();
7306            self.write_keyword("WHERE");
7307            self.write_space();
7308            self.generate_expression(&where_clause.this)?;
7309        }
7310
7311        // RETURNING clause
7312        if !update.returning.is_empty() {
7313            self.write_space();
7314            self.write_keyword("RETURNING");
7315            self.write_space();
7316            for (i, expr) in update.returning.iter().enumerate() {
7317                if i > 0 {
7318                    self.write(", ");
7319                }
7320                self.generate_expression(expr)?;
7321            }
7322        }
7323
7324        // ORDER BY clause (MySQL)
7325        if let Some(ref order_by) = update.order_by {
7326            self.write_space();
7327            self.generate_order_by(order_by)?;
7328        }
7329
7330        // LIMIT clause (MySQL)
7331        if let Some(ref limit) = update.limit {
7332            self.write_space();
7333            self.write_keyword("LIMIT");
7334            self.write_space();
7335            self.generate_expression(limit)?;
7336        }
7337
7338        Ok(())
7339    }
7340
7341    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
7342        // Output WITH clause if present
7343        if let Some(with) = &delete.with {
7344            self.generate_with(with)?;
7345            self.write_space();
7346        }
7347
7348        // Output leading comments before DELETE
7349        for comment in &delete.leading_comments {
7350            self.write_formatted_comment(comment);
7351            self.write(" ");
7352        }
7353
7354        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
7355        if !delete.tables.is_empty() && !delete.tables_from_using {
7356            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
7357            self.write_keyword("DELETE");
7358            self.write_space();
7359            for (i, tbl) in delete.tables.iter().enumerate() {
7360                if i > 0 {
7361                    self.write(", ");
7362                }
7363                self.generate_table(tbl)?;
7364            }
7365            // TSQL: OUTPUT clause between target table and FROM
7366            if let Some(ref output) = delete.output {
7367                self.generate_output_clause(output)?;
7368            }
7369            self.write_space();
7370            self.write_keyword("FROM");
7371            self.write_space();
7372            self.generate_table(&delete.table)?;
7373        } else if !delete.tables.is_empty() && delete.tables_from_using {
7374            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
7375            self.write_keyword("DELETE FROM");
7376            self.write_space();
7377            for (i, tbl) in delete.tables.iter().enumerate() {
7378                if i > 0 {
7379                    self.write(", ");
7380                }
7381                self.generate_table(tbl)?;
7382            }
7383        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
7384            // BigQuery-style DELETE without FROM keyword
7385            self.write_keyword("DELETE");
7386            self.write_space();
7387            self.generate_table(&delete.table)?;
7388        } else {
7389            self.write_keyword("DELETE FROM");
7390            self.write_space();
7391            self.generate_table(&delete.table)?;
7392        }
7393
7394        // ClickHouse: ON CLUSTER clause
7395        if let Some(ref on_cluster) = delete.on_cluster {
7396            self.write_space();
7397            self.generate_on_cluster(on_cluster)?;
7398        }
7399
7400        // FORCE INDEX hint (MySQL)
7401        if let Some(ref idx) = delete.force_index {
7402            self.write_space();
7403            self.write_keyword("FORCE INDEX");
7404            self.write(" (");
7405            self.write(idx);
7406            self.write(")");
7407        }
7408
7409        // Optional alias
7410        if let Some(ref alias) = delete.alias {
7411            self.write_space();
7412            if delete.alias_explicit_as
7413                || matches!(self.config.dialect, Some(DialectType::BigQuery))
7414            {
7415                self.write_keyword("AS");
7416                self.write_space();
7417            }
7418            self.generate_identifier(alias)?;
7419        }
7420
7421        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
7422        if !delete.tables_from_using {
7423            for join in &delete.joins {
7424                self.generate_join(join)?;
7425            }
7426        }
7427
7428        // USING clause (PostgreSQL/DuckDB/MySQL)
7429        if !delete.using.is_empty() {
7430            self.write_space();
7431            self.write_keyword("USING");
7432            for (i, table) in delete.using.iter().enumerate() {
7433                if i > 0 {
7434                    self.write(",");
7435                }
7436                self.write_space();
7437                // Check if the table has subquery hints (DuckDB USING with subquery)
7438                if !table.hints.is_empty() && table.name.is_empty() {
7439                    // Subquery in USING: (VALUES ...) AS alias(cols)
7440                    self.generate_expression(&table.hints[0])?;
7441                    if let Some(ref alias) = table.alias {
7442                        self.write_space();
7443                        if table.alias_explicit_as {
7444                            self.write_keyword("AS");
7445                            self.write_space();
7446                        }
7447                        self.generate_identifier(alias)?;
7448                        if !table.column_aliases.is_empty() {
7449                            self.write("(");
7450                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
7451                                if j > 0 {
7452                                    self.write(", ");
7453                                }
7454                                self.generate_identifier(col_alias)?;
7455                            }
7456                            self.write(")");
7457                        }
7458                    }
7459                } else {
7460                    self.generate_table(table)?;
7461                }
7462            }
7463        }
7464
7465        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
7466        if delete.tables_from_using {
7467            for join in &delete.joins {
7468                self.generate_join(join)?;
7469            }
7470        }
7471
7472        // OUTPUT clause (TSQL) - only if not already emitted in the early position
7473        let output_already_emitted =
7474            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
7475        if !output_already_emitted {
7476            if let Some(ref output) = delete.output {
7477                self.generate_output_clause(output)?;
7478            }
7479        }
7480
7481        if let Some(where_clause) = &delete.where_clause {
7482            self.write_space();
7483            self.write_keyword("WHERE");
7484            self.write_space();
7485            self.generate_expression(&where_clause.this)?;
7486        }
7487
7488        // ORDER BY clause (MySQL)
7489        if let Some(ref order_by) = delete.order_by {
7490            self.write_space();
7491            self.generate_order_by(order_by)?;
7492        }
7493
7494        // LIMIT clause (MySQL)
7495        if let Some(ref limit) = delete.limit {
7496            self.write_space();
7497            self.write_keyword("LIMIT");
7498            self.write_space();
7499            self.generate_expression(limit)?;
7500        }
7501
7502        // RETURNING clause (PostgreSQL)
7503        if !delete.returning.is_empty() {
7504            self.write_space();
7505            self.write_keyword("RETURNING");
7506            self.write_space();
7507            for (i, expr) in delete.returning.iter().enumerate() {
7508                if i > 0 {
7509                    self.write(", ");
7510                }
7511                self.generate_expression(expr)?;
7512            }
7513        }
7514
7515        Ok(())
7516    }
7517
7518    // ==================== DDL Generation ====================
7519
7520    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
7521        // Athena: Determine if this is Hive-style DDL or Trino-style DML
7522        // CREATE TABLE AS SELECT uses Trino (double quotes)
7523        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
7524        let saved_athena_hive_context = self.athena_hive_context;
7525        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
7526        if matches!(
7527            self.config.dialect,
7528            Some(crate::dialects::DialectType::Athena)
7529        ) {
7530            // Use Hive context if:
7531            // 1. It's an EXTERNAL table, OR
7532            // 2. There's no AS SELECT clause
7533            let is_external = ct
7534                .table_modifier
7535                .as_ref()
7536                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
7537                .unwrap_or(false);
7538            let has_as_select = ct.as_select.is_some();
7539            self.athena_hive_context = is_external || !has_as_select;
7540        }
7541
7542        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
7543        if matches!(
7544            self.config.dialect,
7545            Some(crate::dialects::DialectType::TSQL)
7546        ) {
7547            if let Some(ref query) = ct.as_select {
7548                // Output WITH CTE clause if present
7549                if let Some(with_cte) = &ct.with_cte {
7550                    self.generate_with(with_cte)?;
7551                    self.write_space();
7552                }
7553
7554                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
7555                self.write_keyword("SELECT");
7556                self.write(" * ");
7557                self.write_keyword("INTO");
7558                self.write_space();
7559
7560                // If temporary, prefix with # for TSQL temp table
7561                if ct.temporary {
7562                    self.write("#");
7563                }
7564                self.generate_table(&ct.name)?;
7565
7566                self.write_space();
7567                self.write_keyword("FROM");
7568                self.write(" (");
7569                // For TSQL, add aliases to select columns to preserve column names
7570                let aliased_query = Self::add_column_aliases_to_query(query.clone());
7571                self.generate_expression(&aliased_query)?;
7572                self.write(") ");
7573                self.write_keyword("AS");
7574                self.write(" temp");
7575                return Ok(());
7576            }
7577        }
7578
7579        // Output WITH CTE clause if present
7580        if let Some(with_cte) = &ct.with_cte {
7581            self.generate_with(with_cte)?;
7582            self.write_space();
7583        }
7584
7585        // Output leading comments before CREATE
7586        for comment in &ct.leading_comments {
7587            self.write_formatted_comment(comment);
7588            self.write(" ");
7589        }
7590        self.write_keyword("CREATE");
7591
7592        if ct.or_replace {
7593            self.write_space();
7594            self.write_keyword("OR REPLACE");
7595        }
7596
7597        if ct.temporary {
7598            self.write_space();
7599            // Oracle uses GLOBAL TEMPORARY TABLE syntax
7600            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
7601                self.write_keyword("GLOBAL TEMPORARY");
7602            } else {
7603                self.write_keyword("TEMPORARY");
7604            }
7605        }
7606
7607        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
7608        let is_dictionary = ct
7609            .table_modifier
7610            .as_ref()
7611            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
7612            .unwrap_or(false);
7613        if let Some(ref modifier) = ct.table_modifier {
7614            // TRANSIENT is Snowflake-specific - skip for other dialects
7615            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
7616                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
7617            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
7618            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
7619                || modifier.eq_ignore_ascii_case("SET")
7620                || modifier.eq_ignore_ascii_case("MULTISET")
7621                || modifier.to_ascii_uppercase().contains("VOLATILE")
7622                || modifier.to_ascii_uppercase().starts_with("SET ")
7623                || modifier.to_ascii_uppercase().starts_with("MULTISET ");
7624            let skip_teradata =
7625                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
7626            if !skip_transient && !skip_teradata {
7627                self.write_space();
7628                self.write_keyword(modifier);
7629            }
7630        }
7631
7632        if !is_dictionary {
7633            self.write_space();
7634            self.write_keyword("TABLE");
7635        }
7636
7637        if ct.if_not_exists {
7638            self.write_space();
7639            self.write_keyword("IF NOT EXISTS");
7640        }
7641
7642        self.write_space();
7643        self.generate_table(&ct.name)?;
7644
7645        // ClickHouse: UUID 'xxx' clause after table name
7646        if let Some(ref uuid) = ct.uuid {
7647            self.write_space();
7648            self.write_keyword("UUID");
7649            self.write(" '");
7650            self.write(uuid);
7651            self.write("'");
7652        }
7653
7654        // ClickHouse: ON CLUSTER clause
7655        if let Some(ref on_cluster) = ct.on_cluster {
7656            self.write_space();
7657            self.generate_on_cluster(on_cluster)?;
7658        }
7659
7660        // Teradata: options after table name before column list (comma-separated)
7661        if matches!(
7662            self.config.dialect,
7663            Some(crate::dialects::DialectType::Teradata)
7664        ) && !ct.teradata_post_name_options.is_empty()
7665        {
7666            for opt in &ct.teradata_post_name_options {
7667                self.write(", ");
7668                self.write(opt);
7669            }
7670        }
7671
7672        // Snowflake: COPY GRANTS clause
7673        if ct.copy_grants {
7674            self.write_space();
7675            self.write_keyword("COPY GRANTS");
7676        }
7677
7678        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
7679        if let Some(ref using_template) = ct.using_template {
7680            self.write_space();
7681            self.write_keyword("USING TEMPLATE");
7682            self.write_space();
7683            self.generate_expression(using_template)?;
7684            return Ok(());
7685        }
7686
7687        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
7688        if let Some(ref clone_source) = ct.clone_source {
7689            self.write_space();
7690            if ct.is_copy && self.config.supports_table_copy {
7691                // BigQuery uses COPY
7692                self.write_keyword("COPY");
7693            } else if ct.shallow_clone {
7694                self.write_keyword("SHALLOW CLONE");
7695            } else {
7696                self.write_keyword("CLONE");
7697            }
7698            self.write_space();
7699            self.generate_table(clone_source)?;
7700            // Generate AT/BEFORE time travel clause (stored as Raw expression)
7701            if let Some(ref at_clause) = ct.clone_at_clause {
7702                self.write_space();
7703                self.generate_expression(at_clause)?;
7704            }
7705            return Ok(());
7706        }
7707
7708        // Handle PARTITION OF property
7709        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
7710        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
7711        if let Some(ref partition_of) = ct.partition_of {
7712            self.write_space();
7713
7714            // Extract the PartitionedOfProperty parts to generate them separately
7715            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
7716                // Output: PARTITION OF <table>
7717                self.write_keyword("PARTITION OF");
7718                self.write_space();
7719                self.generate_expression(&pop.this)?;
7720
7721                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
7722                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7723                    self.write(" (");
7724                    let mut first = true;
7725                    for col in &ct.columns {
7726                        if !first {
7727                            self.write(", ");
7728                        }
7729                        first = false;
7730                        self.generate_column_def(col)?;
7731                    }
7732                    for constraint in &ct.constraints {
7733                        if !first {
7734                            self.write(", ");
7735                        }
7736                        first = false;
7737                        self.generate_table_constraint(constraint)?;
7738                    }
7739                    self.write(")");
7740                }
7741
7742                // Output partition bound spec: FOR VALUES ... or DEFAULT
7743                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
7744                    self.write_space();
7745                    self.write_keyword("FOR VALUES");
7746                    self.write_space();
7747                    self.generate_expression(&pop.expression)?;
7748                } else {
7749                    self.write_space();
7750                    self.write_keyword("DEFAULT");
7751                }
7752            } else {
7753                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
7754                self.generate_expression(partition_of)?;
7755
7756                // Output columns/constraints if present
7757                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7758                    self.write(" (");
7759                    let mut first = true;
7760                    for col in &ct.columns {
7761                        if !first {
7762                            self.write(", ");
7763                        }
7764                        first = false;
7765                        self.generate_column_def(col)?;
7766                    }
7767                    for constraint in &ct.constraints {
7768                        if !first {
7769                            self.write(", ");
7770                        }
7771                        first = false;
7772                        self.generate_table_constraint(constraint)?;
7773                    }
7774                    self.write(")");
7775                }
7776            }
7777
7778            // Output table properties (e.g., PARTITION BY RANGE(population))
7779            for prop in &ct.properties {
7780                self.write_space();
7781                self.generate_expression(prop)?;
7782            }
7783
7784            return Ok(());
7785        }
7786
7787        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
7788        // This matches Python sqlglot's behavior for SQLite dialect
7789        self.sqlite_inline_pk_columns.clear();
7790        if matches!(
7791            self.config.dialect,
7792            Some(crate::dialects::DialectType::SQLite)
7793        ) {
7794            for constraint in &ct.constraints {
7795                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7796                    // Only inline if: single column, no constraint name, and column exists in table
7797                    if columns.len() == 1 && name.is_none() {
7798                        let pk_col_name = columns[0].name.to_ascii_lowercase();
7799                        // Check if this column exists in the table
7800                        if ct
7801                            .columns
7802                            .iter()
7803                            .any(|c| c.name.name.to_ascii_lowercase() == pk_col_name)
7804                        {
7805                            self.sqlite_inline_pk_columns.insert(pk_col_name);
7806                        }
7807                    }
7808                }
7809            }
7810        }
7811
7812        // Output columns if present (even for CTAS with columns)
7813        if !ct.columns.is_empty() {
7814            if self.config.pretty {
7815                // Pretty print: each column on new line
7816                self.write(" (");
7817                self.write_newline();
7818                self.indent_level += 1;
7819                for (i, col) in ct.columns.iter().enumerate() {
7820                    if i > 0 {
7821                        self.write(",");
7822                        self.write_newline();
7823                    }
7824                    self.write_indent();
7825                    self.generate_column_def(col)?;
7826                }
7827                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7828                for constraint in &ct.constraints {
7829                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7830                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7831                        if columns.len() == 1
7832                            && name.is_none()
7833                            && self
7834                                .sqlite_inline_pk_columns
7835                                .contains(&columns[0].name.to_ascii_lowercase())
7836                        {
7837                            continue;
7838                        }
7839                    }
7840                    self.write(",");
7841                    self.write_newline();
7842                    self.write_indent();
7843                    self.generate_table_constraint(constraint)?;
7844                }
7845                self.indent_level -= 1;
7846                self.write_newline();
7847                self.write(")");
7848            } else {
7849                self.write(" (");
7850                for (i, col) in ct.columns.iter().enumerate() {
7851                    if i > 0 {
7852                        self.write(", ");
7853                    }
7854                    self.generate_column_def(col)?;
7855                }
7856                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7857                let mut first_constraint = true;
7858                for constraint in &ct.constraints {
7859                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7860                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7861                        if columns.len() == 1
7862                            && name.is_none()
7863                            && self
7864                                .sqlite_inline_pk_columns
7865                                .contains(&columns[0].name.to_ascii_lowercase())
7866                        {
7867                            continue;
7868                        }
7869                    }
7870                    if first_constraint {
7871                        self.write(", ");
7872                        first_constraint = false;
7873                    } else {
7874                        self.write(", ");
7875                    }
7876                    self.generate_table_constraint(constraint)?;
7877                }
7878                self.write(")");
7879            }
7880        } else if !ct.constraints.is_empty() {
7881            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
7882            let has_like_only = ct
7883                .constraints
7884                .iter()
7885                .all(|c| matches!(c, TableConstraint::Like { .. }));
7886            let has_tags_only = ct
7887                .constraints
7888                .iter()
7889                .all(|c| matches!(c, TableConstraint::Tags(_)));
7890            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
7891            // Most dialects: CREATE TABLE A LIKE B (no parens)
7892            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
7893            let is_pg_like = matches!(
7894                self.config.dialect,
7895                Some(crate::dialects::DialectType::PostgreSQL)
7896                    | Some(crate::dialects::DialectType::CockroachDB)
7897                    | Some(crate::dialects::DialectType::Materialize)
7898                    | Some(crate::dialects::DialectType::RisingWave)
7899                    | Some(crate::dialects::DialectType::Redshift)
7900                    | Some(crate::dialects::DialectType::Presto)
7901                    | Some(crate::dialects::DialectType::Trino)
7902                    | Some(crate::dialects::DialectType::Athena)
7903            );
7904            let use_parens = if has_like_only {
7905                is_pg_like
7906            } else {
7907                !has_tags_only
7908            };
7909            if self.config.pretty && use_parens {
7910                self.write(" (");
7911                self.write_newline();
7912                self.indent_level += 1;
7913                for (i, constraint) in ct.constraints.iter().enumerate() {
7914                    if i > 0 {
7915                        self.write(",");
7916                        self.write_newline();
7917                    }
7918                    self.write_indent();
7919                    self.generate_table_constraint(constraint)?;
7920                }
7921                self.indent_level -= 1;
7922                self.write_newline();
7923                self.write(")");
7924            } else {
7925                if use_parens {
7926                    self.write(" (");
7927                } else {
7928                    self.write_space();
7929                }
7930                for (i, constraint) in ct.constraints.iter().enumerate() {
7931                    if i > 0 {
7932                        self.write(", ");
7933                    }
7934                    self.generate_table_constraint(constraint)?;
7935                }
7936                if use_parens {
7937                    self.write(")");
7938                }
7939            }
7940        }
7941
7942        // TSQL ON filegroup or ON filegroup (partition_column) clause
7943        if let Some(ref on_prop) = ct.on_property {
7944            self.write(" ");
7945            self.write_keyword("ON");
7946            self.write(" ");
7947            self.generate_expression(&on_prop.this)?;
7948        }
7949
7950        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
7951        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
7952        if !is_clickhouse {
7953            for prop in &ct.properties {
7954                if let Expression::SchemaCommentProperty(_) = prop {
7955                    if self.config.pretty {
7956                        self.write_newline();
7957                    } else {
7958                        self.write_space();
7959                    }
7960                    self.generate_expression(prop)?;
7961                }
7962            }
7963        }
7964
7965        // WITH properties (output after columns if columns exist, otherwise before AS)
7966        if !ct.with_properties.is_empty() {
7967            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
7968            let is_snowflake_special_table = matches!(
7969                self.config.dialect,
7970                Some(crate::dialects::DialectType::Snowflake)
7971            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
7972                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
7973            if is_snowflake_special_table {
7974                for (key, value) in &ct.with_properties {
7975                    self.write_space();
7976                    self.write(key);
7977                    self.write("=");
7978                    self.write(value);
7979                }
7980            } else if self.config.pretty {
7981                self.write_newline();
7982                self.write_keyword("WITH");
7983                self.write(" (");
7984                self.write_newline();
7985                self.indent_level += 1;
7986                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
7987                    if i > 0 {
7988                        self.write(",");
7989                        self.write_newline();
7990                    }
7991                    self.write_indent();
7992                    self.write(key);
7993                    self.write("=");
7994                    self.write(value);
7995                }
7996                self.indent_level -= 1;
7997                self.write_newline();
7998                self.write(")");
7999            } else {
8000                self.write_space();
8001                self.write_keyword("WITH");
8002                self.write(" (");
8003                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8004                    if i > 0 {
8005                        self.write(", ");
8006                    }
8007                    self.write(key);
8008                    self.write("=");
8009                    self.write(value);
8010                }
8011                self.write(")");
8012            }
8013        }
8014
8015        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
8016            if is_clickhouse && ct.as_select.is_some() {
8017                let mut pre = Vec::new();
8018                let mut post = Vec::new();
8019                for prop in &ct.properties {
8020                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
8021                        post.push(prop);
8022                    } else {
8023                        pre.push(prop);
8024                    }
8025                }
8026                (pre, post)
8027            } else {
8028                (ct.properties.iter().collect(), Vec::new())
8029            };
8030
8031        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
8032        for prop in pre_as_properties {
8033            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
8034            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
8035                continue;
8036            }
8037            if self.config.pretty {
8038                self.write_newline();
8039            } else {
8040                self.write_space();
8041            }
8042            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
8043            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
8044            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
8045            if let Expression::Properties(props) = prop {
8046                let is_hive_dialect = matches!(
8047                    self.config.dialect,
8048                    Some(crate::dialects::DialectType::Hive)
8049                        | Some(crate::dialects::DialectType::Spark)
8050                        | Some(crate::dialects::DialectType::Databricks)
8051                        | Some(crate::dialects::DialectType::Athena)
8052                );
8053                let is_doris_starrocks = matches!(
8054                    self.config.dialect,
8055                    Some(crate::dialects::DialectType::Doris)
8056                        | Some(crate::dialects::DialectType::StarRocks)
8057                );
8058                if is_hive_dialect {
8059                    self.generate_tblproperties_clause(&props.expressions)?;
8060                } else if is_doris_starrocks {
8061                    self.generate_properties_clause(&props.expressions)?;
8062                } else {
8063                    self.generate_options_clause(&props.expressions)?;
8064                }
8065            } else {
8066                self.generate_expression(prop)?;
8067            }
8068        }
8069
8070        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
8071        for prop in &ct.post_table_properties {
8072            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
8073                self.write(" WITH(");
8074                self.generate_system_versioning_content(svp)?;
8075                self.write(")");
8076            } else if let Expression::Properties(props) = prop {
8077                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
8078                let is_doris_starrocks = matches!(
8079                    self.config.dialect,
8080                    Some(crate::dialects::DialectType::Doris)
8081                        | Some(crate::dialects::DialectType::StarRocks)
8082                );
8083                self.write_space();
8084                if is_doris_starrocks {
8085                    self.generate_properties_clause(&props.expressions)?;
8086                } else {
8087                    self.generate_options_clause(&props.expressions)?;
8088                }
8089            } else {
8090                self.write_space();
8091                self.generate_expression(prop)?;
8092            }
8093        }
8094
8095        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
8096        // Only output for StarRocks target
8097        if let Some(ref rollup) = ct.rollup {
8098            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
8099                self.write_space();
8100                self.generate_rollup_property(rollup)?;
8101            }
8102        }
8103
8104        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
8105        // Only output for MySQL-compatible dialects; strip for others during transpilation
8106        // COMMENT is also used by Hive/Spark so we selectively preserve it
8107        let is_mysql_compatible = matches!(
8108            self.config.dialect,
8109            Some(DialectType::MySQL)
8110                | Some(DialectType::SingleStore)
8111                | Some(DialectType::Doris)
8112                | Some(DialectType::StarRocks)
8113                | None
8114        );
8115        let is_hive_compatible = matches!(
8116            self.config.dialect,
8117            Some(DialectType::Hive)
8118                | Some(DialectType::Spark)
8119                | Some(DialectType::Databricks)
8120                | Some(DialectType::Athena)
8121        );
8122        let mysql_pretty_options =
8123            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
8124        for (key, value) in &ct.mysql_table_options {
8125            // Skip non-MySQL-specific options for non-MySQL targets
8126            let should_output = if is_mysql_compatible {
8127                true
8128            } else if is_hive_compatible && key == "COMMENT" {
8129                true // COMMENT is valid in Hive/Spark table definitions
8130            } else {
8131                false
8132            };
8133            if should_output {
8134                if mysql_pretty_options {
8135                    self.write_newline();
8136                    self.write_indent();
8137                } else {
8138                    self.write_space();
8139                }
8140                self.write_keyword(key);
8141                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
8142                if key == "COMMENT" && !self.config.schema_comment_with_eq {
8143                    self.write_space();
8144                } else {
8145                    self.write("=");
8146                }
8147                self.write(value);
8148            }
8149        }
8150
8151        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
8152        if ct.temporary
8153            && matches!(
8154                self.config.dialect,
8155                Some(DialectType::Spark) | Some(DialectType::Databricks)
8156            )
8157            && ct.as_select.is_none()
8158        {
8159            self.write_space();
8160            self.write_keyword("USING PARQUET");
8161        }
8162
8163        // PostgreSQL INHERITS clause
8164        if !ct.inherits.is_empty() {
8165            self.write_space();
8166            self.write_keyword("INHERITS");
8167            self.write(" (");
8168            for (i, parent) in ct.inherits.iter().enumerate() {
8169                if i > 0 {
8170                    self.write(", ");
8171                }
8172                self.generate_table(parent)?;
8173            }
8174            self.write(")");
8175        }
8176
8177        // CREATE TABLE AS SELECT
8178        if let Some(ref query) = ct.as_select {
8179            self.write_space();
8180            self.write_keyword("AS");
8181            self.write_space();
8182            if ct.as_select_parenthesized {
8183                self.write("(");
8184            }
8185            self.generate_expression(query)?;
8186            if ct.as_select_parenthesized {
8187                self.write(")");
8188            }
8189
8190            // Teradata: WITH DATA / WITH NO DATA
8191            if let Some(with_data) = ct.with_data {
8192                self.write_space();
8193                self.write_keyword("WITH");
8194                if !with_data {
8195                    self.write_space();
8196                    self.write_keyword("NO");
8197                }
8198                self.write_space();
8199                self.write_keyword("DATA");
8200            }
8201
8202            // Teradata: AND STATISTICS / AND NO STATISTICS
8203            if let Some(with_statistics) = ct.with_statistics {
8204                self.write_space();
8205                self.write_keyword("AND");
8206                if !with_statistics {
8207                    self.write_space();
8208                    self.write_keyword("NO");
8209                }
8210                self.write_space();
8211                self.write_keyword("STATISTICS");
8212            }
8213
8214            // Teradata: Index specifications
8215            for index in &ct.teradata_indexes {
8216                self.write_space();
8217                match index.kind {
8218                    TeradataIndexKind::NoPrimary => {
8219                        self.write_keyword("NO PRIMARY INDEX");
8220                    }
8221                    TeradataIndexKind::Primary => {
8222                        self.write_keyword("PRIMARY INDEX");
8223                    }
8224                    TeradataIndexKind::PrimaryAmp => {
8225                        self.write_keyword("PRIMARY AMP INDEX");
8226                    }
8227                    TeradataIndexKind::Unique => {
8228                        self.write_keyword("UNIQUE INDEX");
8229                    }
8230                    TeradataIndexKind::UniquePrimary => {
8231                        self.write_keyword("UNIQUE PRIMARY INDEX");
8232                    }
8233                    TeradataIndexKind::Secondary => {
8234                        self.write_keyword("INDEX");
8235                    }
8236                }
8237                // Output index name if present
8238                if let Some(ref name) = index.name {
8239                    self.write_space();
8240                    self.write(name);
8241                }
8242                // Output columns if present
8243                if !index.columns.is_empty() {
8244                    self.write(" (");
8245                    for (i, col) in index.columns.iter().enumerate() {
8246                        if i > 0 {
8247                            self.write(", ");
8248                        }
8249                        self.write(col);
8250                    }
8251                    self.write(")");
8252                }
8253            }
8254
8255            // Teradata: ON COMMIT behavior for volatile tables
8256            if let Some(ref on_commit) = ct.on_commit {
8257                self.write_space();
8258                self.write_keyword("ON COMMIT");
8259                self.write_space();
8260                match on_commit {
8261                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8262                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8263                }
8264            }
8265
8266            if !post_as_properties.is_empty() {
8267                for prop in post_as_properties {
8268                    self.write_space();
8269                    self.generate_expression(prop)?;
8270                }
8271            }
8272
8273            // Restore Athena Hive context before early return
8274            self.athena_hive_context = saved_athena_hive_context;
8275            return Ok(());
8276        }
8277
8278        // ON COMMIT behavior (for non-CTAS tables)
8279        if let Some(ref on_commit) = ct.on_commit {
8280            self.write_space();
8281            self.write_keyword("ON COMMIT");
8282            self.write_space();
8283            match on_commit {
8284                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8285                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8286            }
8287        }
8288
8289        // Restore Athena Hive context
8290        self.athena_hive_context = saved_athena_hive_context;
8291
8292        Ok(())
8293    }
8294
8295    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
8296    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
8297    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
8298        // Output column name
8299        self.generate_identifier(&col.name)?;
8300        // Output data type if known
8301        if !matches!(col.data_type, DataType::Unknown) {
8302            self.write_space();
8303            self.generate_data_type(&col.data_type)?;
8304        }
8305        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
8306        for constraint in &col.constraints {
8307            if let ColumnConstraint::Path(path_expr) = constraint {
8308                self.write_space();
8309                self.write_keyword("PATH");
8310                self.write_space();
8311                self.generate_expression(path_expr)?;
8312            }
8313        }
8314        Ok(())
8315    }
8316
8317    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
8318        // Check if this is a TSQL computed column (no data type)
8319        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8320            && col
8321                .constraints
8322                .iter()
8323                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8324        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
8325        let omit_computed_type = !self.config.computed_column_with_type
8326            && col
8327                .constraints
8328                .iter()
8329                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8330
8331        // Check if this is a partition column spec (no data type, type is Unknown)
8332        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
8333        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
8334
8335        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
8336        // Also check the no_type flag for SQLite columns without types
8337        let has_no_type = col.no_type
8338            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8339                && col.constraints.is_empty());
8340
8341        self.generate_identifier(&col.name)?;
8342
8343        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
8344        let serial_expansion = if matches!(
8345            self.config.dialect,
8346            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
8347        ) {
8348            if let DataType::Custom { ref name } = col.data_type {
8349                if name.eq_ignore_ascii_case("SERIAL") {
8350                    Some("INT")
8351                } else if name.eq_ignore_ascii_case("BIGSERIAL") {
8352                    Some("BIGINT")
8353                } else if name.eq_ignore_ascii_case("SMALLSERIAL") {
8354                    Some("SMALLINT")
8355                } else {
8356                    None
8357                }
8358            } else {
8359                None
8360            }
8361        } else {
8362            None
8363        };
8364
8365        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
8366        {
8367            self.write_space();
8368            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
8369            // since ClickHouse uses explicit Nullable() in its type system.
8370            let saved_nullable_depth = self.clickhouse_nullable_depth;
8371            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
8372                self.clickhouse_nullable_depth = -1;
8373            }
8374            if let Some(int_type) = serial_expansion {
8375                // SERIAL -> INT (+ constraints added below)
8376                self.write_keyword(int_type);
8377            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
8378                // For DuckDB: convert unsigned integer types to their unsigned equivalents
8379                let unsigned_type = match &col.data_type {
8380                    DataType::Int { .. } => Some("UINTEGER"),
8381                    DataType::BigInt { .. } => Some("UBIGINT"),
8382                    DataType::SmallInt { .. } => Some("USMALLINT"),
8383                    DataType::TinyInt { .. } => Some("UTINYINT"),
8384                    _ => None,
8385                };
8386                if let Some(utype) = unsigned_type {
8387                    self.write_keyword(utype);
8388                } else {
8389                    self.generate_data_type(&col.data_type)?;
8390                }
8391            } else {
8392                self.generate_data_type(&col.data_type)?;
8393            }
8394            self.clickhouse_nullable_depth = saved_nullable_depth;
8395        }
8396
8397        // MySQL type modifiers (must come right after data type)
8398        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
8399        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
8400            self.write_space();
8401            self.write_keyword("UNSIGNED");
8402        }
8403        if col.zerofill {
8404            self.write_space();
8405            self.write_keyword("ZEROFILL");
8406        }
8407
8408        // Teradata column attributes (must come right after data type, in specific order)
8409        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
8410
8411        if let Some(ref charset) = col.character_set {
8412            self.write_space();
8413            self.write_keyword("CHARACTER SET");
8414            self.write_space();
8415            self.write(charset);
8416        }
8417
8418        if col.uppercase {
8419            self.write_space();
8420            self.write_keyword("UPPERCASE");
8421        }
8422
8423        if let Some(casespecific) = col.casespecific {
8424            self.write_space();
8425            if casespecific {
8426                self.write_keyword("CASESPECIFIC");
8427            } else {
8428                self.write_keyword("NOT CASESPECIFIC");
8429            }
8430        }
8431
8432        if let Some(ref format) = col.format {
8433            self.write_space();
8434            self.write_keyword("FORMAT");
8435            self.write(" '");
8436            self.write(format);
8437            self.write("'");
8438        }
8439
8440        if let Some(ref title) = col.title {
8441            self.write_space();
8442            self.write_keyword("TITLE");
8443            self.write(" '");
8444            self.write(title);
8445            self.write("'");
8446        }
8447
8448        if let Some(length) = col.inline_length {
8449            self.write_space();
8450            self.write_keyword("INLINE LENGTH");
8451            self.write(" ");
8452            self.write(&length.to_string());
8453        }
8454
8455        if let Some(ref compress) = col.compress {
8456            self.write_space();
8457            self.write_keyword("COMPRESS");
8458            if !compress.is_empty() {
8459                // Single string literal: output without parentheses (Teradata syntax)
8460                if compress.len() == 1 {
8461                    if let Expression::Literal(lit) = &compress[0] {
8462                        if let Literal::String(_) = lit.as_ref() {
8463                            self.write_space();
8464                            self.generate_expression(&compress[0])?;
8465                        }
8466                    } else {
8467                        self.write(" (");
8468                        self.generate_expression(&compress[0])?;
8469                        self.write(")");
8470                    }
8471                } else {
8472                    self.write(" (");
8473                    for (i, val) in compress.iter().enumerate() {
8474                        if i > 0 {
8475                            self.write(", ");
8476                        }
8477                        self.generate_expression(val)?;
8478                    }
8479                    self.write(")");
8480                }
8481            }
8482        }
8483
8484        // Column constraints - output in original order if constraint_order is populated
8485        // Otherwise fall back to legacy fixed order for backward compatibility
8486        if !col.constraint_order.is_empty() {
8487            // Use constraint_order for original ordering
8488            // Track indices for constraints stored in the constraints Vec
8489            let mut references_idx = 0;
8490            let mut check_idx = 0;
8491            let mut generated_idx = 0;
8492            let mut collate_idx = 0;
8493            let mut comment_idx = 0;
8494            // The preprocessing in dialects/mod.rs now handles the correct ordering of
8495            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
8496            let defer_not_null_after_identity = false;
8497            let mut pending_not_null_after_identity = false;
8498
8499            for constraint_type in &col.constraint_order {
8500                match constraint_type {
8501                    ConstraintType::PrimaryKey => {
8502                        // Materialize doesn't support PRIMARY KEY column constraints
8503                        if col.primary_key
8504                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
8505                        {
8506                            if let Some(ref cname) = col.primary_key_constraint_name {
8507                                self.write_space();
8508                                self.write_keyword("CONSTRAINT");
8509                                self.write_space();
8510                                self.write(cname);
8511                            }
8512                            self.write_space();
8513                            self.write_keyword("PRIMARY KEY");
8514                            if let Some(ref order) = col.primary_key_order {
8515                                self.write_space();
8516                                match order {
8517                                    SortOrder::Asc => self.write_keyword("ASC"),
8518                                    SortOrder::Desc => self.write_keyword("DESC"),
8519                                }
8520                            }
8521                        }
8522                    }
8523                    ConstraintType::Unique => {
8524                        if col.unique {
8525                            if let Some(ref cname) = col.unique_constraint_name {
8526                                self.write_space();
8527                                self.write_keyword("CONSTRAINT");
8528                                self.write_space();
8529                                self.write(cname);
8530                            }
8531                            self.write_space();
8532                            self.write_keyword("UNIQUE");
8533                            // PostgreSQL 15+: NULLS NOT DISTINCT
8534                            if col.unique_nulls_not_distinct {
8535                                self.write(" NULLS NOT DISTINCT");
8536                            }
8537                        }
8538                    }
8539                    ConstraintType::NotNull => {
8540                        if col.nullable == Some(false) {
8541                            if defer_not_null_after_identity {
8542                                pending_not_null_after_identity = true;
8543                                continue;
8544                            }
8545                            if let Some(ref cname) = col.not_null_constraint_name {
8546                                self.write_space();
8547                                self.write_keyword("CONSTRAINT");
8548                                self.write_space();
8549                                self.write(cname);
8550                            }
8551                            self.write_space();
8552                            self.write_keyword("NOT NULL");
8553                        }
8554                    }
8555                    ConstraintType::Null => {
8556                        if col.nullable == Some(true) {
8557                            self.write_space();
8558                            self.write_keyword("NULL");
8559                        }
8560                    }
8561                    ConstraintType::Default => {
8562                        if let Some(ref default) = col.default {
8563                            self.write_space();
8564                            self.write_keyword("DEFAULT");
8565                            self.write_space();
8566                            self.generate_expression(default)?;
8567                        }
8568                    }
8569                    ConstraintType::AutoIncrement => {
8570                        if col.auto_increment {
8571                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
8572                            if matches!(
8573                                self.config.dialect,
8574                                Some(crate::dialects::DialectType::DuckDB)
8575                            ) {
8576                                // Skip - DuckDB uses sequences or rowid instead
8577                            } else if matches!(
8578                                self.config.dialect,
8579                                Some(crate::dialects::DialectType::Materialize)
8580                            ) {
8581                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
8582                                if !matches!(col.nullable, Some(false)) {
8583                                    self.write_space();
8584                                    self.write_keyword("NOT NULL");
8585                                }
8586                            } else if matches!(
8587                                self.config.dialect,
8588                                Some(crate::dialects::DialectType::PostgreSQL)
8589                            ) {
8590                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
8591                                self.write_space();
8592                                self.generate_auto_increment_keyword(col)?;
8593                            } else {
8594                                self.write_space();
8595                                self.generate_auto_increment_keyword(col)?;
8596                                if pending_not_null_after_identity {
8597                                    self.write_space();
8598                                    self.write_keyword("NOT NULL");
8599                                    pending_not_null_after_identity = false;
8600                                }
8601                            }
8602                        } // close else for DuckDB skip
8603                    }
8604                    ConstraintType::References => {
8605                        // Find next References constraint
8606                        while references_idx < col.constraints.len() {
8607                            if let ColumnConstraint::References(fk_ref) =
8608                                &col.constraints[references_idx]
8609                            {
8610                                // CONSTRAINT name if present
8611                                if let Some(ref name) = fk_ref.constraint_name {
8612                                    self.write_space();
8613                                    self.write_keyword("CONSTRAINT");
8614                                    self.write_space();
8615                                    self.write(name);
8616                                }
8617                                self.write_space();
8618                                if fk_ref.has_foreign_key_keywords {
8619                                    self.write_keyword("FOREIGN KEY");
8620                                    self.write_space();
8621                                }
8622                                self.write_keyword("REFERENCES");
8623                                self.write_space();
8624                                self.generate_table(&fk_ref.table)?;
8625                                if !fk_ref.columns.is_empty() {
8626                                    self.write(" (");
8627                                    for (i, c) in fk_ref.columns.iter().enumerate() {
8628                                        if i > 0 {
8629                                            self.write(", ");
8630                                        }
8631                                        self.generate_identifier(c)?;
8632                                    }
8633                                    self.write(")");
8634                                }
8635                                self.generate_referential_actions(fk_ref)?;
8636                                references_idx += 1;
8637                                break;
8638                            }
8639                            references_idx += 1;
8640                        }
8641                    }
8642                    ConstraintType::Check => {
8643                        // Find next Check constraint
8644                        while check_idx < col.constraints.len() {
8645                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
8646                                // Output CONSTRAINT name if present (only for first CHECK)
8647                                if check_idx == 0 {
8648                                    if let Some(ref cname) = col.check_constraint_name {
8649                                        self.write_space();
8650                                        self.write_keyword("CONSTRAINT");
8651                                        self.write_space();
8652                                        self.write(cname);
8653                                    }
8654                                }
8655                                self.write_space();
8656                                self.write_keyword("CHECK");
8657                                self.write(" (");
8658                                self.generate_expression(expr)?;
8659                                self.write(")");
8660                                check_idx += 1;
8661                                break;
8662                            }
8663                            check_idx += 1;
8664                        }
8665                    }
8666                    ConstraintType::GeneratedAsIdentity => {
8667                        // Find next GeneratedAsIdentity constraint
8668                        while generated_idx < col.constraints.len() {
8669                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
8670                                &col.constraints[generated_idx]
8671                            {
8672                                self.write_space();
8673                                // Redshift uses IDENTITY(start, increment) syntax
8674                                if matches!(
8675                                    self.config.dialect,
8676                                    Some(crate::dialects::DialectType::Redshift)
8677                                ) {
8678                                    self.write_keyword("IDENTITY");
8679                                    self.write("(");
8680                                    if let Some(ref start) = gen.start {
8681                                        self.generate_expression(start)?;
8682                                    } else {
8683                                        self.write("0");
8684                                    }
8685                                    self.write(", ");
8686                                    if let Some(ref incr) = gen.increment {
8687                                        self.generate_expression(incr)?;
8688                                    } else {
8689                                        self.write("1");
8690                                    }
8691                                    self.write(")");
8692                                } else {
8693                                    self.write_keyword("GENERATED");
8694                                    if gen.always {
8695                                        self.write_space();
8696                                        self.write_keyword("ALWAYS");
8697                                    } else {
8698                                        self.write_space();
8699                                        self.write_keyword("BY DEFAULT");
8700                                        if gen.on_null {
8701                                            self.write_space();
8702                                            self.write_keyword("ON NULL");
8703                                        }
8704                                    }
8705                                    self.write_space();
8706                                    self.write_keyword("AS IDENTITY");
8707
8708                                    let has_options = gen.start.is_some()
8709                                        || gen.increment.is_some()
8710                                        || gen.minvalue.is_some()
8711                                        || gen.maxvalue.is_some()
8712                                        || gen.cycle.is_some();
8713                                    if has_options {
8714                                        self.write(" (");
8715                                        let mut first = true;
8716                                        if let Some(ref start) = gen.start {
8717                                            if !first {
8718                                                self.write(" ");
8719                                            }
8720                                            first = false;
8721                                            self.write_keyword("START WITH");
8722                                            self.write_space();
8723                                            self.generate_expression(start)?;
8724                                        }
8725                                        if let Some(ref incr) = gen.increment {
8726                                            if !first {
8727                                                self.write(" ");
8728                                            }
8729                                            first = false;
8730                                            self.write_keyword("INCREMENT BY");
8731                                            self.write_space();
8732                                            self.generate_expression(incr)?;
8733                                        }
8734                                        if let Some(ref minv) = gen.minvalue {
8735                                            if !first {
8736                                                self.write(" ");
8737                                            }
8738                                            first = false;
8739                                            self.write_keyword("MINVALUE");
8740                                            self.write_space();
8741                                            self.generate_expression(minv)?;
8742                                        }
8743                                        if let Some(ref maxv) = gen.maxvalue {
8744                                            if !first {
8745                                                self.write(" ");
8746                                            }
8747                                            first = false;
8748                                            self.write_keyword("MAXVALUE");
8749                                            self.write_space();
8750                                            self.generate_expression(maxv)?;
8751                                        }
8752                                        if let Some(cycle) = gen.cycle {
8753                                            if !first {
8754                                                self.write(" ");
8755                                            }
8756                                            if cycle {
8757                                                self.write_keyword("CYCLE");
8758                                            } else {
8759                                                self.write_keyword("NO CYCLE");
8760                                            }
8761                                        }
8762                                        self.write(")");
8763                                    }
8764                                }
8765                                generated_idx += 1;
8766                                break;
8767                            }
8768                            generated_idx += 1;
8769                        }
8770                    }
8771                    ConstraintType::Collate => {
8772                        // Find next Collate constraint
8773                        while collate_idx < col.constraints.len() {
8774                            if let ColumnConstraint::Collate(collation) =
8775                                &col.constraints[collate_idx]
8776                            {
8777                                self.write_space();
8778                                self.write_keyword("COLLATE");
8779                                self.write_space();
8780                                self.generate_identifier(collation)?;
8781                                collate_idx += 1;
8782                                break;
8783                            }
8784                            collate_idx += 1;
8785                        }
8786                    }
8787                    ConstraintType::Comment => {
8788                        // Find next Comment constraint
8789                        while comment_idx < col.constraints.len() {
8790                            if let ColumnConstraint::Comment(comment) =
8791                                &col.constraints[comment_idx]
8792                            {
8793                                self.write_space();
8794                                self.write_keyword("COMMENT");
8795                                self.write_space();
8796                                self.generate_string_literal(comment)?;
8797                                comment_idx += 1;
8798                                break;
8799                            }
8800                            comment_idx += 1;
8801                        }
8802                    }
8803                    ConstraintType::Tags => {
8804                        // Find next Tags constraint (Snowflake)
8805                        for constraint in &col.constraints {
8806                            if let ColumnConstraint::Tags(tags) = constraint {
8807                                self.write_space();
8808                                self.write_keyword("TAG");
8809                                self.write(" (");
8810                                for (i, expr) in tags.expressions.iter().enumerate() {
8811                                    if i > 0 {
8812                                        self.write(", ");
8813                                    }
8814                                    self.generate_expression(expr)?;
8815                                }
8816                                self.write(")");
8817                                break;
8818                            }
8819                        }
8820                    }
8821                    ConstraintType::ComputedColumn => {
8822                        // Find next ComputedColumn constraint
8823                        for constraint in &col.constraints {
8824                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
8825                                self.write_space();
8826                                self.generate_computed_column_inline(cc)?;
8827                                break;
8828                            }
8829                        }
8830                    }
8831                    ConstraintType::GeneratedAsRow => {
8832                        // Find next GeneratedAsRow constraint
8833                        for constraint in &col.constraints {
8834                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
8835                                self.write_space();
8836                                self.generate_generated_as_row_inline(gar)?;
8837                                break;
8838                            }
8839                        }
8840                    }
8841                    ConstraintType::OnUpdate => {
8842                        if let Some(ref expr) = col.on_update {
8843                            self.write_space();
8844                            self.write_keyword("ON UPDATE");
8845                            self.write_space();
8846                            self.generate_expression(expr)?;
8847                        }
8848                    }
8849                    ConstraintType::Encode => {
8850                        if let Some(ref encoding) = col.encoding {
8851                            self.write_space();
8852                            self.write_keyword("ENCODE");
8853                            self.write_space();
8854                            self.write(encoding);
8855                        }
8856                    }
8857                    ConstraintType::Path => {
8858                        // Find next Path constraint
8859                        for constraint in &col.constraints {
8860                            if let ColumnConstraint::Path(path_expr) = constraint {
8861                                self.write_space();
8862                                self.write_keyword("PATH");
8863                                self.write_space();
8864                                self.generate_expression(path_expr)?;
8865                                break;
8866                            }
8867                        }
8868                    }
8869                }
8870            }
8871            if pending_not_null_after_identity {
8872                self.write_space();
8873                self.write_keyword("NOT NULL");
8874            }
8875        } else {
8876            // Legacy fixed order for backward compatibility
8877            if col.primary_key {
8878                self.write_space();
8879                self.write_keyword("PRIMARY KEY");
8880                if let Some(ref order) = col.primary_key_order {
8881                    self.write_space();
8882                    match order {
8883                        SortOrder::Asc => self.write_keyword("ASC"),
8884                        SortOrder::Desc => self.write_keyword("DESC"),
8885                    }
8886                }
8887            }
8888
8889            if col.unique {
8890                self.write_space();
8891                self.write_keyword("UNIQUE");
8892                // PostgreSQL 15+: NULLS NOT DISTINCT
8893                if col.unique_nulls_not_distinct {
8894                    self.write(" NULLS NOT DISTINCT");
8895                }
8896            }
8897
8898            match col.nullable {
8899                Some(false) => {
8900                    self.write_space();
8901                    self.write_keyword("NOT NULL");
8902                }
8903                Some(true) => {
8904                    self.write_space();
8905                    self.write_keyword("NULL");
8906                }
8907                None => {}
8908            }
8909
8910            if let Some(ref default) = col.default {
8911                self.write_space();
8912                self.write_keyword("DEFAULT");
8913                self.write_space();
8914                self.generate_expression(default)?;
8915            }
8916
8917            if col.auto_increment {
8918                self.write_space();
8919                self.generate_auto_increment_keyword(col)?;
8920            }
8921
8922            // Column-level constraints from Vec
8923            for constraint in &col.constraints {
8924                match constraint {
8925                    ColumnConstraint::References(fk_ref) => {
8926                        self.write_space();
8927                        if fk_ref.has_foreign_key_keywords {
8928                            self.write_keyword("FOREIGN KEY");
8929                            self.write_space();
8930                        }
8931                        self.write_keyword("REFERENCES");
8932                        self.write_space();
8933                        self.generate_table(&fk_ref.table)?;
8934                        if !fk_ref.columns.is_empty() {
8935                            self.write(" (");
8936                            for (i, c) in fk_ref.columns.iter().enumerate() {
8937                                if i > 0 {
8938                                    self.write(", ");
8939                                }
8940                                self.generate_identifier(c)?;
8941                            }
8942                            self.write(")");
8943                        }
8944                        self.generate_referential_actions(fk_ref)?;
8945                    }
8946                    ColumnConstraint::Check(expr) => {
8947                        self.write_space();
8948                        self.write_keyword("CHECK");
8949                        self.write(" (");
8950                        self.generate_expression(expr)?;
8951                        self.write(")");
8952                    }
8953                    ColumnConstraint::GeneratedAsIdentity(gen) => {
8954                        self.write_space();
8955                        // Redshift uses IDENTITY(start, increment) syntax
8956                        if matches!(
8957                            self.config.dialect,
8958                            Some(crate::dialects::DialectType::Redshift)
8959                        ) {
8960                            self.write_keyword("IDENTITY");
8961                            self.write("(");
8962                            if let Some(ref start) = gen.start {
8963                                self.generate_expression(start)?;
8964                            } else {
8965                                self.write("0");
8966                            }
8967                            self.write(", ");
8968                            if let Some(ref incr) = gen.increment {
8969                                self.generate_expression(incr)?;
8970                            } else {
8971                                self.write("1");
8972                            }
8973                            self.write(")");
8974                        } else {
8975                            self.write_keyword("GENERATED");
8976                            if gen.always {
8977                                self.write_space();
8978                                self.write_keyword("ALWAYS");
8979                            } else {
8980                                self.write_space();
8981                                self.write_keyword("BY DEFAULT");
8982                                if gen.on_null {
8983                                    self.write_space();
8984                                    self.write_keyword("ON NULL");
8985                                }
8986                            }
8987                            self.write_space();
8988                            self.write_keyword("AS IDENTITY");
8989
8990                            let has_options = gen.start.is_some()
8991                                || gen.increment.is_some()
8992                                || gen.minvalue.is_some()
8993                                || gen.maxvalue.is_some()
8994                                || gen.cycle.is_some();
8995                            if has_options {
8996                                self.write(" (");
8997                                let mut first = true;
8998                                if let Some(ref start) = gen.start {
8999                                    if !first {
9000                                        self.write(" ");
9001                                    }
9002                                    first = false;
9003                                    self.write_keyword("START WITH");
9004                                    self.write_space();
9005                                    self.generate_expression(start)?;
9006                                }
9007                                if let Some(ref incr) = gen.increment {
9008                                    if !first {
9009                                        self.write(" ");
9010                                    }
9011                                    first = false;
9012                                    self.write_keyword("INCREMENT BY");
9013                                    self.write_space();
9014                                    self.generate_expression(incr)?;
9015                                }
9016                                if let Some(ref minv) = gen.minvalue {
9017                                    if !first {
9018                                        self.write(" ");
9019                                    }
9020                                    first = false;
9021                                    self.write_keyword("MINVALUE");
9022                                    self.write_space();
9023                                    self.generate_expression(minv)?;
9024                                }
9025                                if let Some(ref maxv) = gen.maxvalue {
9026                                    if !first {
9027                                        self.write(" ");
9028                                    }
9029                                    first = false;
9030                                    self.write_keyword("MAXVALUE");
9031                                    self.write_space();
9032                                    self.generate_expression(maxv)?;
9033                                }
9034                                if let Some(cycle) = gen.cycle {
9035                                    if !first {
9036                                        self.write(" ");
9037                                    }
9038                                    if cycle {
9039                                        self.write_keyword("CYCLE");
9040                                    } else {
9041                                        self.write_keyword("NO CYCLE");
9042                                    }
9043                                }
9044                                self.write(")");
9045                            }
9046                        }
9047                    }
9048                    ColumnConstraint::Collate(collation) => {
9049                        self.write_space();
9050                        self.write_keyword("COLLATE");
9051                        self.write_space();
9052                        self.generate_identifier(collation)?;
9053                    }
9054                    ColumnConstraint::Comment(comment) => {
9055                        self.write_space();
9056                        self.write_keyword("COMMENT");
9057                        self.write_space();
9058                        self.generate_string_literal(comment)?;
9059                    }
9060                    ColumnConstraint::Path(path_expr) => {
9061                        self.write_space();
9062                        self.write_keyword("PATH");
9063                        self.write_space();
9064                        self.generate_expression(path_expr)?;
9065                    }
9066                    _ => {} // Other constraints handled above
9067                }
9068            }
9069
9070            // Redshift: ENCODE encoding_type (legacy path)
9071            if let Some(ref encoding) = col.encoding {
9072                self.write_space();
9073                self.write_keyword("ENCODE");
9074                self.write_space();
9075                self.write(encoding);
9076            }
9077        }
9078
9079        // ClickHouse: CODEC(...)
9080        if let Some(ref codec) = col.codec {
9081            self.write_space();
9082            self.write_keyword("CODEC");
9083            self.write("(");
9084            self.write(codec);
9085            self.write(")");
9086        }
9087
9088        // ClickHouse: EPHEMERAL [expr]
9089        if let Some(ref ephemeral) = col.ephemeral {
9090            self.write_space();
9091            self.write_keyword("EPHEMERAL");
9092            if let Some(ref expr) = ephemeral {
9093                self.write_space();
9094                self.generate_expression(expr)?;
9095            }
9096        }
9097
9098        // ClickHouse: MATERIALIZED expr
9099        if let Some(ref mat_expr) = col.materialized_expr {
9100            self.write_space();
9101            self.write_keyword("MATERIALIZED");
9102            self.write_space();
9103            self.generate_expression(mat_expr)?;
9104        }
9105
9106        // ClickHouse: ALIAS expr
9107        if let Some(ref alias_expr) = col.alias_expr {
9108            self.write_space();
9109            self.write_keyword("ALIAS");
9110            self.write_space();
9111            self.generate_expression(alias_expr)?;
9112        }
9113
9114        // ClickHouse: TTL expr
9115        if let Some(ref ttl_expr) = col.ttl_expr {
9116            self.write_space();
9117            self.write_keyword("TTL");
9118            self.write_space();
9119            self.generate_expression(ttl_expr)?;
9120        }
9121
9122        // TSQL: NOT FOR REPLICATION
9123        if col.not_for_replication
9124            && matches!(
9125                self.config.dialect,
9126                Some(crate::dialects::DialectType::TSQL)
9127                    | Some(crate::dialects::DialectType::Fabric)
9128            )
9129        {
9130            self.write_space();
9131            self.write_keyword("NOT FOR REPLICATION");
9132        }
9133
9134        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
9135        if !col.options.is_empty() {
9136            self.write_space();
9137            self.generate_options_clause(&col.options)?;
9138        }
9139
9140        // SQLite: Inline PRIMARY KEY from table constraint
9141        // This comes at the end, after all existing column constraints
9142        if !col.primary_key
9143            && self
9144                .sqlite_inline_pk_columns
9145                .contains(&col.name.name.to_ascii_lowercase())
9146        {
9147            self.write_space();
9148            self.write_keyword("PRIMARY KEY");
9149        }
9150
9151        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
9152        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
9153        if serial_expansion.is_some() {
9154            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
9155                self.write_space();
9156                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
9157            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
9158                self.write_space();
9159                self.write_keyword("NOT NULL");
9160            }
9161        }
9162
9163        Ok(())
9164    }
9165
9166    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
9167        match constraint {
9168            TableConstraint::PrimaryKey {
9169                name,
9170                columns,
9171                include_columns,
9172                modifiers,
9173                has_constraint_keyword,
9174            } => {
9175                if let Some(ref n) = name {
9176                    if *has_constraint_keyword {
9177                        self.write_keyword("CONSTRAINT");
9178                        self.write_space();
9179                        self.generate_identifier(n)?;
9180                        self.write_space();
9181                    }
9182                }
9183                self.write_keyword("PRIMARY KEY");
9184                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9185                if let Some(ref clustered) = modifiers.clustered {
9186                    self.write_space();
9187                    self.write_keyword(clustered);
9188                }
9189                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
9190                if let Some(ref n) = name {
9191                    if !*has_constraint_keyword {
9192                        self.write_space();
9193                        self.generate_identifier(n)?;
9194                    }
9195                }
9196                self.write(" (");
9197                for (i, col) in columns.iter().enumerate() {
9198                    if i > 0 {
9199                        self.write(", ");
9200                    }
9201                    self.generate_identifier(col)?;
9202                }
9203                self.write(")");
9204                if !include_columns.is_empty() {
9205                    self.write_space();
9206                    self.write_keyword("INCLUDE");
9207                    self.write(" (");
9208                    for (i, col) in include_columns.iter().enumerate() {
9209                        if i > 0 {
9210                            self.write(", ");
9211                        }
9212                        self.generate_identifier(col)?;
9213                    }
9214                    self.write(")");
9215                }
9216                self.generate_constraint_modifiers(modifiers);
9217            }
9218            TableConstraint::Unique {
9219                name,
9220                columns,
9221                columns_parenthesized,
9222                modifiers,
9223                has_constraint_keyword,
9224                nulls_not_distinct,
9225            } => {
9226                if let Some(ref n) = name {
9227                    if *has_constraint_keyword {
9228                        self.write_keyword("CONSTRAINT");
9229                        self.write_space();
9230                        self.generate_identifier(n)?;
9231                        self.write_space();
9232                    }
9233                }
9234                self.write_keyword("UNIQUE");
9235                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9236                if let Some(ref clustered) = modifiers.clustered {
9237                    self.write_space();
9238                    self.write_keyword(clustered);
9239                }
9240                // PostgreSQL 15+: NULLS NOT DISTINCT
9241                if *nulls_not_distinct {
9242                    self.write(" NULLS NOT DISTINCT");
9243                }
9244                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
9245                if let Some(ref n) = name {
9246                    if !*has_constraint_keyword {
9247                        self.write_space();
9248                        self.generate_identifier(n)?;
9249                    }
9250                }
9251                if *columns_parenthesized {
9252                    self.write(" (");
9253                    for (i, col) in columns.iter().enumerate() {
9254                        if i > 0 {
9255                            self.write(", ");
9256                        }
9257                        self.generate_identifier(col)?;
9258                    }
9259                    self.write(")");
9260                } else {
9261                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
9262                    for col in columns.iter() {
9263                        self.write_space();
9264                        self.generate_identifier(col)?;
9265                    }
9266                }
9267                self.generate_constraint_modifiers(modifiers);
9268            }
9269            TableConstraint::ForeignKey {
9270                name,
9271                columns,
9272                references,
9273                on_delete,
9274                on_update,
9275                modifiers,
9276            } => {
9277                if let Some(ref n) = name {
9278                    self.write_keyword("CONSTRAINT");
9279                    self.write_space();
9280                    self.generate_identifier(n)?;
9281                    self.write_space();
9282                }
9283                self.write_keyword("FOREIGN KEY");
9284                self.write(" (");
9285                for (i, col) in columns.iter().enumerate() {
9286                    if i > 0 {
9287                        self.write(", ");
9288                    }
9289                    self.generate_identifier(col)?;
9290                }
9291                self.write(")");
9292                if let Some(ref refs) = references {
9293                    self.write(" ");
9294                    self.write_keyword("REFERENCES");
9295                    self.write_space();
9296                    self.generate_table(&refs.table)?;
9297                    if !refs.columns.is_empty() {
9298                        if self.config.pretty {
9299                            self.write(" (");
9300                            self.write_newline();
9301                            self.indent_level += 1;
9302                            for (i, col) in refs.columns.iter().enumerate() {
9303                                if i > 0 {
9304                                    self.write(",");
9305                                    self.write_newline();
9306                                }
9307                                self.write_indent();
9308                                self.generate_identifier(col)?;
9309                            }
9310                            self.indent_level -= 1;
9311                            self.write_newline();
9312                            self.write_indent();
9313                            self.write(")");
9314                        } else {
9315                            self.write(" (");
9316                            for (i, col) in refs.columns.iter().enumerate() {
9317                                if i > 0 {
9318                                    self.write(", ");
9319                                }
9320                                self.generate_identifier(col)?;
9321                            }
9322                            self.write(")");
9323                        }
9324                    }
9325                    self.generate_referential_actions(refs)?;
9326                } else {
9327                    // No REFERENCES - output ON DELETE/ON UPDATE directly
9328                    if let Some(ref action) = on_delete {
9329                        self.write_space();
9330                        self.write_keyword("ON DELETE");
9331                        self.write_space();
9332                        self.generate_referential_action(action);
9333                    }
9334                    if let Some(ref action) = on_update {
9335                        self.write_space();
9336                        self.write_keyword("ON UPDATE");
9337                        self.write_space();
9338                        self.generate_referential_action(action);
9339                    }
9340                }
9341                self.generate_constraint_modifiers(modifiers);
9342            }
9343            TableConstraint::Check {
9344                name,
9345                expression,
9346                modifiers,
9347            } => {
9348                if let Some(ref n) = name {
9349                    self.write_keyword("CONSTRAINT");
9350                    self.write_space();
9351                    self.generate_identifier(n)?;
9352                    self.write_space();
9353                }
9354                self.write_keyword("CHECK");
9355                self.write(" (");
9356                self.generate_expression(expression)?;
9357                self.write(")");
9358                self.generate_constraint_modifiers(modifiers);
9359            }
9360            TableConstraint::Assume { name, expression } => {
9361                if let Some(ref n) = name {
9362                    self.write_keyword("CONSTRAINT");
9363                    self.write_space();
9364                    self.generate_identifier(n)?;
9365                    self.write_space();
9366                }
9367                self.write_keyword("ASSUME");
9368                self.write(" (");
9369                self.generate_expression(expression)?;
9370                self.write(")");
9371            }
9372            TableConstraint::Default {
9373                name,
9374                expression,
9375                column,
9376            } => {
9377                if let Some(ref n) = name {
9378                    self.write_keyword("CONSTRAINT");
9379                    self.write_space();
9380                    self.generate_identifier(n)?;
9381                    self.write_space();
9382                }
9383                self.write_keyword("DEFAULT");
9384                self.write_space();
9385                self.generate_expression(expression)?;
9386                self.write_space();
9387                self.write_keyword("FOR");
9388                self.write_space();
9389                self.generate_identifier(column)?;
9390            }
9391            TableConstraint::Index {
9392                name,
9393                columns,
9394                kind,
9395                modifiers,
9396                use_key_keyword,
9397                expression,
9398                index_type,
9399                granularity,
9400            } => {
9401                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
9402                if expression.is_some() {
9403                    self.write_keyword("INDEX");
9404                    if let Some(ref n) = name {
9405                        self.write_space();
9406                        self.generate_identifier(n)?;
9407                    }
9408                    if let Some(ref expr) = expression {
9409                        self.write_space();
9410                        self.generate_expression(expr)?;
9411                    }
9412                    if let Some(ref idx_type) = index_type {
9413                        self.write_space();
9414                        self.write_keyword("TYPE");
9415                        self.write_space();
9416                        self.generate_expression(idx_type)?;
9417                    }
9418                    if let Some(ref gran) = granularity {
9419                        self.write_space();
9420                        self.write_keyword("GRANULARITY");
9421                        self.write_space();
9422                        self.generate_expression(gran)?;
9423                    }
9424                } else {
9425                    // Standard INDEX syntax
9426                    // Determine the index keyword to use
9427                    // MySQL normalizes KEY to INDEX
9428                    use crate::dialects::DialectType;
9429                    let index_keyword = if *use_key_keyword
9430                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
9431                    {
9432                        "KEY"
9433                    } else {
9434                        "INDEX"
9435                    };
9436
9437                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
9438                    if let Some(ref k) = kind {
9439                        self.write_keyword(k);
9440                        // For UNIQUE, don't add INDEX/KEY keyword
9441                        if k != "UNIQUE" {
9442                            self.write_space();
9443                            self.write_keyword(index_keyword);
9444                        }
9445                    } else {
9446                        self.write_keyword(index_keyword);
9447                    }
9448
9449                    // Output USING before name if using_before_columns is true and there's no name
9450                    if modifiers.using_before_columns && name.is_none() {
9451                        if let Some(ref using) = modifiers.using {
9452                            self.write_space();
9453                            self.write_keyword("USING");
9454                            self.write_space();
9455                            self.write_keyword(using);
9456                        }
9457                    }
9458
9459                    // Output index name if present
9460                    if let Some(ref n) = name {
9461                        self.write_space();
9462                        self.generate_identifier(n)?;
9463                    }
9464
9465                    // Output USING after name but before columns if using_before_columns and there's a name
9466                    if modifiers.using_before_columns && name.is_some() {
9467                        if let Some(ref using) = modifiers.using {
9468                            self.write_space();
9469                            self.write_keyword("USING");
9470                            self.write_space();
9471                            self.write_keyword(using);
9472                        }
9473                    }
9474
9475                    // Output columns
9476                    self.write(" (");
9477                    for (i, col) in columns.iter().enumerate() {
9478                        if i > 0 {
9479                            self.write(", ");
9480                        }
9481                        self.generate_identifier(col)?;
9482                    }
9483                    self.write(")");
9484
9485                    // Output USING after columns if not using_before_columns
9486                    if !modifiers.using_before_columns {
9487                        if let Some(ref using) = modifiers.using {
9488                            self.write_space();
9489                            self.write_keyword("USING");
9490                            self.write_space();
9491                            self.write_keyword(using);
9492                        }
9493                    }
9494
9495                    // Output other constraint modifiers (but skip USING since we already handled it)
9496                    self.generate_constraint_modifiers_without_using(modifiers);
9497                }
9498            }
9499            TableConstraint::Projection { name, expression } => {
9500                // ClickHouse: PROJECTION name (SELECT ...)
9501                self.write_keyword("PROJECTION");
9502                self.write_space();
9503                self.generate_identifier(name)?;
9504                self.write(" (");
9505                self.generate_expression(expression)?;
9506                self.write(")");
9507            }
9508            TableConstraint::Like { source, options } => {
9509                self.write_keyword("LIKE");
9510                self.write_space();
9511                self.generate_table(source)?;
9512                for (action, prop) in options {
9513                    self.write_space();
9514                    match action {
9515                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
9516                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
9517                    }
9518                    self.write_space();
9519                    self.write_keyword(prop);
9520                }
9521            }
9522            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
9523                self.write_keyword("PERIOD FOR SYSTEM_TIME");
9524                self.write(" (");
9525                self.generate_identifier(start_col)?;
9526                self.write(", ");
9527                self.generate_identifier(end_col)?;
9528                self.write(")");
9529            }
9530            TableConstraint::Exclude {
9531                name,
9532                using,
9533                elements,
9534                include_columns,
9535                where_clause,
9536                with_params,
9537                using_index_tablespace,
9538                modifiers: _,
9539            } => {
9540                if let Some(ref n) = name {
9541                    self.write_keyword("CONSTRAINT");
9542                    self.write_space();
9543                    self.generate_identifier(n)?;
9544                    self.write_space();
9545                }
9546                self.write_keyword("EXCLUDE");
9547                if let Some(ref method) = using {
9548                    self.write_space();
9549                    self.write_keyword("USING");
9550                    self.write_space();
9551                    self.write(method);
9552                    self.write("(");
9553                } else {
9554                    self.write(" (");
9555                }
9556                for (i, elem) in elements.iter().enumerate() {
9557                    if i > 0 {
9558                        self.write(", ");
9559                    }
9560                    self.write(&elem.expression);
9561                    self.write_space();
9562                    self.write_keyword("WITH");
9563                    self.write_space();
9564                    self.write(&elem.operator);
9565                }
9566                self.write(")");
9567                if !include_columns.is_empty() {
9568                    self.write_space();
9569                    self.write_keyword("INCLUDE");
9570                    self.write(" (");
9571                    for (i, col) in include_columns.iter().enumerate() {
9572                        if i > 0 {
9573                            self.write(", ");
9574                        }
9575                        self.generate_identifier(col)?;
9576                    }
9577                    self.write(")");
9578                }
9579                if !with_params.is_empty() {
9580                    self.write_space();
9581                    self.write_keyword("WITH");
9582                    self.write(" (");
9583                    for (i, (key, val)) in with_params.iter().enumerate() {
9584                        if i > 0 {
9585                            self.write(", ");
9586                        }
9587                        self.write(key);
9588                        self.write("=");
9589                        self.write(val);
9590                    }
9591                    self.write(")");
9592                }
9593                if let Some(ref tablespace) = using_index_tablespace {
9594                    self.write_space();
9595                    self.write_keyword("USING INDEX TABLESPACE");
9596                    self.write_space();
9597                    self.write(tablespace);
9598                }
9599                if let Some(ref where_expr) = where_clause {
9600                    self.write_space();
9601                    self.write_keyword("WHERE");
9602                    self.write(" (");
9603                    self.generate_expression(where_expr)?;
9604                    self.write(")");
9605                }
9606            }
9607            TableConstraint::Tags(tags) => {
9608                self.write_keyword("TAG");
9609                self.write(" (");
9610                for (i, expr) in tags.expressions.iter().enumerate() {
9611                    if i > 0 {
9612                        self.write(", ");
9613                    }
9614                    self.generate_expression(expr)?;
9615                }
9616                self.write(")");
9617            }
9618            TableConstraint::InitiallyDeferred { deferred } => {
9619                self.write_keyword("INITIALLY");
9620                self.write_space();
9621                if *deferred {
9622                    self.write_keyword("DEFERRED");
9623                } else {
9624                    self.write_keyword("IMMEDIATE");
9625                }
9626            }
9627        }
9628        Ok(())
9629    }
9630
9631    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9632        // Output USING BTREE/HASH (MySQL) - comes first
9633        if let Some(using) = &modifiers.using {
9634            self.write_space();
9635            self.write_keyword("USING");
9636            self.write_space();
9637            self.write_keyword(using);
9638        }
9639        // Output ENFORCED/NOT ENFORCED
9640        if let Some(enforced) = modifiers.enforced {
9641            self.write_space();
9642            if enforced {
9643                self.write_keyword("ENFORCED");
9644            } else {
9645                self.write_keyword("NOT ENFORCED");
9646            }
9647        }
9648        // Output DEFERRABLE/NOT DEFERRABLE
9649        if let Some(deferrable) = modifiers.deferrable {
9650            self.write_space();
9651            if deferrable {
9652                self.write_keyword("DEFERRABLE");
9653            } else {
9654                self.write_keyword("NOT DEFERRABLE");
9655            }
9656        }
9657        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9658        if let Some(initially_deferred) = modifiers.initially_deferred {
9659            self.write_space();
9660            if initially_deferred {
9661                self.write_keyword("INITIALLY DEFERRED");
9662            } else {
9663                self.write_keyword("INITIALLY IMMEDIATE");
9664            }
9665        }
9666        // Output NORELY
9667        if modifiers.norely {
9668            self.write_space();
9669            self.write_keyword("NORELY");
9670        }
9671        // Output RELY
9672        if modifiers.rely {
9673            self.write_space();
9674            self.write_keyword("RELY");
9675        }
9676        // Output NOT VALID (PostgreSQL)
9677        if modifiers.not_valid {
9678            self.write_space();
9679            self.write_keyword("NOT VALID");
9680        }
9681        // Output ON CONFLICT (SQLite)
9682        if let Some(on_conflict) = &modifiers.on_conflict {
9683            self.write_space();
9684            self.write_keyword("ON CONFLICT");
9685            self.write_space();
9686            self.write_keyword(on_conflict);
9687        }
9688        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
9689        if !modifiers.with_options.is_empty() {
9690            self.write_space();
9691            self.write_keyword("WITH");
9692            self.write(" (");
9693            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
9694                if i > 0 {
9695                    self.write(", ");
9696                }
9697                self.write(key);
9698                self.write("=");
9699                self.write(value);
9700            }
9701            self.write(")");
9702        }
9703        // Output TSQL ON filegroup
9704        if let Some(ref fg) = modifiers.on_filegroup {
9705            self.write_space();
9706            self.write_keyword("ON");
9707            self.write_space();
9708            let _ = self.generate_identifier(fg);
9709        }
9710    }
9711
9712    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
9713    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
9714        // Output ENFORCED/NOT ENFORCED
9715        if let Some(enforced) = modifiers.enforced {
9716            self.write_space();
9717            if enforced {
9718                self.write_keyword("ENFORCED");
9719            } else {
9720                self.write_keyword("NOT ENFORCED");
9721            }
9722        }
9723        // Output DEFERRABLE/NOT DEFERRABLE
9724        if let Some(deferrable) = modifiers.deferrable {
9725            self.write_space();
9726            if deferrable {
9727                self.write_keyword("DEFERRABLE");
9728            } else {
9729                self.write_keyword("NOT DEFERRABLE");
9730            }
9731        }
9732        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9733        if let Some(initially_deferred) = modifiers.initially_deferred {
9734            self.write_space();
9735            if initially_deferred {
9736                self.write_keyword("INITIALLY DEFERRED");
9737            } else {
9738                self.write_keyword("INITIALLY IMMEDIATE");
9739            }
9740        }
9741        // Output NORELY
9742        if modifiers.norely {
9743            self.write_space();
9744            self.write_keyword("NORELY");
9745        }
9746        // Output RELY
9747        if modifiers.rely {
9748            self.write_space();
9749            self.write_keyword("RELY");
9750        }
9751        // Output NOT VALID (PostgreSQL)
9752        if modifiers.not_valid {
9753            self.write_space();
9754            self.write_keyword("NOT VALID");
9755        }
9756        // Output ON CONFLICT (SQLite)
9757        if let Some(on_conflict) = &modifiers.on_conflict {
9758            self.write_space();
9759            self.write_keyword("ON CONFLICT");
9760            self.write_space();
9761            self.write_keyword(on_conflict);
9762        }
9763        // Output MySQL index-specific modifiers
9764        self.generate_index_specific_modifiers(modifiers);
9765    }
9766
9767    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
9768    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9769        if let Some(ref comment) = modifiers.comment {
9770            self.write_space();
9771            self.write_keyword("COMMENT");
9772            self.write(" '");
9773            self.write(comment);
9774            self.write("'");
9775        }
9776        if let Some(visible) = modifiers.visible {
9777            self.write_space();
9778            if visible {
9779                self.write_keyword("VISIBLE");
9780            } else {
9781                self.write_keyword("INVISIBLE");
9782            }
9783        }
9784        if let Some(ref attr) = modifiers.engine_attribute {
9785            self.write_space();
9786            self.write_keyword("ENGINE_ATTRIBUTE");
9787            self.write(" = '");
9788            self.write(attr);
9789            self.write("'");
9790        }
9791        if let Some(ref parser) = modifiers.with_parser {
9792            self.write_space();
9793            self.write_keyword("WITH PARSER");
9794            self.write_space();
9795            self.write(parser);
9796        }
9797    }
9798
9799    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
9800        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
9801        if !fk_ref.match_after_actions {
9802            if let Some(ref match_type) = fk_ref.match_type {
9803                self.write_space();
9804                self.write_keyword("MATCH");
9805                self.write_space();
9806                match match_type {
9807                    MatchType::Full => self.write_keyword("FULL"),
9808                    MatchType::Partial => self.write_keyword("PARTIAL"),
9809                    MatchType::Simple => self.write_keyword("SIMPLE"),
9810                }
9811            }
9812        }
9813
9814        // Output ON UPDATE and ON DELETE in the original order
9815        if fk_ref.on_update_first {
9816            if let Some(ref action) = fk_ref.on_update {
9817                self.write_space();
9818                self.write_keyword("ON UPDATE");
9819                self.write_space();
9820                self.generate_referential_action(action);
9821            }
9822            if let Some(ref action) = fk_ref.on_delete {
9823                self.write_space();
9824                self.write_keyword("ON DELETE");
9825                self.write_space();
9826                self.generate_referential_action(action);
9827            }
9828        } else {
9829            if let Some(ref action) = fk_ref.on_delete {
9830                self.write_space();
9831                self.write_keyword("ON DELETE");
9832                self.write_space();
9833                self.generate_referential_action(action);
9834            }
9835            if let Some(ref action) = fk_ref.on_update {
9836                self.write_space();
9837                self.write_keyword("ON UPDATE");
9838                self.write_space();
9839                self.generate_referential_action(action);
9840            }
9841        }
9842
9843        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
9844        if fk_ref.match_after_actions {
9845            if let Some(ref match_type) = fk_ref.match_type {
9846                self.write_space();
9847                self.write_keyword("MATCH");
9848                self.write_space();
9849                match match_type {
9850                    MatchType::Full => self.write_keyword("FULL"),
9851                    MatchType::Partial => self.write_keyword("PARTIAL"),
9852                    MatchType::Simple => self.write_keyword("SIMPLE"),
9853                }
9854            }
9855        }
9856
9857        // DEFERRABLE / NOT DEFERRABLE
9858        if let Some(deferrable) = fk_ref.deferrable {
9859            self.write_space();
9860            if deferrable {
9861                self.write_keyword("DEFERRABLE");
9862            } else {
9863                self.write_keyword("NOT DEFERRABLE");
9864            }
9865        }
9866
9867        Ok(())
9868    }
9869
9870    fn generate_referential_action(&mut self, action: &ReferentialAction) {
9871        match action {
9872            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
9873            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
9874            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
9875            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
9876            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
9877        }
9878    }
9879
9880    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
9881        // TSQL: IF NOT OBJECT_ID(...) IS NULL BEGIN DROP TABLE ...; END
9882        if let Some(ref object_id_args) = dt.object_id_args {
9883            if matches!(
9884                self.config.dialect,
9885                Some(crate::dialects::DialectType::TSQL)
9886                    | Some(crate::dialects::DialectType::Fabric)
9887            ) {
9888                self.write_keyword("IF NOT OBJECT_ID");
9889                self.write("(");
9890                self.write(object_id_args);
9891                self.write(")");
9892                self.write_space();
9893                self.write_keyword("IS NULL BEGIN DROP TABLE");
9894                self.write_space();
9895                for (i, table) in dt.names.iter().enumerate() {
9896                    if i > 0 {
9897                        self.write(", ");
9898                    }
9899                    self.generate_table(table)?;
9900                }
9901                self.write("; ");
9902                self.write_keyword("END");
9903                return Ok(());
9904            }
9905        }
9906
9907        // Athena: DROP TABLE uses Hive engine (backticks)
9908        let saved_athena_hive_context = self.athena_hive_context;
9909        if matches!(
9910            self.config.dialect,
9911            Some(crate::dialects::DialectType::Athena)
9912        ) {
9913            self.athena_hive_context = true;
9914        }
9915
9916        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
9917        for comment in &dt.leading_comments {
9918            self.write_formatted_comment(comment);
9919            self.write_space();
9920        }
9921        if dt.iceberg {
9922            self.write_keyword("DROP ICEBERG TABLE");
9923        } else {
9924            self.write_keyword("DROP TABLE");
9925        }
9926
9927        if dt.if_exists {
9928            self.write_space();
9929            self.write_keyword("IF EXISTS");
9930        }
9931
9932        self.write_space();
9933        for (i, table) in dt.names.iter().enumerate() {
9934            if i > 0 {
9935                self.write(", ");
9936            }
9937            self.generate_table(table)?;
9938        }
9939
9940        if dt.cascade_constraints {
9941            self.write_space();
9942            self.write_keyword("CASCADE CONSTRAINTS");
9943        } else if dt.cascade {
9944            self.write_space();
9945            self.write_keyword("CASCADE");
9946        }
9947
9948        if dt.restrict {
9949            self.write_space();
9950            self.write_keyword("RESTRICT");
9951        }
9952
9953        if dt.purge {
9954            self.write_space();
9955            self.write_keyword("PURGE");
9956        }
9957
9958        if dt.sync {
9959            self.write_space();
9960            self.write_keyword("SYNC");
9961        }
9962
9963        // Restore Athena Hive context
9964        self.athena_hive_context = saved_athena_hive_context;
9965
9966        Ok(())
9967    }
9968
9969    fn generate_undrop(&mut self, u: &Undrop) -> Result<()> {
9970        self.write_keyword("UNDROP");
9971        self.write_space();
9972        self.write_keyword(&u.kind);
9973        if u.if_exists {
9974            self.write_space();
9975            self.write_keyword("IF EXISTS");
9976        }
9977        self.write_space();
9978        self.generate_table(&u.name)?;
9979        Ok(())
9980    }
9981
9982    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
9983        // Athena: ALTER TABLE uses Hive engine (backticks)
9984        let saved_athena_hive_context = self.athena_hive_context;
9985        if matches!(
9986            self.config.dialect,
9987            Some(crate::dialects::DialectType::Athena)
9988        ) {
9989            self.athena_hive_context = true;
9990        }
9991
9992        self.write_keyword("ALTER");
9993        // Write table modifier (e.g., ICEBERG) unless target is DuckDB
9994        if let Some(ref modifier) = at.table_modifier {
9995            if !matches!(
9996                self.config.dialect,
9997                Some(crate::dialects::DialectType::DuckDB)
9998            ) {
9999                self.write_space();
10000                self.write_keyword(modifier);
10001            }
10002        }
10003        self.write(" ");
10004        self.write_keyword("TABLE");
10005        if at.if_exists {
10006            self.write_space();
10007            self.write_keyword("IF EXISTS");
10008        }
10009        self.write_space();
10010        self.generate_table(&at.name)?;
10011
10012        // ClickHouse: ON CLUSTER clause
10013        if let Some(ref on_cluster) = at.on_cluster {
10014            self.write_space();
10015            self.generate_on_cluster(on_cluster)?;
10016        }
10017
10018        // Hive: PARTITION(key=value, ...) clause
10019        if let Some(ref partition) = at.partition {
10020            self.write_space();
10021            self.write_keyword("PARTITION");
10022            self.write("(");
10023            for (i, (key, value)) in partition.iter().enumerate() {
10024                if i > 0 {
10025                    self.write(", ");
10026                }
10027                self.generate_identifier(key)?;
10028                self.write(" = ");
10029                self.generate_expression(value)?;
10030            }
10031            self.write(")");
10032        }
10033
10034        // TSQL: WITH CHECK / WITH NOCHECK modifier
10035        if let Some(ref with_check) = at.with_check {
10036            self.write_space();
10037            self.write_keyword(with_check);
10038        }
10039
10040        if self.config.pretty {
10041            // In pretty mode, format actions with newlines and indentation
10042            self.write_newline();
10043            self.indent_level += 1;
10044            for (i, action) in at.actions.iter().enumerate() {
10045                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
10046                let is_continuation = i > 0
10047                    && matches!(
10048                        (&at.actions[i - 1], action),
10049                        (
10050                            AlterTableAction::AddColumn { .. },
10051                            AlterTableAction::AddColumn { .. }
10052                        ) | (
10053                            AlterTableAction::AddConstraint(_),
10054                            AlterTableAction::AddConstraint(_)
10055                        )
10056                    );
10057                if i > 0 {
10058                    self.write(",");
10059                    self.write_newline();
10060                }
10061                self.write_indent();
10062                self.generate_alter_action_with_continuation(action, is_continuation)?;
10063            }
10064            self.indent_level -= 1;
10065        } else {
10066            for (i, action) in at.actions.iter().enumerate() {
10067                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
10068                let is_continuation = i > 0
10069                    && matches!(
10070                        (&at.actions[i - 1], action),
10071                        (
10072                            AlterTableAction::AddColumn { .. },
10073                            AlterTableAction::AddColumn { .. }
10074                        ) | (
10075                            AlterTableAction::AddConstraint(_),
10076                            AlterTableAction::AddConstraint(_)
10077                        )
10078                    );
10079                if i > 0 {
10080                    self.write(",");
10081                }
10082                self.write_space();
10083                self.generate_alter_action_with_continuation(action, is_continuation)?;
10084            }
10085        }
10086
10087        // MySQL ALTER TABLE trailing options
10088        if let Some(ref algorithm) = at.algorithm {
10089            self.write(", ");
10090            self.write_keyword("ALGORITHM");
10091            self.write("=");
10092            self.write_keyword(algorithm);
10093        }
10094        if let Some(ref lock) = at.lock {
10095            self.write(", ");
10096            self.write_keyword("LOCK");
10097            self.write("=");
10098            self.write_keyword(lock);
10099        }
10100
10101        // Restore Athena Hive context
10102        self.athena_hive_context = saved_athena_hive_context;
10103
10104        Ok(())
10105    }
10106
10107    fn generate_alter_action_with_continuation(
10108        &mut self,
10109        action: &AlterTableAction,
10110        is_continuation: bool,
10111    ) -> Result<()> {
10112        match action {
10113            AlterTableAction::AddColumn {
10114                column,
10115                if_not_exists,
10116                position,
10117            } => {
10118                use crate::dialects::DialectType;
10119                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
10120                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
10121                // For other dialects, repeat ADD COLUMN for each
10122                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
10123                let is_tsql_like = matches!(
10124                    self.config.dialect,
10125                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
10126                );
10127                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
10128                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
10129
10130                if is_continuation && (is_snowflake || is_tsql_like) {
10131                    // Don't write ADD keyword for continuation in Snowflake/TSQL
10132                } else if is_snowflake {
10133                    self.write_keyword("ADD");
10134                    self.write_space();
10135                } else if is_athena {
10136                    // Athena uses ADD COLUMNS (col_def) syntax
10137                    self.write_keyword("ADD COLUMNS");
10138                    self.write(" (");
10139                } else if self.config.alter_table_include_column_keyword {
10140                    self.write_keyword("ADD COLUMN");
10141                    self.write_space();
10142                } else {
10143                    // Dialects like Oracle and TSQL don't use COLUMN keyword
10144                    self.write_keyword("ADD");
10145                    self.write_space();
10146                }
10147
10148                if *if_not_exists {
10149                    self.write_keyword("IF NOT EXISTS");
10150                    self.write_space();
10151                }
10152                self.generate_column_def(column)?;
10153
10154                // Close parenthesis for Athena
10155                if is_athena {
10156                    self.write(")");
10157                }
10158
10159                // Column position (FIRST or AFTER)
10160                if let Some(pos) = position {
10161                    self.write_space();
10162                    match pos {
10163                        ColumnPosition::First => self.write_keyword("FIRST"),
10164                        ColumnPosition::After(col_name) => {
10165                            self.write_keyword("AFTER");
10166                            self.write_space();
10167                            self.generate_identifier(col_name)?;
10168                        }
10169                    }
10170                }
10171            }
10172            AlterTableAction::DropColumn {
10173                name,
10174                if_exists,
10175                cascade,
10176            } => {
10177                self.write_keyword("DROP COLUMN");
10178                if *if_exists {
10179                    self.write_space();
10180                    self.write_keyword("IF EXISTS");
10181                }
10182                self.write_space();
10183                self.generate_identifier(name)?;
10184                if *cascade {
10185                    self.write_space();
10186                    self.write_keyword("CASCADE");
10187                }
10188            }
10189            AlterTableAction::DropColumns { names } => {
10190                self.write_keyword("DROP COLUMNS");
10191                self.write(" (");
10192                for (i, name) in names.iter().enumerate() {
10193                    if i > 0 {
10194                        self.write(", ");
10195                    }
10196                    self.generate_identifier(name)?;
10197                }
10198                self.write(")");
10199            }
10200            AlterTableAction::RenameColumn {
10201                old_name,
10202                new_name,
10203                if_exists,
10204            } => {
10205                self.write_keyword("RENAME COLUMN");
10206                if *if_exists {
10207                    self.write_space();
10208                    self.write_keyword("IF EXISTS");
10209                }
10210                self.write_space();
10211                self.generate_identifier(old_name)?;
10212                self.write_space();
10213                self.write_keyword("TO");
10214                self.write_space();
10215                self.generate_identifier(new_name)?;
10216            }
10217            AlterTableAction::AlterColumn {
10218                name,
10219                action,
10220                use_modify_keyword,
10221            } => {
10222                use crate::dialects::DialectType;
10223                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
10224                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
10225                let use_modify = *use_modify_keyword
10226                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
10227                        && matches!(action, AlterColumnAction::SetDataType { .. }));
10228                if use_modify {
10229                    self.write_keyword("MODIFY COLUMN");
10230                    self.write_space();
10231                    self.generate_identifier(name)?;
10232                    // For MODIFY COLUMN, output the type directly
10233                    if let AlterColumnAction::SetDataType {
10234                        data_type,
10235                        using: _,
10236                        collate,
10237                    } = action
10238                    {
10239                        self.write_space();
10240                        self.generate_data_type(data_type)?;
10241                        // Output COLLATE clause if present
10242                        if let Some(collate_name) = collate {
10243                            self.write_space();
10244                            self.write_keyword("COLLATE");
10245                            self.write_space();
10246                            // Output as single-quoted string
10247                            self.write(&format!("'{}'", collate_name));
10248                        }
10249                    } else {
10250                        self.write_space();
10251                        self.generate_alter_column_action(action)?;
10252                    }
10253                } else if matches!(self.config.dialect, Some(DialectType::Hive))
10254                    && matches!(action, AlterColumnAction::SetDataType { .. })
10255                {
10256                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
10257                    self.write_keyword("CHANGE COLUMN");
10258                    self.write_space();
10259                    self.generate_identifier(name)?;
10260                    self.write_space();
10261                    self.generate_identifier(name)?;
10262                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
10263                        self.write_space();
10264                        self.generate_data_type(data_type)?;
10265                    }
10266                } else {
10267                    self.write_keyword("ALTER COLUMN");
10268                    self.write_space();
10269                    self.generate_identifier(name)?;
10270                    self.write_space();
10271                    self.generate_alter_column_action(action)?;
10272                }
10273            }
10274            AlterTableAction::RenameTable(new_name) => {
10275                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
10276                let mysql_like = matches!(
10277                    self.config.dialect,
10278                    Some(DialectType::MySQL)
10279                        | Some(DialectType::Doris)
10280                        | Some(DialectType::StarRocks)
10281                        | Some(DialectType::SingleStore)
10282                );
10283                if mysql_like {
10284                    self.write_keyword("RENAME");
10285                } else {
10286                    self.write_keyword("RENAME TO");
10287                }
10288                self.write_space();
10289                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
10290                let rename_table_with_db = !matches!(
10291                    self.config.dialect,
10292                    Some(DialectType::Doris)
10293                        | Some(DialectType::DuckDB)
10294                        | Some(DialectType::BigQuery)
10295                        | Some(DialectType::PostgreSQL)
10296                );
10297                if !rename_table_with_db {
10298                    let mut stripped = new_name.clone();
10299                    stripped.schema = None;
10300                    stripped.catalog = None;
10301                    self.generate_table(&stripped)?;
10302                } else {
10303                    self.generate_table(new_name)?;
10304                }
10305            }
10306            AlterTableAction::AddConstraint(constraint) => {
10307                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
10308                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
10309                if !is_continuation {
10310                    self.write_keyword("ADD");
10311                    self.write_space();
10312                }
10313                self.generate_table_constraint(constraint)?;
10314            }
10315            AlterTableAction::DropConstraint { name, if_exists } => {
10316                self.write_keyword("DROP CONSTRAINT");
10317                if *if_exists {
10318                    self.write_space();
10319                    self.write_keyword("IF EXISTS");
10320                }
10321                self.write_space();
10322                self.generate_identifier(name)?;
10323            }
10324            AlterTableAction::DropForeignKey { name } => {
10325                self.write_keyword("DROP FOREIGN KEY");
10326                self.write_space();
10327                self.generate_identifier(name)?;
10328            }
10329            AlterTableAction::DropPartition {
10330                partitions,
10331                if_exists,
10332            } => {
10333                self.write_keyword("DROP");
10334                if *if_exists {
10335                    self.write_space();
10336                    self.write_keyword("IF EXISTS");
10337                }
10338                for (i, partition) in partitions.iter().enumerate() {
10339                    if i > 0 {
10340                        self.write(",");
10341                    }
10342                    self.write_space();
10343                    self.write_keyword("PARTITION");
10344                    // Check for special ClickHouse partition formats
10345                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
10346                        // ClickHouse: PARTITION <expression>
10347                        self.write_space();
10348                        self.generate_expression(&partition[0].1)?;
10349                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
10350                        // ClickHouse: PARTITION ALL
10351                        self.write_space();
10352                        self.write_keyword("ALL");
10353                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
10354                        // ClickHouse: PARTITION ID 'string'
10355                        self.write_space();
10356                        self.write_keyword("ID");
10357                        self.write_space();
10358                        self.generate_expression(&partition[0].1)?;
10359                    } else {
10360                        // Standard SQL: PARTITION(key=value, ...)
10361                        self.write("(");
10362                        for (j, (key, value)) in partition.iter().enumerate() {
10363                            if j > 0 {
10364                                self.write(", ");
10365                            }
10366                            self.generate_identifier(key)?;
10367                            self.write(" = ");
10368                            self.generate_expression(value)?;
10369                        }
10370                        self.write(")");
10371                    }
10372                }
10373            }
10374            AlterTableAction::Delete { where_clause } => {
10375                self.write_keyword("DELETE");
10376                self.write_space();
10377                self.write_keyword("WHERE");
10378                self.write_space();
10379                self.generate_expression(where_clause)?;
10380            }
10381            AlterTableAction::SwapWith(target) => {
10382                self.write_keyword("SWAP WITH");
10383                self.write_space();
10384                self.generate_table(target)?;
10385            }
10386            AlterTableAction::SetProperty { properties } => {
10387                use crate::dialects::DialectType;
10388                self.write_keyword("SET");
10389                // Trino/Presto use SET PROPERTIES syntax with spaces around =
10390                let is_trino_presto = matches!(
10391                    self.config.dialect,
10392                    Some(DialectType::Trino) | Some(DialectType::Presto)
10393                );
10394                if is_trino_presto {
10395                    self.write_space();
10396                    self.write_keyword("PROPERTIES");
10397                }
10398                let eq = if is_trino_presto { " = " } else { "=" };
10399                for (i, (key, value)) in properties.iter().enumerate() {
10400                    if i > 0 {
10401                        self.write(",");
10402                    }
10403                    self.write_space();
10404                    // Handle quoted property names for Trino
10405                    if key.contains(' ') {
10406                        self.generate_string_literal(key)?;
10407                    } else {
10408                        self.write(key);
10409                    }
10410                    self.write(eq);
10411                    self.generate_expression(value)?;
10412                }
10413            }
10414            AlterTableAction::UnsetProperty { properties } => {
10415                self.write_keyword("UNSET");
10416                for (i, name) in properties.iter().enumerate() {
10417                    if i > 0 {
10418                        self.write(",");
10419                    }
10420                    self.write_space();
10421                    self.write(name);
10422                }
10423            }
10424            AlterTableAction::ClusterBy { expressions } => {
10425                self.write_keyword("CLUSTER BY");
10426                self.write(" (");
10427                for (i, expr) in expressions.iter().enumerate() {
10428                    if i > 0 {
10429                        self.write(", ");
10430                    }
10431                    self.generate_expression(expr)?;
10432                }
10433                self.write(")");
10434            }
10435            AlterTableAction::SetTag { expressions } => {
10436                self.write_keyword("SET TAG");
10437                for (i, (key, value)) in expressions.iter().enumerate() {
10438                    if i > 0 {
10439                        self.write(",");
10440                    }
10441                    self.write_space();
10442                    self.write(key);
10443                    self.write(" = ");
10444                    self.generate_expression(value)?;
10445                }
10446            }
10447            AlterTableAction::UnsetTag { names } => {
10448                self.write_keyword("UNSET TAG");
10449                for (i, name) in names.iter().enumerate() {
10450                    if i > 0 {
10451                        self.write(",");
10452                    }
10453                    self.write_space();
10454                    self.write(name);
10455                }
10456            }
10457            AlterTableAction::SetOptions { expressions } => {
10458                self.write_keyword("SET");
10459                self.write(" (");
10460                for (i, expr) in expressions.iter().enumerate() {
10461                    if i > 0 {
10462                        self.write(", ");
10463                    }
10464                    self.generate_expression(expr)?;
10465                }
10466                self.write(")");
10467            }
10468            AlterTableAction::AlterIndex { name, visible } => {
10469                self.write_keyword("ALTER INDEX");
10470                self.write_space();
10471                self.generate_identifier(name)?;
10472                self.write_space();
10473                if *visible {
10474                    self.write_keyword("VISIBLE");
10475                } else {
10476                    self.write_keyword("INVISIBLE");
10477                }
10478            }
10479            AlterTableAction::SetAttribute { attribute } => {
10480                self.write_keyword("SET");
10481                self.write_space();
10482                self.write_keyword(attribute);
10483            }
10484            AlterTableAction::SetStageFileFormat { options } => {
10485                self.write_keyword("SET");
10486                self.write_space();
10487                self.write_keyword("STAGE_FILE_FORMAT");
10488                self.write(" = (");
10489                if let Some(opts) = options {
10490                    self.generate_space_separated_properties(opts)?;
10491                }
10492                self.write(")");
10493            }
10494            AlterTableAction::SetStageCopyOptions { options } => {
10495                self.write_keyword("SET");
10496                self.write_space();
10497                self.write_keyword("STAGE_COPY_OPTIONS");
10498                self.write(" = (");
10499                if let Some(opts) = options {
10500                    self.generate_space_separated_properties(opts)?;
10501                }
10502                self.write(")");
10503            }
10504            AlterTableAction::AddColumns { columns, cascade } => {
10505                // Oracle uses ADD (...) without COLUMNS keyword
10506                // Hive/Spark uses ADD COLUMNS (...)
10507                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
10508                if is_oracle {
10509                    self.write_keyword("ADD");
10510                } else {
10511                    self.write_keyword("ADD COLUMNS");
10512                }
10513                self.write(" (");
10514                for (i, col) in columns.iter().enumerate() {
10515                    if i > 0 {
10516                        self.write(", ");
10517                    }
10518                    self.generate_column_def(col)?;
10519                }
10520                self.write(")");
10521                if *cascade {
10522                    self.write_space();
10523                    self.write_keyword("CASCADE");
10524                }
10525            }
10526            AlterTableAction::ChangeColumn {
10527                old_name,
10528                new_name,
10529                data_type,
10530                comment,
10531                cascade,
10532            } => {
10533                use crate::dialects::DialectType;
10534                let is_spark = matches!(
10535                    self.config.dialect,
10536                    Some(DialectType::Spark) | Some(DialectType::Databricks)
10537                );
10538                let is_rename = old_name.name != new_name.name;
10539
10540                if is_spark {
10541                    if is_rename {
10542                        // Spark: RENAME COLUMN old TO new
10543                        self.write_keyword("RENAME COLUMN");
10544                        self.write_space();
10545                        self.generate_identifier(old_name)?;
10546                        self.write_space();
10547                        self.write_keyword("TO");
10548                        self.write_space();
10549                        self.generate_identifier(new_name)?;
10550                    } else if comment.is_some() {
10551                        // Spark: ALTER COLUMN old COMMENT 'comment'
10552                        self.write_keyword("ALTER COLUMN");
10553                        self.write_space();
10554                        self.generate_identifier(old_name)?;
10555                        self.write_space();
10556                        self.write_keyword("COMMENT");
10557                        self.write_space();
10558                        self.write("'");
10559                        self.write(comment.as_ref().unwrap());
10560                        self.write("'");
10561                    } else if data_type.is_some() {
10562                        // Spark: ALTER COLUMN old TYPE data_type
10563                        self.write_keyword("ALTER COLUMN");
10564                        self.write_space();
10565                        self.generate_identifier(old_name)?;
10566                        self.write_space();
10567                        self.write_keyword("TYPE");
10568                        self.write_space();
10569                        self.generate_data_type(data_type.as_ref().unwrap())?;
10570                    } else {
10571                        // Fallback to CHANGE COLUMN
10572                        self.write_keyword("CHANGE COLUMN");
10573                        self.write_space();
10574                        self.generate_identifier(old_name)?;
10575                        self.write_space();
10576                        self.generate_identifier(new_name)?;
10577                    }
10578                } else {
10579                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
10580                    if data_type.is_some() {
10581                        self.write_keyword("CHANGE COLUMN");
10582                    } else {
10583                        self.write_keyword("CHANGE");
10584                    }
10585                    self.write_space();
10586                    self.generate_identifier(old_name)?;
10587                    self.write_space();
10588                    self.generate_identifier(new_name)?;
10589                    if let Some(ref dt) = data_type {
10590                        self.write_space();
10591                        self.generate_data_type(dt)?;
10592                    }
10593                    if let Some(ref c) = comment {
10594                        self.write_space();
10595                        self.write_keyword("COMMENT");
10596                        self.write_space();
10597                        self.write("'");
10598                        self.write(c);
10599                        self.write("'");
10600                    }
10601                    if *cascade {
10602                        self.write_space();
10603                        self.write_keyword("CASCADE");
10604                    }
10605                }
10606            }
10607            AlterTableAction::AddPartition {
10608                partition,
10609                if_not_exists,
10610                location,
10611            } => {
10612                self.write_keyword("ADD");
10613                self.write_space();
10614                if *if_not_exists {
10615                    self.write_keyword("IF NOT EXISTS");
10616                    self.write_space();
10617                }
10618                self.generate_expression(partition)?;
10619                if let Some(ref loc) = location {
10620                    self.write_space();
10621                    self.write_keyword("LOCATION");
10622                    self.write_space();
10623                    self.generate_expression(loc)?;
10624                }
10625            }
10626            AlterTableAction::AlterSortKey {
10627                this,
10628                expressions,
10629                compound,
10630            } => {
10631                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
10632                self.write_keyword("ALTER");
10633                if *compound {
10634                    self.write_space();
10635                    self.write_keyword("COMPOUND");
10636                }
10637                self.write_space();
10638                self.write_keyword("SORTKEY");
10639                self.write_space();
10640                if let Some(style) = this {
10641                    self.write_keyword(style);
10642                } else if !expressions.is_empty() {
10643                    self.write("(");
10644                    for (i, expr) in expressions.iter().enumerate() {
10645                        if i > 0 {
10646                            self.write(", ");
10647                        }
10648                        self.generate_expression(expr)?;
10649                    }
10650                    self.write(")");
10651                }
10652            }
10653            AlterTableAction::AlterDistStyle { style, distkey } => {
10654                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
10655                self.write_keyword("ALTER");
10656                self.write_space();
10657                self.write_keyword("DISTSTYLE");
10658                self.write_space();
10659                self.write_keyword(style);
10660                if let Some(col) = distkey {
10661                    self.write_space();
10662                    self.write_keyword("DISTKEY");
10663                    self.write_space();
10664                    self.generate_identifier(col)?;
10665                }
10666            }
10667            AlterTableAction::SetTableProperties { properties } => {
10668                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
10669                self.write_keyword("SET TABLE PROPERTIES");
10670                self.write(" (");
10671                for (i, (key, value)) in properties.iter().enumerate() {
10672                    if i > 0 {
10673                        self.write(", ");
10674                    }
10675                    self.generate_expression(key)?;
10676                    self.write(" = ");
10677                    self.generate_expression(value)?;
10678                }
10679                self.write(")");
10680            }
10681            AlterTableAction::SetLocation { location } => {
10682                // Redshift: SET LOCATION 's3://bucket/folder/'
10683                self.write_keyword("SET LOCATION");
10684                self.write_space();
10685                self.write("'");
10686                self.write(location);
10687                self.write("'");
10688            }
10689            AlterTableAction::SetFileFormat { format } => {
10690                // Redshift: SET FILE FORMAT AVRO
10691                self.write_keyword("SET FILE FORMAT");
10692                self.write_space();
10693                self.write_keyword(format);
10694            }
10695            AlterTableAction::ReplacePartition { partition, source } => {
10696                // ClickHouse: REPLACE PARTITION expr FROM source
10697                self.write_keyword("REPLACE PARTITION");
10698                self.write_space();
10699                self.generate_expression(partition)?;
10700                if let Some(src) = source {
10701                    self.write_space();
10702                    self.write_keyword("FROM");
10703                    self.write_space();
10704                    self.generate_expression(src)?;
10705                }
10706            }
10707            AlterTableAction::Raw { sql } => {
10708                self.write(sql);
10709            }
10710        }
10711        Ok(())
10712    }
10713
10714    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
10715        match action {
10716            AlterColumnAction::SetDataType {
10717                data_type,
10718                using,
10719                collate,
10720            } => {
10721                use crate::dialects::DialectType;
10722                // Dialect-specific type change syntax:
10723                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
10724                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
10725                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
10726                let is_no_prefix = matches!(
10727                    self.config.dialect,
10728                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
10729                );
10730                let is_type_only = matches!(
10731                    self.config.dialect,
10732                    Some(DialectType::Redshift)
10733                        | Some(DialectType::Spark)
10734                        | Some(DialectType::Databricks)
10735                );
10736                if is_type_only {
10737                    self.write_keyword("TYPE");
10738                    self.write_space();
10739                } else if !is_no_prefix {
10740                    self.write_keyword("SET DATA TYPE");
10741                    self.write_space();
10742                }
10743                self.generate_data_type(data_type)?;
10744                if let Some(ref collation) = collate {
10745                    self.write_space();
10746                    self.write_keyword("COLLATE");
10747                    self.write_space();
10748                    self.write(collation);
10749                }
10750                if let Some(ref using_expr) = using {
10751                    self.write_space();
10752                    self.write_keyword("USING");
10753                    self.write_space();
10754                    self.generate_expression(using_expr)?;
10755                }
10756            }
10757            AlterColumnAction::SetDefault(expr) => {
10758                self.write_keyword("SET DEFAULT");
10759                self.write_space();
10760                self.generate_expression(expr)?;
10761            }
10762            AlterColumnAction::DropDefault => {
10763                self.write_keyword("DROP DEFAULT");
10764            }
10765            AlterColumnAction::SetNotNull => {
10766                self.write_keyword("SET NOT NULL");
10767            }
10768            AlterColumnAction::DropNotNull => {
10769                self.write_keyword("DROP NOT NULL");
10770            }
10771            AlterColumnAction::Comment(comment) => {
10772                self.write_keyword("COMMENT");
10773                self.write_space();
10774                self.generate_string_literal(comment)?;
10775            }
10776            AlterColumnAction::SetVisible => {
10777                self.write_keyword("SET VISIBLE");
10778            }
10779            AlterColumnAction::SetInvisible => {
10780                self.write_keyword("SET INVISIBLE");
10781            }
10782        }
10783        Ok(())
10784    }
10785
10786    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
10787        self.write_keyword("CREATE");
10788
10789        if ci.unique {
10790            self.write_space();
10791            self.write_keyword("UNIQUE");
10792        }
10793
10794        // TSQL CLUSTERED/NONCLUSTERED modifier
10795        if let Some(ref clustered) = ci.clustered {
10796            self.write_space();
10797            self.write_keyword(clustered);
10798        }
10799
10800        self.write_space();
10801        self.write_keyword("INDEX");
10802
10803        // PostgreSQL CONCURRENTLY modifier
10804        if ci.concurrently {
10805            self.write_space();
10806            self.write_keyword("CONCURRENTLY");
10807        }
10808
10809        if ci.if_not_exists {
10810            self.write_space();
10811            self.write_keyword("IF NOT EXISTS");
10812        }
10813
10814        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
10815        if !ci.name.name.is_empty() {
10816            self.write_space();
10817            self.generate_identifier(&ci.name)?;
10818        }
10819        self.write_space();
10820        self.write_keyword("ON");
10821        // Hive uses ON TABLE
10822        if matches!(self.config.dialect, Some(DialectType::Hive)) {
10823            self.write_space();
10824            self.write_keyword("TABLE");
10825        }
10826        self.write_space();
10827        self.generate_table(&ci.table)?;
10828
10829        // Column list (optional for COLUMNSTORE indexes)
10830        // Standard SQL convention: ON t(a) without space before paren
10831        if !ci.columns.is_empty() || ci.using.is_some() {
10832            let space_before_paren = false;
10833
10834            if let Some(ref using) = ci.using {
10835                self.write_space();
10836                self.write_keyword("USING");
10837                self.write_space();
10838                self.write(using);
10839                if space_before_paren {
10840                    self.write(" (");
10841                } else {
10842                    self.write("(");
10843                }
10844            } else {
10845                if space_before_paren {
10846                    self.write(" (");
10847                } else {
10848                    self.write("(");
10849                }
10850            }
10851            for (i, col) in ci.columns.iter().enumerate() {
10852                if i > 0 {
10853                    self.write(", ");
10854                }
10855                self.generate_identifier(&col.column)?;
10856                if let Some(ref opclass) = col.opclass {
10857                    self.write_space();
10858                    self.write(opclass);
10859                }
10860                if col.desc {
10861                    self.write_space();
10862                    self.write_keyword("DESC");
10863                } else if col.asc {
10864                    self.write_space();
10865                    self.write_keyword("ASC");
10866                }
10867                if let Some(nulls_first) = col.nulls_first {
10868                    self.write_space();
10869                    self.write_keyword("NULLS");
10870                    self.write_space();
10871                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
10872                }
10873            }
10874            self.write(")");
10875        }
10876
10877        // PostgreSQL INCLUDE (col1, col2) clause
10878        if !ci.include_columns.is_empty() {
10879            self.write_space();
10880            self.write_keyword("INCLUDE");
10881            self.write(" (");
10882            for (i, col) in ci.include_columns.iter().enumerate() {
10883                if i > 0 {
10884                    self.write(", ");
10885                }
10886                self.generate_identifier(col)?;
10887            }
10888            self.write(")");
10889        }
10890
10891        // TSQL: WITH (option=value, ...) clause
10892        if !ci.with_options.is_empty() {
10893            self.write_space();
10894            self.write_keyword("WITH");
10895            self.write(" (");
10896            for (i, (key, value)) in ci.with_options.iter().enumerate() {
10897                if i > 0 {
10898                    self.write(", ");
10899                }
10900                self.write(key);
10901                self.write("=");
10902                self.write(value);
10903            }
10904            self.write(")");
10905        }
10906
10907        // PostgreSQL WHERE clause for partial indexes
10908        if let Some(ref where_clause) = ci.where_clause {
10909            self.write_space();
10910            self.write_keyword("WHERE");
10911            self.write_space();
10912            self.generate_expression(where_clause)?;
10913        }
10914
10915        // TSQL: ON filegroup or partition scheme clause
10916        if let Some(ref on_fg) = ci.on_filegroup {
10917            self.write_space();
10918            self.write_keyword("ON");
10919            self.write_space();
10920            self.write(on_fg);
10921        }
10922
10923        Ok(())
10924    }
10925
10926    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
10927        self.write_keyword("DROP INDEX");
10928
10929        if di.concurrently {
10930            self.write_space();
10931            self.write_keyword("CONCURRENTLY");
10932        }
10933
10934        if di.if_exists {
10935            self.write_space();
10936            self.write_keyword("IF EXISTS");
10937        }
10938
10939        self.write_space();
10940        self.generate_identifier(&di.name)?;
10941
10942        if let Some(ref table) = di.table {
10943            self.write_space();
10944            self.write_keyword("ON");
10945            self.write_space();
10946            self.generate_table(table)?;
10947        }
10948
10949        Ok(())
10950    }
10951
10952    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
10953        self.write_keyword("CREATE");
10954
10955        // MySQL: ALGORITHM=...
10956        if let Some(ref algorithm) = cv.algorithm {
10957            self.write_space();
10958            self.write_keyword("ALGORITHM");
10959            self.write("=");
10960            self.write_keyword(algorithm);
10961        }
10962
10963        // MySQL: DEFINER=...
10964        if let Some(ref definer) = cv.definer {
10965            self.write_space();
10966            self.write_keyword("DEFINER");
10967            self.write("=");
10968            self.write(definer);
10969        }
10970
10971        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword, unless it appeared after view name)
10972        if cv.security_sql_style && !cv.security_after_name {
10973            if let Some(ref security) = cv.security {
10974                self.write_space();
10975                self.write_keyword("SQL SECURITY");
10976                self.write_space();
10977                match security {
10978                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
10979                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
10980                    FunctionSecurity::None => self.write_keyword("NONE"),
10981                }
10982            }
10983        }
10984
10985        if cv.or_alter {
10986            self.write_space();
10987            self.write_keyword("OR ALTER");
10988        } else if cv.or_replace {
10989            self.write_space();
10990            self.write_keyword("OR REPLACE");
10991        }
10992
10993        if cv.temporary {
10994            self.write_space();
10995            self.write_keyword("TEMPORARY");
10996        }
10997
10998        if cv.materialized {
10999            self.write_space();
11000            self.write_keyword("MATERIALIZED");
11001        }
11002
11003        // Snowflake: SECURE VIEW
11004        if cv.secure {
11005            self.write_space();
11006            self.write_keyword("SECURE");
11007        }
11008
11009        self.write_space();
11010        self.write_keyword("VIEW");
11011
11012        if cv.if_not_exists {
11013            self.write_space();
11014            self.write_keyword("IF NOT EXISTS");
11015        }
11016
11017        self.write_space();
11018        self.generate_table(&cv.name)?;
11019
11020        // ClickHouse: ON CLUSTER clause
11021        if let Some(ref on_cluster) = cv.on_cluster {
11022            self.write_space();
11023            self.generate_on_cluster(on_cluster)?;
11024        }
11025
11026        // ClickHouse: TO destination_table
11027        if let Some(ref to_table) = cv.to_table {
11028            self.write_space();
11029            self.write_keyword("TO");
11030            self.write_space();
11031            self.generate_table(to_table)?;
11032        }
11033
11034        // For regular VIEW: columns come before COPY GRANTS
11035        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
11036        if !cv.materialized {
11037            // Regular VIEW: columns first
11038            if !cv.columns.is_empty() {
11039                self.write(" (");
11040                for (i, col) in cv.columns.iter().enumerate() {
11041                    if i > 0 {
11042                        self.write(", ");
11043                    }
11044                    self.generate_identifier(&col.name)?;
11045                    // BigQuery: OPTIONS (key=value, ...) on view column
11046                    if !col.options.is_empty() {
11047                        self.write_space();
11048                        self.generate_options_clause(&col.options)?;
11049                    }
11050                    if let Some(ref comment) = col.comment {
11051                        self.write_space();
11052                        self.write_keyword("COMMENT");
11053                        self.write_space();
11054                        self.generate_string_literal(comment)?;
11055                    }
11056                }
11057                self.write(")");
11058            }
11059
11060            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
11061            // Also handles SQL SECURITY after view name (security_after_name)
11062            if !cv.security_sql_style || cv.security_after_name {
11063                if let Some(ref security) = cv.security {
11064                    self.write_space();
11065                    if cv.security_sql_style {
11066                        self.write_keyword("SQL SECURITY");
11067                    } else {
11068                        self.write_keyword("SECURITY");
11069                    }
11070                    self.write_space();
11071                    match security {
11072                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
11073                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
11074                        FunctionSecurity::None => self.write_keyword("NONE"),
11075                    }
11076                }
11077            }
11078
11079            // Snowflake: COPY GRANTS
11080            if cv.copy_grants {
11081                self.write_space();
11082                self.write_keyword("COPY GRANTS");
11083            }
11084        } else {
11085            // MATERIALIZED VIEW: COPY GRANTS first
11086            if cv.copy_grants {
11087                self.write_space();
11088                self.write_keyword("COPY GRANTS");
11089            }
11090
11091            // Doris: If we have a schema (typed columns), generate that instead
11092            if let Some(ref schema) = cv.schema {
11093                self.write(" (");
11094                for (i, expr) in schema.expressions.iter().enumerate() {
11095                    if i > 0 {
11096                        self.write(", ");
11097                    }
11098                    self.generate_expression(expr)?;
11099                }
11100                self.write(")");
11101            } else if !cv.columns.is_empty() {
11102                // Then columns (simple column names without types)
11103                self.write(" (");
11104                for (i, col) in cv.columns.iter().enumerate() {
11105                    if i > 0 {
11106                        self.write(", ");
11107                    }
11108                    self.generate_identifier(&col.name)?;
11109                    // BigQuery: OPTIONS (key=value, ...) on view column
11110                    if !col.options.is_empty() {
11111                        self.write_space();
11112                        self.generate_options_clause(&col.options)?;
11113                    }
11114                    if let Some(ref comment) = col.comment {
11115                        self.write_space();
11116                        self.write_keyword("COMMENT");
11117                        self.write_space();
11118                        self.generate_string_literal(comment)?;
11119                    }
11120                }
11121                self.write(")");
11122            }
11123
11124            // Doris: KEY (columns) for materialized views
11125            if let Some(ref unique_key) = cv.unique_key {
11126                self.write_space();
11127                self.write_keyword("KEY");
11128                self.write(" (");
11129                for (i, expr) in unique_key.expressions.iter().enumerate() {
11130                    if i > 0 {
11131                        self.write(", ");
11132                    }
11133                    self.generate_expression(expr)?;
11134                }
11135                self.write(")");
11136            }
11137        }
11138
11139        // Snowflake: COMMENT = 'text'
11140        if let Some(ref comment) = cv.comment {
11141            self.write_space();
11142            self.write_keyword("COMMENT");
11143            self.write("=");
11144            self.generate_string_literal(comment)?;
11145        }
11146
11147        // Snowflake: TAG (name='value', ...)
11148        if !cv.tags.is_empty() {
11149            self.write_space();
11150            self.write_keyword("TAG");
11151            self.write(" (");
11152            for (i, (name, value)) in cv.tags.iter().enumerate() {
11153                if i > 0 {
11154                    self.write(", ");
11155                }
11156                self.write(name);
11157                self.write("='");
11158                self.write(value);
11159                self.write("'");
11160            }
11161            self.write(")");
11162        }
11163
11164        // BigQuery: OPTIONS (key=value, ...)
11165        if !cv.options.is_empty() {
11166            self.write_space();
11167            self.generate_options_clause(&cv.options)?;
11168        }
11169
11170        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
11171        if let Some(ref build) = cv.build {
11172            self.write_space();
11173            self.write_keyword("BUILD");
11174            self.write_space();
11175            self.write_keyword(build);
11176        }
11177
11178        // Doris: REFRESH clause for materialized views
11179        if let Some(ref refresh) = cv.refresh {
11180            self.write_space();
11181            self.generate_refresh_trigger_property(refresh)?;
11182        }
11183
11184        // Redshift: AUTO REFRESH YES|NO for materialized views
11185        if let Some(auto_refresh) = cv.auto_refresh {
11186            self.write_space();
11187            self.write_keyword("AUTO REFRESH");
11188            self.write_space();
11189            if auto_refresh {
11190                self.write_keyword("YES");
11191            } else {
11192                self.write_keyword("NO");
11193            }
11194        }
11195
11196        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
11197        for prop in &cv.table_properties {
11198            self.write_space();
11199            self.generate_expression(prop)?;
11200        }
11201
11202        // Only output AS clause if there's a real query (not just NULL placeholder)
11203        if !matches!(&cv.query, Expression::Null(_)) {
11204            self.write_space();
11205            self.write_keyword("AS");
11206            self.write_space();
11207
11208            // Teradata: LOCKING clause (between AS and query)
11209            if let Some(ref mode) = cv.locking_mode {
11210                self.write_keyword("LOCKING");
11211                self.write_space();
11212                self.write_keyword(mode);
11213                if let Some(ref access) = cv.locking_access {
11214                    self.write_space();
11215                    self.write_keyword("FOR");
11216                    self.write_space();
11217                    self.write_keyword(access);
11218                }
11219                self.write_space();
11220            }
11221
11222            if cv.query_parenthesized {
11223                self.write("(");
11224            }
11225            self.generate_expression(&cv.query)?;
11226            if cv.query_parenthesized {
11227                self.write(")");
11228            }
11229        }
11230
11231        // Redshift: WITH NO SCHEMA BINDING (after query)
11232        if cv.no_schema_binding {
11233            self.write_space();
11234            self.write_keyword("WITH NO SCHEMA BINDING");
11235        }
11236
11237        Ok(())
11238    }
11239
11240    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
11241        self.write_keyword("DROP");
11242
11243        if dv.materialized {
11244            self.write_space();
11245            self.write_keyword("MATERIALIZED");
11246        }
11247
11248        self.write_space();
11249        self.write_keyword("VIEW");
11250
11251        if dv.if_exists {
11252            self.write_space();
11253            self.write_keyword("IF EXISTS");
11254        }
11255
11256        self.write_space();
11257        self.generate_table(&dv.name)?;
11258
11259        Ok(())
11260    }
11261
11262    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
11263        match tr.target {
11264            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
11265            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
11266        }
11267        if tr.if_exists {
11268            self.write_space();
11269            self.write_keyword("IF EXISTS");
11270        }
11271        self.write_space();
11272        self.generate_table(&tr.table)?;
11273
11274        // ClickHouse: ON CLUSTER clause
11275        if let Some(ref on_cluster) = tr.on_cluster {
11276            self.write_space();
11277            self.generate_on_cluster(on_cluster)?;
11278        }
11279
11280        // Check if first table has a * (multi-table with star)
11281        if !tr.extra_tables.is_empty() {
11282            // Check if the first entry matches the main table (star case)
11283            let skip_first = if let Some(first) = tr.extra_tables.first() {
11284                first.table.name == tr.table.name && first.star
11285            } else {
11286                false
11287            };
11288
11289            // PostgreSQL normalizes away the * suffix (it's the default behavior)
11290            let strip_star = matches!(
11291                self.config.dialect,
11292                Some(crate::dialects::DialectType::PostgreSQL)
11293                    | Some(crate::dialects::DialectType::Redshift)
11294            );
11295            if skip_first && !strip_star {
11296                self.write("*");
11297            }
11298
11299            // Generate additional tables
11300            for (i, entry) in tr.extra_tables.iter().enumerate() {
11301                if i == 0 && skip_first {
11302                    continue; // Already handled the star for first table
11303                }
11304                self.write(", ");
11305                self.generate_table(&entry.table)?;
11306                if entry.star && !strip_star {
11307                    self.write("*");
11308                }
11309            }
11310        }
11311
11312        // RESTART/CONTINUE IDENTITY
11313        if let Some(identity) = &tr.identity {
11314            self.write_space();
11315            match identity {
11316                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
11317                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
11318            }
11319        }
11320
11321        if tr.cascade {
11322            self.write_space();
11323            self.write_keyword("CASCADE");
11324        }
11325
11326        if tr.restrict {
11327            self.write_space();
11328            self.write_keyword("RESTRICT");
11329        }
11330
11331        // Output Hive PARTITION clause
11332        if let Some(ref partition) = tr.partition {
11333            self.write_space();
11334            self.generate_expression(partition)?;
11335        }
11336
11337        Ok(())
11338    }
11339
11340    fn generate_use(&mut self, u: &Use) -> Result<()> {
11341        // Teradata uses "DATABASE <name>" instead of "USE <name>"
11342        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
11343            self.write_keyword("DATABASE");
11344            self.write_space();
11345            self.generate_identifier(&u.this)?;
11346            return Ok(());
11347        }
11348
11349        self.write_keyword("USE");
11350
11351        if let Some(kind) = &u.kind {
11352            self.write_space();
11353            match kind {
11354                UseKind::Database => self.write_keyword("DATABASE"),
11355                UseKind::Schema => self.write_keyword("SCHEMA"),
11356                UseKind::Role => self.write_keyword("ROLE"),
11357                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
11358                UseKind::Catalog => self.write_keyword("CATALOG"),
11359                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
11360            }
11361        }
11362
11363        self.write_space();
11364        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
11365        // without quoting, since these are keywords not identifiers
11366        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
11367            self.write(&u.this.name);
11368        } else {
11369            self.generate_identifier(&u.this)?;
11370        }
11371        Ok(())
11372    }
11373
11374    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
11375        self.write_keyword("CACHE");
11376        if c.lazy {
11377            self.write_space();
11378            self.write_keyword("LAZY");
11379        }
11380        self.write_space();
11381        self.write_keyword("TABLE");
11382        self.write_space();
11383        self.generate_identifier(&c.table)?;
11384
11385        // OPTIONS clause
11386        if !c.options.is_empty() {
11387            self.write_space();
11388            self.write_keyword("OPTIONS");
11389            self.write("(");
11390            for (i, (key, value)) in c.options.iter().enumerate() {
11391                if i > 0 {
11392                    self.write(", ");
11393                }
11394                self.generate_expression(key)?;
11395                self.write(" = ");
11396                self.generate_expression(value)?;
11397            }
11398            self.write(")");
11399        }
11400
11401        // AS query
11402        if let Some(query) = &c.query {
11403            self.write_space();
11404            self.write_keyword("AS");
11405            self.write_space();
11406            self.generate_expression(query)?;
11407        }
11408
11409        Ok(())
11410    }
11411
11412    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
11413        self.write_keyword("UNCACHE TABLE");
11414        if u.if_exists {
11415            self.write_space();
11416            self.write_keyword("IF EXISTS");
11417        }
11418        self.write_space();
11419        self.generate_identifier(&u.table)?;
11420        Ok(())
11421    }
11422
11423    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
11424        self.write_keyword("LOAD DATA");
11425        if l.local {
11426            self.write_space();
11427            self.write_keyword("LOCAL");
11428        }
11429        self.write_space();
11430        self.write_keyword("INPATH");
11431        self.write_space();
11432        self.write("'");
11433        self.write(&l.inpath);
11434        self.write("'");
11435
11436        if l.overwrite {
11437            self.write_space();
11438            self.write_keyword("OVERWRITE");
11439        }
11440
11441        self.write_space();
11442        self.write_keyword("INTO TABLE");
11443        self.write_space();
11444        self.generate_expression(&l.table)?;
11445
11446        // PARTITION clause
11447        if !l.partition.is_empty() {
11448            self.write_space();
11449            self.write_keyword("PARTITION");
11450            self.write("(");
11451            for (i, (col, val)) in l.partition.iter().enumerate() {
11452                if i > 0 {
11453                    self.write(", ");
11454                }
11455                self.generate_identifier(col)?;
11456                self.write(" = ");
11457                self.generate_expression(val)?;
11458            }
11459            self.write(")");
11460        }
11461
11462        // INPUTFORMAT clause
11463        if let Some(fmt) = &l.input_format {
11464            self.write_space();
11465            self.write_keyword("INPUTFORMAT");
11466            self.write_space();
11467            self.write("'");
11468            self.write(fmt);
11469            self.write("'");
11470        }
11471
11472        // SERDE clause
11473        if let Some(serde) = &l.serde {
11474            self.write_space();
11475            self.write_keyword("SERDE");
11476            self.write_space();
11477            self.write("'");
11478            self.write(serde);
11479            self.write("'");
11480        }
11481
11482        Ok(())
11483    }
11484
11485    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
11486        self.write_keyword("PRAGMA");
11487        self.write_space();
11488
11489        // Schema prefix if present
11490        if let Some(schema) = &p.schema {
11491            self.generate_identifier(schema)?;
11492            self.write(".");
11493        }
11494
11495        // Pragma name
11496        self.generate_identifier(&p.name)?;
11497
11498        // Value assignment or function call
11499        if let Some(value) = &p.value {
11500            self.write(" = ");
11501            self.generate_expression(value)?;
11502        } else if !p.args.is_empty() {
11503            self.write("(");
11504            for (i, arg) in p.args.iter().enumerate() {
11505                if i > 0 {
11506                    self.write(", ");
11507                }
11508                self.generate_expression(arg)?;
11509            }
11510            self.write(")");
11511        }
11512
11513        Ok(())
11514    }
11515
11516    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
11517        self.write_keyword("GRANT");
11518        self.write_space();
11519
11520        // Privileges (with optional column lists)
11521        for (i, privilege) in g.privileges.iter().enumerate() {
11522            if i > 0 {
11523                self.write(", ");
11524            }
11525            self.write_keyword(&privilege.name);
11526            // Output column list if present: SELECT(col1, col2)
11527            if !privilege.columns.is_empty() {
11528                self.write("(");
11529                for (j, col) in privilege.columns.iter().enumerate() {
11530                    if j > 0 {
11531                        self.write(", ");
11532                    }
11533                    self.write(col);
11534                }
11535                self.write(")");
11536            }
11537        }
11538
11539        self.write_space();
11540        self.write_keyword("ON");
11541        self.write_space();
11542
11543        // Object kind (TABLE, SCHEMA, etc.)
11544        if let Some(kind) = &g.kind {
11545            self.write_keyword(kind);
11546            self.write_space();
11547        }
11548
11549        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11550        {
11551            use crate::dialects::DialectType;
11552            let should_upper = matches!(
11553                self.config.dialect,
11554                Some(DialectType::PostgreSQL)
11555                    | Some(DialectType::CockroachDB)
11556                    | Some(DialectType::Materialize)
11557                    | Some(DialectType::RisingWave)
11558            ) && (g.kind.as_deref() == Some("FUNCTION")
11559                || g.kind.as_deref() == Some("PROCEDURE"));
11560            if should_upper {
11561                use crate::expressions::Identifier;
11562                let upper_id = Identifier {
11563                    name: g.securable.name.to_ascii_uppercase(),
11564                    quoted: g.securable.quoted,
11565                    ..g.securable.clone()
11566                };
11567                self.generate_identifier(&upper_id)?;
11568            } else {
11569                self.generate_identifier(&g.securable)?;
11570            }
11571        }
11572
11573        // Function parameter types (if present)
11574        if !g.function_params.is_empty() {
11575            self.write("(");
11576            for (i, param) in g.function_params.iter().enumerate() {
11577                if i > 0 {
11578                    self.write(", ");
11579                }
11580                self.write(param);
11581            }
11582            self.write(")");
11583        }
11584
11585        self.write_space();
11586        self.write_keyword("TO");
11587        self.write_space();
11588
11589        // Principals
11590        for (i, principal) in g.principals.iter().enumerate() {
11591            if i > 0 {
11592                self.write(", ");
11593            }
11594            if principal.is_role {
11595                self.write_keyword("ROLE");
11596                self.write_space();
11597            } else if principal.is_group {
11598                self.write_keyword("GROUP");
11599                self.write_space();
11600            } else if principal.is_share {
11601                self.write_keyword("SHARE");
11602                self.write_space();
11603            }
11604            self.generate_identifier(&principal.name)?;
11605        }
11606
11607        // WITH GRANT OPTION
11608        if g.grant_option {
11609            self.write_space();
11610            self.write_keyword("WITH GRANT OPTION");
11611        }
11612
11613        // TSQL: AS principal
11614        if let Some(ref principal) = g.as_principal {
11615            self.write_space();
11616            self.write_keyword("AS");
11617            self.write_space();
11618            self.generate_identifier(principal)?;
11619        }
11620
11621        Ok(())
11622    }
11623
11624    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
11625        self.write_keyword("REVOKE");
11626        self.write_space();
11627
11628        // GRANT OPTION FOR
11629        if r.grant_option {
11630            self.write_keyword("GRANT OPTION FOR");
11631            self.write_space();
11632        }
11633
11634        // Privileges (with optional column lists)
11635        for (i, privilege) in r.privileges.iter().enumerate() {
11636            if i > 0 {
11637                self.write(", ");
11638            }
11639            self.write_keyword(&privilege.name);
11640            // Output column list if present: SELECT(col1, col2)
11641            if !privilege.columns.is_empty() {
11642                self.write("(");
11643                for (j, col) in privilege.columns.iter().enumerate() {
11644                    if j > 0 {
11645                        self.write(", ");
11646                    }
11647                    self.write(col);
11648                }
11649                self.write(")");
11650            }
11651        }
11652
11653        self.write_space();
11654        self.write_keyword("ON");
11655        self.write_space();
11656
11657        // Object kind
11658        if let Some(kind) = &r.kind {
11659            self.write_keyword(kind);
11660            self.write_space();
11661        }
11662
11663        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11664        {
11665            use crate::dialects::DialectType;
11666            let should_upper = matches!(
11667                self.config.dialect,
11668                Some(DialectType::PostgreSQL)
11669                    | Some(DialectType::CockroachDB)
11670                    | Some(DialectType::Materialize)
11671                    | Some(DialectType::RisingWave)
11672            ) && (r.kind.as_deref() == Some("FUNCTION")
11673                || r.kind.as_deref() == Some("PROCEDURE"));
11674            if should_upper {
11675                use crate::expressions::Identifier;
11676                let upper_id = Identifier {
11677                    name: r.securable.name.to_ascii_uppercase(),
11678                    quoted: r.securable.quoted,
11679                    ..r.securable.clone()
11680                };
11681                self.generate_identifier(&upper_id)?;
11682            } else {
11683                self.generate_identifier(&r.securable)?;
11684            }
11685        }
11686
11687        // Function parameter types (if present)
11688        if !r.function_params.is_empty() {
11689            self.write("(");
11690            for (i, param) in r.function_params.iter().enumerate() {
11691                if i > 0 {
11692                    self.write(", ");
11693                }
11694                self.write(param);
11695            }
11696            self.write(")");
11697        }
11698
11699        self.write_space();
11700        self.write_keyword("FROM");
11701        self.write_space();
11702
11703        // Principals
11704        for (i, principal) in r.principals.iter().enumerate() {
11705            if i > 0 {
11706                self.write(", ");
11707            }
11708            if principal.is_role {
11709                self.write_keyword("ROLE");
11710                self.write_space();
11711            } else if principal.is_group {
11712                self.write_keyword("GROUP");
11713                self.write_space();
11714            } else if principal.is_share {
11715                self.write_keyword("SHARE");
11716                self.write_space();
11717            }
11718            self.generate_identifier(&principal.name)?;
11719        }
11720
11721        // CASCADE or RESTRICT
11722        if r.cascade {
11723            self.write_space();
11724            self.write_keyword("CASCADE");
11725        } else if r.restrict {
11726            self.write_space();
11727            self.write_keyword("RESTRICT");
11728        }
11729
11730        Ok(())
11731    }
11732
11733    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
11734        self.write_keyword("COMMENT");
11735
11736        // IF EXISTS
11737        if c.exists {
11738            self.write_space();
11739            self.write_keyword("IF EXISTS");
11740        }
11741
11742        self.write_space();
11743        self.write_keyword("ON");
11744
11745        // MATERIALIZED
11746        if c.materialized {
11747            self.write_space();
11748            self.write_keyword("MATERIALIZED");
11749        }
11750
11751        self.write_space();
11752        self.write_keyword(&c.kind);
11753        self.write_space();
11754
11755        // Object name
11756        self.generate_expression(&c.this)?;
11757
11758        self.write_space();
11759        self.write_keyword("IS");
11760        self.write_space();
11761
11762        // Comment expression
11763        self.generate_expression(&c.expression)?;
11764
11765        Ok(())
11766    }
11767
11768    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
11769        self.write_keyword("SET");
11770
11771        for (i, item) in s.items.iter().enumerate() {
11772            if i > 0 {
11773                self.write(",");
11774            }
11775            self.write_space();
11776
11777            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY, VARIABLE)
11778            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
11779            if let Some(ref kind) = item.kind {
11780                // For VARIABLE kind, only output the keyword for dialects that require it
11781                // (Spark, Databricks, DuckDB) - matching Python sqlglot's
11782                // SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD flag
11783                if has_variable_kind {
11784                    if matches!(
11785                        self.config.dialect,
11786                        Some(DialectType::Spark | DialectType::Databricks | DialectType::DuckDB)
11787                    ) {
11788                        self.write_keyword("VARIABLE");
11789                        self.write_space();
11790                    }
11791                } else {
11792                    self.write_keyword(kind);
11793                    self.write_space();
11794                }
11795            }
11796
11797            // Check for special SET forms by name
11798            let name_str = match &item.name {
11799                Expression::Identifier(id) => Some(id.name.as_str()),
11800                _ => None,
11801            };
11802
11803            let is_transaction = name_str == Some("TRANSACTION");
11804            let is_character_set = name_str == Some("CHARACTER SET");
11805            let is_names = name_str == Some("NAMES");
11806            let is_collate = name_str == Some("COLLATE");
11807            let is_value_only =
11808                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
11809
11810            if is_transaction {
11811                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
11812                self.write_keyword("TRANSACTION");
11813                if let Expression::Identifier(id) = &item.value {
11814                    if !id.name.is_empty() {
11815                        self.write_space();
11816                        self.write(&id.name);
11817                    }
11818                }
11819            } else if is_character_set {
11820                // Output: SET CHARACTER SET <charset>
11821                self.write_keyword("CHARACTER SET");
11822                self.write_space();
11823                self.generate_set_value(&item.value)?;
11824            } else if is_names {
11825                // Output: SET NAMES <charset>
11826                self.write_keyword("NAMES");
11827                self.write_space();
11828                self.generate_set_value(&item.value)?;
11829            } else if is_collate {
11830                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
11831                self.write_keyword("COLLATE");
11832                self.write_space();
11833                self.generate_set_value(&item.value)?;
11834            } else if has_variable_kind {
11835                // Output: SET [VARIABLE] <name> = <value>
11836                // VARIABLE keyword already written above if dialect requires it
11837                if let Some(ns) = name_str {
11838                    self.write(ns);
11839                } else {
11840                    self.generate_expression(&item.name)?;
11841                }
11842                self.write(" = ");
11843                self.generate_set_value(&item.value)?;
11844            } else if is_value_only {
11845                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
11846                self.generate_expression(&item.name)?;
11847            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
11848                // SET key value without = (TSQL style)
11849                self.generate_expression(&item.name)?;
11850                self.write_space();
11851                self.generate_set_value(&item.value)?;
11852            } else {
11853                // Standard: variable = value
11854                // SET item names should not be quoted (they are config parameter names, not column refs)
11855                match &item.name {
11856                    Expression::Identifier(id) => {
11857                        self.write(&id.name);
11858                    }
11859                    _ => {
11860                        self.generate_expression(&item.name)?;
11861                    }
11862                }
11863                self.write(" = ");
11864                self.generate_set_value(&item.value)?;
11865            }
11866        }
11867
11868        Ok(())
11869    }
11870
11871    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
11872    /// directly to avoid reserved keyword quoting.
11873    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
11874        if let Expression::Identifier(id) = value {
11875            match id.name.as_str() {
11876                "DEFAULT" | "ON" | "OFF" => {
11877                    self.write_keyword(&id.name);
11878                    return Ok(());
11879                }
11880                _ => {}
11881            }
11882        }
11883        self.generate_expression(value)
11884    }
11885
11886    // ==================== Phase 4: Additional DDL Generation ====================
11887
11888    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
11889        self.write_keyword("ALTER");
11890        // MySQL modifiers before VIEW
11891        if let Some(ref algorithm) = av.algorithm {
11892            self.write_space();
11893            self.write_keyword("ALGORITHM");
11894            self.write(" = ");
11895            self.write_keyword(algorithm);
11896        }
11897        if let Some(ref definer) = av.definer {
11898            self.write_space();
11899            self.write_keyword("DEFINER");
11900            self.write(" = ");
11901            self.write(definer);
11902        }
11903        if let Some(ref sql_security) = av.sql_security {
11904            self.write_space();
11905            self.write_keyword("SQL SECURITY");
11906            self.write(" = ");
11907            self.write_keyword(sql_security);
11908        }
11909        self.write_space();
11910        self.write_keyword("VIEW");
11911        self.write_space();
11912        self.generate_table(&av.name)?;
11913
11914        // Hive: Column aliases with optional COMMENT
11915        if !av.columns.is_empty() {
11916            self.write(" (");
11917            for (i, col) in av.columns.iter().enumerate() {
11918                if i > 0 {
11919                    self.write(", ");
11920                }
11921                self.generate_identifier(&col.name)?;
11922                if let Some(ref comment) = col.comment {
11923                    self.write_space();
11924                    self.write_keyword("COMMENT");
11925                    self.write(" ");
11926                    self.generate_string_literal(comment)?;
11927                }
11928            }
11929            self.write(")");
11930        }
11931
11932        // TSQL: WITH option before actions
11933        if let Some(ref opt) = av.with_option {
11934            self.write_space();
11935            self.write_keyword("WITH");
11936            self.write_space();
11937            self.write_keyword(opt);
11938        }
11939
11940        for action in &av.actions {
11941            self.write_space();
11942            match action {
11943                AlterViewAction::Rename(new_name) => {
11944                    self.write_keyword("RENAME TO");
11945                    self.write_space();
11946                    self.generate_table(new_name)?;
11947                }
11948                AlterViewAction::OwnerTo(owner) => {
11949                    self.write_keyword("OWNER TO");
11950                    self.write_space();
11951                    self.generate_identifier(owner)?;
11952                }
11953                AlterViewAction::SetSchema(schema) => {
11954                    self.write_keyword("SET SCHEMA");
11955                    self.write_space();
11956                    self.generate_identifier(schema)?;
11957                }
11958                AlterViewAction::SetAuthorization(auth) => {
11959                    self.write_keyword("SET AUTHORIZATION");
11960                    self.write_space();
11961                    self.write(auth);
11962                }
11963                AlterViewAction::AlterColumn { name, action } => {
11964                    self.write_keyword("ALTER COLUMN");
11965                    self.write_space();
11966                    self.generate_identifier(name)?;
11967                    self.write_space();
11968                    self.generate_alter_column_action(action)?;
11969                }
11970                AlterViewAction::AsSelect(query) => {
11971                    self.write_keyword("AS");
11972                    self.write_space();
11973                    self.generate_expression(query)?;
11974                }
11975                AlterViewAction::SetTblproperties(props) => {
11976                    self.write_keyword("SET TBLPROPERTIES");
11977                    self.write(" (");
11978                    for (i, (key, value)) in props.iter().enumerate() {
11979                        if i > 0 {
11980                            self.write(", ");
11981                        }
11982                        self.generate_string_literal(key)?;
11983                        self.write("=");
11984                        self.generate_string_literal(value)?;
11985                    }
11986                    self.write(")");
11987                }
11988                AlterViewAction::UnsetTblproperties(keys) => {
11989                    self.write_keyword("UNSET TBLPROPERTIES");
11990                    self.write(" (");
11991                    for (i, key) in keys.iter().enumerate() {
11992                        if i > 0 {
11993                            self.write(", ");
11994                        }
11995                        self.generate_string_literal(key)?;
11996                    }
11997                    self.write(")");
11998                }
11999            }
12000        }
12001
12002        Ok(())
12003    }
12004
12005    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
12006        self.write_keyword("ALTER INDEX");
12007        self.write_space();
12008        self.generate_identifier(&ai.name)?;
12009
12010        if let Some(table) = &ai.table {
12011            self.write_space();
12012            self.write_keyword("ON");
12013            self.write_space();
12014            self.generate_table(table)?;
12015        }
12016
12017        for action in &ai.actions {
12018            self.write_space();
12019            match action {
12020                AlterIndexAction::Rename(new_name) => {
12021                    self.write_keyword("RENAME TO");
12022                    self.write_space();
12023                    self.generate_identifier(new_name)?;
12024                }
12025                AlterIndexAction::SetTablespace(tablespace) => {
12026                    self.write_keyword("SET TABLESPACE");
12027                    self.write_space();
12028                    self.generate_identifier(tablespace)?;
12029                }
12030                AlterIndexAction::Visible(visible) => {
12031                    if *visible {
12032                        self.write_keyword("VISIBLE");
12033                    } else {
12034                        self.write_keyword("INVISIBLE");
12035                    }
12036                }
12037            }
12038        }
12039
12040        Ok(())
12041    }
12042
12043    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
12044        // Output leading comments
12045        for comment in &cs.leading_comments {
12046            self.write_formatted_comment(comment);
12047            self.write_space();
12048        }
12049
12050        // Athena: CREATE SCHEMA uses Hive engine (backticks)
12051        let saved_athena_hive_context = self.athena_hive_context;
12052        if matches!(
12053            self.config.dialect,
12054            Some(crate::dialects::DialectType::Athena)
12055        ) {
12056            self.athena_hive_context = true;
12057        }
12058
12059        self.write_keyword("CREATE SCHEMA");
12060
12061        if cs.if_not_exists {
12062            self.write_space();
12063            self.write_keyword("IF NOT EXISTS");
12064        }
12065
12066        self.write_space();
12067        for (i, part) in cs.name.iter().enumerate() {
12068            if i > 0 {
12069                self.write(".");
12070            }
12071            self.generate_identifier(part)?;
12072        }
12073
12074        if let Some(ref clone_parts) = cs.clone_from {
12075            self.write_keyword(" CLONE ");
12076            for (i, part) in clone_parts.iter().enumerate() {
12077                if i > 0 {
12078                    self.write(".");
12079                }
12080                self.generate_identifier(part)?;
12081            }
12082        }
12083
12084        if let Some(ref at_clause) = cs.at_clause {
12085            self.write_space();
12086            self.generate_expression(at_clause)?;
12087        }
12088
12089        if let Some(auth) = &cs.authorization {
12090            self.write_space();
12091            self.write_keyword("AUTHORIZATION");
12092            self.write_space();
12093            self.generate_identifier(auth)?;
12094        }
12095
12096        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
12097        // Separate WITH properties from other properties
12098        let with_properties: Vec<_> = cs
12099            .properties
12100            .iter()
12101            .filter(|p| matches!(p, Expression::Property(_)))
12102            .collect();
12103        let other_properties: Vec<_> = cs
12104            .properties
12105            .iter()
12106            .filter(|p| !matches!(p, Expression::Property(_)))
12107            .collect();
12108
12109        // Generate WITH (props) if we have Property expressions
12110        if !with_properties.is_empty() {
12111            self.write_space();
12112            self.write_keyword("WITH");
12113            self.write(" (");
12114            for (i, prop) in with_properties.iter().enumerate() {
12115                if i > 0 {
12116                    self.write(", ");
12117                }
12118                self.generate_expression(prop)?;
12119            }
12120            self.write(")");
12121        }
12122
12123        // Generate other properties (like DEFAULT COLLATE)
12124        for prop in other_properties {
12125            self.write_space();
12126            self.generate_expression(prop)?;
12127        }
12128
12129        // Restore Athena Hive context
12130        self.athena_hive_context = saved_athena_hive_context;
12131
12132        Ok(())
12133    }
12134
12135    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
12136        self.write_keyword("DROP SCHEMA");
12137
12138        if ds.if_exists {
12139            self.write_space();
12140            self.write_keyword("IF EXISTS");
12141        }
12142
12143        self.write_space();
12144        self.generate_identifier(&ds.name)?;
12145
12146        if ds.cascade {
12147            self.write_space();
12148            self.write_keyword("CASCADE");
12149        }
12150
12151        Ok(())
12152    }
12153
12154    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
12155        self.write_keyword("DROP NAMESPACE");
12156
12157        if dn.if_exists {
12158            self.write_space();
12159            self.write_keyword("IF EXISTS");
12160        }
12161
12162        self.write_space();
12163        self.generate_identifier(&dn.name)?;
12164
12165        if dn.cascade {
12166            self.write_space();
12167            self.write_keyword("CASCADE");
12168        }
12169
12170        Ok(())
12171    }
12172
12173    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
12174        self.write_keyword("CREATE DATABASE");
12175
12176        if cd.if_not_exists {
12177            self.write_space();
12178            self.write_keyword("IF NOT EXISTS");
12179        }
12180
12181        self.write_space();
12182        self.generate_identifier(&cd.name)?;
12183
12184        if let Some(ref clone_src) = cd.clone_from {
12185            self.write_keyword(" CLONE ");
12186            self.generate_identifier(clone_src)?;
12187        }
12188
12189        // AT/BEFORE clause for time travel (Snowflake)
12190        if let Some(ref at_clause) = cd.at_clause {
12191            self.write_space();
12192            self.generate_expression(at_clause)?;
12193        }
12194
12195        for option in &cd.options {
12196            self.write_space();
12197            match option {
12198                DatabaseOption::CharacterSet(charset) => {
12199                    self.write_keyword("CHARACTER SET");
12200                    self.write(" = ");
12201                    self.write(&format!("'{}'", charset));
12202                }
12203                DatabaseOption::Collate(collate) => {
12204                    self.write_keyword("COLLATE");
12205                    self.write(" = ");
12206                    self.write(&format!("'{}'", collate));
12207                }
12208                DatabaseOption::Owner(owner) => {
12209                    self.write_keyword("OWNER");
12210                    self.write(" = ");
12211                    self.generate_identifier(owner)?;
12212                }
12213                DatabaseOption::Template(template) => {
12214                    self.write_keyword("TEMPLATE");
12215                    self.write(" = ");
12216                    self.generate_identifier(template)?;
12217                }
12218                DatabaseOption::Encoding(encoding) => {
12219                    self.write_keyword("ENCODING");
12220                    self.write(" = ");
12221                    self.write(&format!("'{}'", encoding));
12222                }
12223                DatabaseOption::Location(location) => {
12224                    self.write_keyword("LOCATION");
12225                    self.write(" = ");
12226                    self.write(&format!("'{}'", location));
12227                }
12228            }
12229        }
12230
12231        Ok(())
12232    }
12233
12234    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
12235        self.write_keyword("DROP DATABASE");
12236
12237        if dd.if_exists {
12238            self.write_space();
12239            self.write_keyword("IF EXISTS");
12240        }
12241
12242        self.write_space();
12243        self.generate_identifier(&dd.name)?;
12244
12245        if dd.sync {
12246            self.write_space();
12247            self.write_keyword("SYNC");
12248        }
12249
12250        Ok(())
12251    }
12252
12253    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
12254        self.write_keyword("CREATE");
12255
12256        if cf.or_alter {
12257            self.write_space();
12258            self.write_keyword("OR ALTER");
12259        } else if cf.or_replace {
12260            self.write_space();
12261            self.write_keyword("OR REPLACE");
12262        }
12263
12264        if cf.temporary {
12265            self.write_space();
12266            self.write_keyword("TEMPORARY");
12267        }
12268
12269        self.write_space();
12270        if cf.is_table_function {
12271            self.write_keyword("TABLE FUNCTION");
12272        } else {
12273            self.write_keyword("FUNCTION");
12274        }
12275
12276        if cf.if_not_exists {
12277            self.write_space();
12278            self.write_keyword("IF NOT EXISTS");
12279        }
12280
12281        self.write_space();
12282        self.generate_table(&cf.name)?;
12283        if cf.has_parens {
12284            let func_multiline = self.config.pretty
12285                && matches!(
12286                    self.config.dialect,
12287                    Some(crate::dialects::DialectType::TSQL)
12288                        | Some(crate::dialects::DialectType::Fabric)
12289                )
12290                && !cf.parameters.is_empty();
12291            if func_multiline {
12292                self.write("(\n");
12293                self.indent_level += 2;
12294                self.write_indent();
12295                self.generate_function_parameters(&cf.parameters)?;
12296                self.write("\n");
12297                self.indent_level -= 2;
12298                self.write(")");
12299            } else {
12300                self.write("(");
12301                self.generate_function_parameters(&cf.parameters)?;
12302                self.write(")");
12303            }
12304        }
12305
12306        // Output RETURNS clause (always comes first after parameters)
12307        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
12308        let use_multiline = self.config.pretty
12309            && matches!(
12310                self.config.dialect,
12311                Some(crate::dialects::DialectType::BigQuery)
12312                    | Some(crate::dialects::DialectType::TSQL)
12313                    | Some(crate::dialects::DialectType::Fabric)
12314            );
12315
12316        if cf.language_first {
12317            // LANGUAGE first, then SQL data access, then RETURNS
12318            if let Some(lang) = &cf.language {
12319                if use_multiline {
12320                    self.write_newline();
12321                } else {
12322                    self.write_space();
12323                }
12324                self.write_keyword("LANGUAGE");
12325                self.write_space();
12326                self.write(lang);
12327            }
12328
12329            // SQL data access comes after LANGUAGE in this case
12330            if let Some(sql_data) = &cf.sql_data_access {
12331                self.write_space();
12332                match sql_data {
12333                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12334                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12335                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12336                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12337                }
12338            }
12339
12340            if let Some(ref rtb) = cf.returns_table_body {
12341                if use_multiline {
12342                    self.write_newline();
12343                } else {
12344                    self.write_space();
12345                }
12346                self.write_keyword("RETURNS");
12347                self.write_space();
12348                self.write(rtb);
12349            } else if let Some(return_type) = &cf.return_type {
12350                if use_multiline {
12351                    self.write_newline();
12352                } else {
12353                    self.write_space();
12354                }
12355                self.write_keyword("RETURNS");
12356                self.write_space();
12357                self.generate_data_type(return_type)?;
12358            }
12359        } else {
12360            // RETURNS first (default)
12361            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
12362            let is_duckdb = matches!(
12363                self.config.dialect,
12364                Some(crate::dialects::DialectType::DuckDB)
12365            );
12366            if let Some(ref rtb) = cf.returns_table_body {
12367                if !(is_duckdb && rtb.is_empty()) {
12368                    if use_multiline {
12369                        self.write_newline();
12370                    } else {
12371                        self.write_space();
12372                    }
12373                    self.write_keyword("RETURNS");
12374                    self.write_space();
12375                    self.write(rtb);
12376                }
12377            } else if let Some(return_type) = &cf.return_type {
12378                // DuckDB: skip all RETURNS (DuckDB macros don't use RETURNS clause)
12379                if !is_duckdb {
12380                    let is_table_return = matches!(return_type, crate::expressions::DataType::Custom { ref name } if name.eq_ignore_ascii_case("TABLE"));
12381                    if use_multiline {
12382                        self.write_newline();
12383                    } else {
12384                        self.write_space();
12385                    }
12386                    self.write_keyword("RETURNS");
12387                    self.write_space();
12388                    if is_table_return {
12389                        self.write_keyword("TABLE");
12390                    } else {
12391                        self.generate_data_type(return_type)?;
12392                    }
12393                }
12394            }
12395        }
12396
12397        // If we have property_order, use it to output properties in original order
12398        if !cf.property_order.is_empty() {
12399            // For BigQuery, OPTIONS must come before AS - reorder if needed
12400            let is_bigquery = matches!(
12401                self.config.dialect,
12402                Some(crate::dialects::DialectType::BigQuery)
12403            );
12404            let property_order = if is_bigquery {
12405                // Move Options before As if both are present
12406                let mut reordered = Vec::new();
12407                let mut has_as = false;
12408                let mut has_options = false;
12409                for prop in &cf.property_order {
12410                    match prop {
12411                        FunctionPropertyKind::As => has_as = true,
12412                        FunctionPropertyKind::Options => has_options = true,
12413                        _ => {}
12414                    }
12415                }
12416                if has_as && has_options {
12417                    // Output all props except As and Options, then Options, then As
12418                    for prop in &cf.property_order {
12419                        if *prop != FunctionPropertyKind::As
12420                            && *prop != FunctionPropertyKind::Options
12421                        {
12422                            reordered.push(*prop);
12423                        }
12424                    }
12425                    reordered.push(FunctionPropertyKind::Options);
12426                    reordered.push(FunctionPropertyKind::As);
12427                    reordered
12428                } else {
12429                    cf.property_order.clone()
12430                }
12431            } else {
12432                cf.property_order.clone()
12433            };
12434
12435            for prop in &property_order {
12436                match prop {
12437                    FunctionPropertyKind::Set => {
12438                        self.generate_function_set_options(cf)?;
12439                    }
12440                    FunctionPropertyKind::As => {
12441                        self.generate_function_body(cf)?;
12442                    }
12443                    FunctionPropertyKind::Language => {
12444                        if !cf.language_first {
12445                            // Only output here if not already output above
12446                            if let Some(lang) = &cf.language {
12447                                // Only BigQuery uses multiline formatting
12448                                let use_multiline = self.config.pretty
12449                                    && matches!(
12450                                        self.config.dialect,
12451                                        Some(crate::dialects::DialectType::BigQuery)
12452                                    );
12453                                if use_multiline {
12454                                    self.write_newline();
12455                                } else {
12456                                    self.write_space();
12457                                }
12458                                self.write_keyword("LANGUAGE");
12459                                self.write_space();
12460                                self.write(lang);
12461                            }
12462                        }
12463                    }
12464                    FunctionPropertyKind::Determinism => {
12465                        self.generate_function_determinism(cf)?;
12466                    }
12467                    FunctionPropertyKind::NullInput => {
12468                        self.generate_function_null_input(cf)?;
12469                    }
12470                    FunctionPropertyKind::Security => {
12471                        self.generate_function_security(cf)?;
12472                    }
12473                    FunctionPropertyKind::SqlDataAccess => {
12474                        if !cf.language_first {
12475                            // Only output here if not already output above
12476                            self.generate_function_sql_data_access(cf)?;
12477                        }
12478                    }
12479                    FunctionPropertyKind::Options => {
12480                        if !cf.options.is_empty() {
12481                            self.write_space();
12482                            self.generate_options_clause(&cf.options)?;
12483                        }
12484                    }
12485                    FunctionPropertyKind::Environment => {
12486                        if !cf.environment.is_empty() {
12487                            self.write_space();
12488                            self.generate_environment_clause(&cf.environment)?;
12489                        }
12490                    }
12491                    FunctionPropertyKind::Handler => {
12492                        if let Some(ref h) = cf.handler {
12493                            self.write_space();
12494                            self.write_keyword("HANDLER");
12495                            self.write_space();
12496                            self.write("'");
12497                            self.write(h);
12498                            self.write("'");
12499                        }
12500                    }
12501                    FunctionPropertyKind::ParameterStyle => {
12502                        if let Some(ref ps) = cf.parameter_style {
12503                            self.write_space();
12504                            self.write_keyword("PARAMETER STYLE");
12505                            self.write_space();
12506                            self.write_keyword(ps);
12507                        }
12508                    }
12509                }
12510            }
12511
12512            // Output OPTIONS if not tracked in property_order (legacy)
12513            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
12514            {
12515                self.write_space();
12516                self.generate_options_clause(&cf.options)?;
12517            }
12518
12519            // Output ENVIRONMENT if not tracked in property_order (legacy)
12520            if !cf.environment.is_empty()
12521                && !cf
12522                    .property_order
12523                    .contains(&FunctionPropertyKind::Environment)
12524            {
12525                self.write_space();
12526                self.generate_environment_clause(&cf.environment)?;
12527            }
12528        } else {
12529            // Legacy behavior when property_order is empty
12530            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
12531            if matches!(
12532                self.config.dialect,
12533                Some(crate::dialects::DialectType::BigQuery)
12534            ) {
12535                self.generate_function_determinism(cf)?;
12536            }
12537
12538            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
12539            let use_multiline = self.config.pretty
12540                && matches!(
12541                    self.config.dialect,
12542                    Some(crate::dialects::DialectType::BigQuery)
12543                );
12544
12545            if !cf.language_first {
12546                if let Some(lang) = &cf.language {
12547                    if use_multiline {
12548                        self.write_newline();
12549                    } else {
12550                        self.write_space();
12551                    }
12552                    self.write_keyword("LANGUAGE");
12553                    self.write_space();
12554                    self.write(lang);
12555                }
12556
12557                // SQL data access characteristic comes after LANGUAGE
12558                self.generate_function_sql_data_access(cf)?;
12559            }
12560
12561            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
12562            if !matches!(
12563                self.config.dialect,
12564                Some(crate::dialects::DialectType::BigQuery)
12565            ) {
12566                self.generate_function_determinism(cf)?;
12567            }
12568
12569            self.generate_function_null_input(cf)?;
12570            self.generate_function_security(cf)?;
12571            self.generate_function_set_options(cf)?;
12572
12573            // BigQuery: OPTIONS (key=value, ...) - comes before AS
12574            if !cf.options.is_empty() {
12575                self.write_space();
12576                self.generate_options_clause(&cf.options)?;
12577            }
12578
12579            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
12580            if !cf.environment.is_empty() {
12581                self.write_space();
12582                self.generate_environment_clause(&cf.environment)?;
12583            }
12584
12585            self.generate_function_body(cf)?;
12586        }
12587
12588        Ok(())
12589    }
12590
12591    /// Generate SET options for CREATE FUNCTION
12592    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
12593        for opt in &cf.set_options {
12594            self.write_space();
12595            self.write_keyword("SET");
12596            self.write_space();
12597            self.write(&opt.name);
12598            match &opt.value {
12599                FunctionSetValue::Value { value, use_to } => {
12600                    if *use_to {
12601                        self.write(" TO ");
12602                    } else {
12603                        self.write(" = ");
12604                    }
12605                    self.write(value);
12606                }
12607                FunctionSetValue::FromCurrent => {
12608                    self.write_space();
12609                    self.write_keyword("FROM CURRENT");
12610                }
12611            }
12612        }
12613        Ok(())
12614    }
12615
12616    /// Generate function body (AS clause)
12617    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
12618        if let Some(body) = &cf.body {
12619            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
12620            self.write_space();
12621            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
12622            let use_multiline = self.config.pretty
12623                && matches!(
12624                    self.config.dialect,
12625                    Some(crate::dialects::DialectType::BigQuery)
12626                );
12627            match body {
12628                FunctionBody::Block(block) => {
12629                    self.write_keyword("AS");
12630                    if matches!(
12631                        self.config.dialect,
12632                        Some(crate::dialects::DialectType::TSQL)
12633                    ) {
12634                        self.write(" BEGIN ");
12635                        self.write(block);
12636                        self.write(" END");
12637                    } else if matches!(
12638                        self.config.dialect,
12639                        Some(crate::dialects::DialectType::PostgreSQL)
12640                    ) {
12641                        self.write(" $$");
12642                        self.write(block);
12643                        self.write("$$");
12644                    } else {
12645                        // Escape content for single-quoted output
12646                        let escaped = self.escape_block_for_single_quote(block);
12647                        // In BigQuery pretty mode, body content goes on new line
12648                        if use_multiline {
12649                            self.write_newline();
12650                        } else {
12651                            self.write(" ");
12652                        }
12653                        self.write("'");
12654                        self.write(&escaped);
12655                        self.write("'");
12656                    }
12657                }
12658                FunctionBody::StringLiteral(s) => {
12659                    self.write_keyword("AS");
12660                    // In BigQuery pretty mode, body content goes on new line
12661                    if use_multiline {
12662                        self.write_newline();
12663                    } else {
12664                        self.write(" ");
12665                    }
12666                    self.write("'");
12667                    self.write(s);
12668                    self.write("'");
12669                }
12670                FunctionBody::Expression(expr) => {
12671                    self.write_keyword("AS");
12672                    self.write_space();
12673                    self.generate_expression(expr)?;
12674                }
12675                FunctionBody::External(name) => {
12676                    self.write_keyword("EXTERNAL NAME");
12677                    self.write(" '");
12678                    self.write(name);
12679                    self.write("'");
12680                }
12681                FunctionBody::Return(expr) => {
12682                    if matches!(
12683                        self.config.dialect,
12684                        Some(crate::dialects::DialectType::DuckDB)
12685                    ) {
12686                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
12687                        self.write_keyword("AS");
12688                        self.write_space();
12689                        // Check both returns_table_body marker and return_type = Custom "TABLE"
12690                        let is_table_return = cf.returns_table_body.is_some()
12691                            || matches!(&cf.return_type, Some(crate::expressions::DataType::Custom { ref name }) if name.eq_ignore_ascii_case("TABLE"));
12692                        if is_table_return {
12693                            self.write_keyword("TABLE");
12694                            self.write_space();
12695                        }
12696                        self.generate_expression(expr)?;
12697                    } else {
12698                        if self.config.create_function_return_as {
12699                            self.write_keyword("AS");
12700                            // TSQL pretty: newline between AS and RETURN
12701                            if self.config.pretty
12702                                && matches!(
12703                                    self.config.dialect,
12704                                    Some(crate::dialects::DialectType::TSQL)
12705                                        | Some(crate::dialects::DialectType::Fabric)
12706                                )
12707                            {
12708                                self.write_newline();
12709                            } else {
12710                                self.write_space();
12711                            }
12712                        }
12713                        self.write_keyword("RETURN");
12714                        self.write_space();
12715                        self.generate_expression(expr)?;
12716                    }
12717                }
12718                FunctionBody::Statements(stmts) => {
12719                    self.write_keyword("AS");
12720                    self.write(" BEGIN ");
12721                    for (i, stmt) in stmts.iter().enumerate() {
12722                        if i > 0 {
12723                            self.write(" ");
12724                        }
12725                        self.generate_expression(stmt)?;
12726                        self.write(";");
12727                    }
12728                    self.write(" END");
12729                }
12730                FunctionBody::RawBlock(text) => {
12731                    self.write_newline();
12732                    self.write(text);
12733                }
12734                FunctionBody::DollarQuoted { content, tag } => {
12735                    self.write_keyword("AS");
12736                    self.write(" ");
12737                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
12738                    let supports_dollar_quoting = matches!(
12739                        self.config.dialect,
12740                        Some(crate::dialects::DialectType::PostgreSQL)
12741                            | Some(crate::dialects::DialectType::Databricks)
12742                            | Some(crate::dialects::DialectType::Redshift)
12743                            | Some(crate::dialects::DialectType::DuckDB)
12744                    );
12745                    if supports_dollar_quoting {
12746                        // Output in dollar-quoted format
12747                        self.write("$");
12748                        if let Some(t) = tag {
12749                            self.write(t);
12750                        }
12751                        self.write("$");
12752                        self.write(content);
12753                        self.write("$");
12754                        if let Some(t) = tag {
12755                            self.write(t);
12756                        }
12757                        self.write("$");
12758                    } else {
12759                        // Convert to single-quoted string for other dialects
12760                        let escaped = self.escape_block_for_single_quote(content);
12761                        self.write("'");
12762                        self.write(&escaped);
12763                        self.write("'");
12764                    }
12765                }
12766            }
12767        }
12768        Ok(())
12769    }
12770
12771    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
12772    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
12773        if let Some(det) = cf.deterministic {
12774            self.write_space();
12775            if matches!(
12776                self.config.dialect,
12777                Some(crate::dialects::DialectType::BigQuery)
12778            ) {
12779                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
12780                if det {
12781                    self.write_keyword("DETERMINISTIC");
12782                } else {
12783                    self.write_keyword("NOT DETERMINISTIC");
12784                }
12785            } else {
12786                // PostgreSQL and others use IMMUTABLE/VOLATILE
12787                if det {
12788                    self.write_keyword("IMMUTABLE");
12789                } else {
12790                    self.write_keyword("VOLATILE");
12791                }
12792            }
12793        }
12794        Ok(())
12795    }
12796
12797    /// Generate null input handling clause
12798    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
12799        if let Some(returns_null) = cf.returns_null_on_null_input {
12800            self.write_space();
12801            if returns_null {
12802                if cf.strict {
12803                    self.write_keyword("STRICT");
12804                } else {
12805                    self.write_keyword("RETURNS NULL ON NULL INPUT");
12806                }
12807            } else {
12808                self.write_keyword("CALLED ON NULL INPUT");
12809            }
12810        }
12811        Ok(())
12812    }
12813
12814    /// Generate security clause
12815    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
12816        if let Some(security) = &cf.security {
12817            self.write_space();
12818            // MySQL uses SQL SECURITY prefix
12819            if matches!(
12820                self.config.dialect,
12821                Some(crate::dialects::DialectType::MySQL)
12822            ) {
12823                self.write_keyword("SQL SECURITY");
12824            } else {
12825                self.write_keyword("SECURITY");
12826            }
12827            self.write_space();
12828            match security {
12829                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12830                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12831                FunctionSecurity::None => self.write_keyword("NONE"),
12832            }
12833        }
12834        Ok(())
12835    }
12836
12837    /// Generate SQL data access clause
12838    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
12839        if let Some(sql_data) = &cf.sql_data_access {
12840            self.write_space();
12841            match sql_data {
12842                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12843                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12844                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12845                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12846            }
12847        }
12848        Ok(())
12849    }
12850
12851    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
12852        for (i, param) in params.iter().enumerate() {
12853            if i > 0 {
12854                self.write(", ");
12855            }
12856
12857            if let Some(mode) = &param.mode {
12858                if let Some(text) = &param.mode_text {
12859                    self.write(text);
12860                } else {
12861                    match mode {
12862                        ParameterMode::In => self.write_keyword("IN"),
12863                        ParameterMode::Out => self.write_keyword("OUT"),
12864                        ParameterMode::InOut => self.write_keyword("INOUT"),
12865                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
12866                    }
12867                }
12868                self.write_space();
12869            }
12870
12871            if let Some(name) = &param.name {
12872                self.generate_identifier(name)?;
12873                // Skip space and type for empty Custom types (e.g., DuckDB macros)
12874                let skip_type =
12875                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
12876                if !skip_type {
12877                    self.write_space();
12878                    self.generate_data_type(&param.data_type)?;
12879                }
12880            } else {
12881                self.generate_data_type(&param.data_type)?;
12882            }
12883
12884            if let Some(default) = &param.default {
12885                if self.config.parameter_default_equals {
12886                    self.write(" = ");
12887                } else {
12888                    self.write(" DEFAULT ");
12889                }
12890                self.generate_expression(default)?;
12891            }
12892        }
12893
12894        Ok(())
12895    }
12896
12897    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
12898        self.write_keyword("DROP FUNCTION");
12899
12900        if df.if_exists {
12901            self.write_space();
12902            self.write_keyword("IF EXISTS");
12903        }
12904
12905        self.write_space();
12906        self.generate_table(&df.name)?;
12907
12908        if let Some(params) = &df.parameters {
12909            self.write(" (");
12910            for (i, dt) in params.iter().enumerate() {
12911                if i > 0 {
12912                    self.write(", ");
12913                }
12914                self.generate_data_type(dt)?;
12915            }
12916            self.write(")");
12917        }
12918
12919        if df.cascade {
12920            self.write_space();
12921            self.write_keyword("CASCADE");
12922        }
12923
12924        Ok(())
12925    }
12926
12927    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
12928        self.write_keyword("CREATE");
12929
12930        if cp.or_alter {
12931            self.write_space();
12932            self.write_keyword("OR ALTER");
12933        } else if cp.or_replace {
12934            self.write_space();
12935            self.write_keyword("OR REPLACE");
12936        }
12937
12938        self.write_space();
12939        if cp.use_proc_keyword {
12940            self.write_keyword("PROC");
12941        } else {
12942            self.write_keyword("PROCEDURE");
12943        }
12944
12945        if cp.if_not_exists {
12946            self.write_space();
12947            self.write_keyword("IF NOT EXISTS");
12948        }
12949
12950        self.write_space();
12951        self.generate_table(&cp.name)?;
12952        if cp.has_parens {
12953            self.write("(");
12954            self.generate_function_parameters(&cp.parameters)?;
12955            self.write(")");
12956        } else if !cp.parameters.is_empty() {
12957            // TSQL: unparenthesized parameters
12958            self.write_space();
12959            self.generate_function_parameters(&cp.parameters)?;
12960        }
12961
12962        // RETURNS clause (Snowflake)
12963        if let Some(return_type) = &cp.return_type {
12964            self.write_space();
12965            self.write_keyword("RETURNS");
12966            self.write_space();
12967            self.generate_data_type(return_type)?;
12968        }
12969
12970        // EXECUTE AS clause (Snowflake)
12971        if let Some(execute_as) = &cp.execute_as {
12972            self.write_space();
12973            self.write_keyword("EXECUTE AS");
12974            self.write_space();
12975            self.write_keyword(execute_as);
12976        }
12977
12978        if let Some(lang) = &cp.language {
12979            self.write_space();
12980            self.write_keyword("LANGUAGE");
12981            self.write_space();
12982            self.write(lang);
12983        }
12984
12985        if let Some(security) = &cp.security {
12986            self.write_space();
12987            self.write_keyword("SECURITY");
12988            self.write_space();
12989            match security {
12990                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12991                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12992                FunctionSecurity::None => self.write_keyword("NONE"),
12993            }
12994        }
12995
12996        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
12997        if !cp.with_options.is_empty() {
12998            self.write_space();
12999            self.write_keyword("WITH");
13000            self.write_space();
13001            for (i, opt) in cp.with_options.iter().enumerate() {
13002                if i > 0 {
13003                    self.write(", ");
13004                }
13005                self.write(opt);
13006            }
13007        }
13008
13009        if let Some(body) = &cp.body {
13010            self.write_space();
13011            match body {
13012                FunctionBody::Block(block) => {
13013                    self.write_keyword("AS");
13014                    if matches!(
13015                        self.config.dialect,
13016                        Some(crate::dialects::DialectType::TSQL)
13017                    ) {
13018                        self.write(" BEGIN ");
13019                        self.write(block);
13020                        self.write(" END");
13021                    } else if matches!(
13022                        self.config.dialect,
13023                        Some(crate::dialects::DialectType::PostgreSQL)
13024                    ) {
13025                        self.write(" $$");
13026                        self.write(block);
13027                        self.write("$$");
13028                    } else {
13029                        // Escape content for single-quoted output
13030                        let escaped = self.escape_block_for_single_quote(block);
13031                        self.write(" '");
13032                        self.write(&escaped);
13033                        self.write("'");
13034                    }
13035                }
13036                FunctionBody::StringLiteral(s) => {
13037                    self.write_keyword("AS");
13038                    self.write(" '");
13039                    self.write(s);
13040                    self.write("'");
13041                }
13042                FunctionBody::Expression(expr) => {
13043                    self.write_keyword("AS");
13044                    self.write_space();
13045                    self.generate_expression(expr)?;
13046                }
13047                FunctionBody::External(name) => {
13048                    self.write_keyword("EXTERNAL NAME");
13049                    self.write(" '");
13050                    self.write(name);
13051                    self.write("'");
13052                }
13053                FunctionBody::Return(expr) => {
13054                    self.write_keyword("RETURN");
13055                    self.write_space();
13056                    self.generate_expression(expr)?;
13057                }
13058                FunctionBody::Statements(stmts) => {
13059                    self.write_keyword("AS");
13060                    self.write(" BEGIN ");
13061                    for (i, stmt) in stmts.iter().enumerate() {
13062                        if i > 0 {
13063                            self.write(" ");
13064                        }
13065                        self.generate_expression(stmt)?;
13066                        self.write(";");
13067                    }
13068                    self.write(" END");
13069                }
13070                FunctionBody::RawBlock(text) => {
13071                    self.write_newline();
13072                    self.write(text);
13073                }
13074                FunctionBody::DollarQuoted { content, tag } => {
13075                    self.write_keyword("AS");
13076                    self.write(" ");
13077                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
13078                    let supports_dollar_quoting = matches!(
13079                        self.config.dialect,
13080                        Some(crate::dialects::DialectType::PostgreSQL)
13081                            | Some(crate::dialects::DialectType::Databricks)
13082                            | Some(crate::dialects::DialectType::Redshift)
13083                            | Some(crate::dialects::DialectType::DuckDB)
13084                    );
13085                    if supports_dollar_quoting {
13086                        // Output in dollar-quoted format
13087                        self.write("$");
13088                        if let Some(t) = tag {
13089                            self.write(t);
13090                        }
13091                        self.write("$");
13092                        self.write(content);
13093                        self.write("$");
13094                        if let Some(t) = tag {
13095                            self.write(t);
13096                        }
13097                        self.write("$");
13098                    } else {
13099                        // Convert to single-quoted string for other dialects
13100                        let escaped = self.escape_block_for_single_quote(content);
13101                        self.write("'");
13102                        self.write(&escaped);
13103                        self.write("'");
13104                    }
13105                }
13106            }
13107        }
13108
13109        Ok(())
13110    }
13111
13112    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
13113        self.write_keyword("DROP PROCEDURE");
13114
13115        if dp.if_exists {
13116            self.write_space();
13117            self.write_keyword("IF EXISTS");
13118        }
13119
13120        self.write_space();
13121        self.generate_table(&dp.name)?;
13122
13123        if let Some(params) = &dp.parameters {
13124            self.write(" (");
13125            for (i, dt) in params.iter().enumerate() {
13126                if i > 0 {
13127                    self.write(", ");
13128                }
13129                self.generate_data_type(dt)?;
13130            }
13131            self.write(")");
13132        }
13133
13134        if dp.cascade {
13135            self.write_space();
13136            self.write_keyword("CASCADE");
13137        }
13138
13139        Ok(())
13140    }
13141
13142    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
13143        self.write_keyword("CREATE");
13144
13145        if cs.or_replace {
13146            self.write_space();
13147            self.write_keyword("OR REPLACE");
13148        }
13149
13150        if cs.temporary {
13151            self.write_space();
13152            self.write_keyword("TEMPORARY");
13153        }
13154
13155        self.write_space();
13156        self.write_keyword("SEQUENCE");
13157
13158        if cs.if_not_exists {
13159            self.write_space();
13160            self.write_keyword("IF NOT EXISTS");
13161        }
13162
13163        self.write_space();
13164        self.generate_table(&cs.name)?;
13165
13166        // Output AS <type> if present
13167        if let Some(as_type) = &cs.as_type {
13168            self.write_space();
13169            self.write_keyword("AS");
13170            self.write_space();
13171            self.generate_data_type(as_type)?;
13172        }
13173
13174        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
13175        if let Some(comment) = &cs.comment {
13176            self.write_space();
13177            self.write_keyword("COMMENT");
13178            self.write("=");
13179            self.generate_string_literal(comment)?;
13180        }
13181
13182        // If property_order is available, use it to preserve original order
13183        if !cs.property_order.is_empty() {
13184            for prop in &cs.property_order {
13185                match prop {
13186                    SeqPropKind::Start => {
13187                        if let Some(start) = cs.start {
13188                            self.write_space();
13189                            self.write_keyword("START WITH");
13190                            self.write(&format!(" {}", start));
13191                        }
13192                    }
13193                    SeqPropKind::Increment => {
13194                        if let Some(inc) = cs.increment {
13195                            self.write_space();
13196                            self.write_keyword("INCREMENT BY");
13197                            self.write(&format!(" {}", inc));
13198                        }
13199                    }
13200                    SeqPropKind::Minvalue => {
13201                        if let Some(min) = &cs.minvalue {
13202                            self.write_space();
13203                            match min {
13204                                SequenceBound::Value(v) => {
13205                                    self.write_keyword("MINVALUE");
13206                                    self.write(&format!(" {}", v));
13207                                }
13208                                SequenceBound::None => {
13209                                    self.write_keyword("NO MINVALUE");
13210                                }
13211                            }
13212                        }
13213                    }
13214                    SeqPropKind::Maxvalue => {
13215                        if let Some(max) = &cs.maxvalue {
13216                            self.write_space();
13217                            match max {
13218                                SequenceBound::Value(v) => {
13219                                    self.write_keyword("MAXVALUE");
13220                                    self.write(&format!(" {}", v));
13221                                }
13222                                SequenceBound::None => {
13223                                    self.write_keyword("NO MAXVALUE");
13224                                }
13225                            }
13226                        }
13227                    }
13228                    SeqPropKind::Cache => {
13229                        if let Some(cache) = cs.cache {
13230                            self.write_space();
13231                            self.write_keyword("CACHE");
13232                            self.write(&format!(" {}", cache));
13233                        }
13234                    }
13235                    SeqPropKind::NoCache => {
13236                        self.write_space();
13237                        self.write_keyword("NO CACHE");
13238                    }
13239                    SeqPropKind::NoCacheWord => {
13240                        self.write_space();
13241                        self.write_keyword("NOCACHE");
13242                    }
13243                    SeqPropKind::Cycle => {
13244                        self.write_space();
13245                        self.write_keyword("CYCLE");
13246                    }
13247                    SeqPropKind::NoCycle => {
13248                        self.write_space();
13249                        self.write_keyword("NO CYCLE");
13250                    }
13251                    SeqPropKind::NoCycleWord => {
13252                        self.write_space();
13253                        self.write_keyword("NOCYCLE");
13254                    }
13255                    SeqPropKind::OwnedBy => {
13256                        // Skip OWNED BY NONE (it's a no-op)
13257                        if !cs.owned_by_none {
13258                            if let Some(owned) = &cs.owned_by {
13259                                self.write_space();
13260                                self.write_keyword("OWNED BY");
13261                                self.write_space();
13262                                self.generate_table(owned)?;
13263                            }
13264                        }
13265                    }
13266                    SeqPropKind::Order => {
13267                        self.write_space();
13268                        self.write_keyword("ORDER");
13269                    }
13270                    SeqPropKind::NoOrder => {
13271                        self.write_space();
13272                        self.write_keyword("NOORDER");
13273                    }
13274                    SeqPropKind::Comment => {
13275                        // COMMENT is output above, before property_order iteration
13276                    }
13277                    SeqPropKind::Sharing => {
13278                        if let Some(val) = &cs.sharing {
13279                            self.write_space();
13280                            self.write(&format!("SHARING={}", val));
13281                        }
13282                    }
13283                    SeqPropKind::Keep => {
13284                        self.write_space();
13285                        self.write_keyword("KEEP");
13286                    }
13287                    SeqPropKind::NoKeep => {
13288                        self.write_space();
13289                        self.write_keyword("NOKEEP");
13290                    }
13291                    SeqPropKind::Scale => {
13292                        self.write_space();
13293                        self.write_keyword("SCALE");
13294                        if let Some(modifier) = &cs.scale_modifier {
13295                            if !modifier.is_empty() {
13296                                self.write_space();
13297                                self.write_keyword(modifier);
13298                            }
13299                        }
13300                    }
13301                    SeqPropKind::NoScale => {
13302                        self.write_space();
13303                        self.write_keyword("NOSCALE");
13304                    }
13305                    SeqPropKind::Shard => {
13306                        self.write_space();
13307                        self.write_keyword("SHARD");
13308                        if let Some(modifier) = &cs.shard_modifier {
13309                            if !modifier.is_empty() {
13310                                self.write_space();
13311                                self.write_keyword(modifier);
13312                            }
13313                        }
13314                    }
13315                    SeqPropKind::NoShard => {
13316                        self.write_space();
13317                        self.write_keyword("NOSHARD");
13318                    }
13319                    SeqPropKind::Session => {
13320                        self.write_space();
13321                        self.write_keyword("SESSION");
13322                    }
13323                    SeqPropKind::Global => {
13324                        self.write_space();
13325                        self.write_keyword("GLOBAL");
13326                    }
13327                    SeqPropKind::NoMinvalueWord => {
13328                        self.write_space();
13329                        self.write_keyword("NOMINVALUE");
13330                    }
13331                    SeqPropKind::NoMaxvalueWord => {
13332                        self.write_space();
13333                        self.write_keyword("NOMAXVALUE");
13334                    }
13335                }
13336            }
13337        } else {
13338            // Fallback: default order for backwards compatibility
13339            if let Some(inc) = cs.increment {
13340                self.write_space();
13341                self.write_keyword("INCREMENT BY");
13342                self.write(&format!(" {}", inc));
13343            }
13344
13345            if let Some(min) = &cs.minvalue {
13346                self.write_space();
13347                match min {
13348                    SequenceBound::Value(v) => {
13349                        self.write_keyword("MINVALUE");
13350                        self.write(&format!(" {}", v));
13351                    }
13352                    SequenceBound::None => {
13353                        self.write_keyword("NO MINVALUE");
13354                    }
13355                }
13356            }
13357
13358            if let Some(max) = &cs.maxvalue {
13359                self.write_space();
13360                match max {
13361                    SequenceBound::Value(v) => {
13362                        self.write_keyword("MAXVALUE");
13363                        self.write(&format!(" {}", v));
13364                    }
13365                    SequenceBound::None => {
13366                        self.write_keyword("NO MAXVALUE");
13367                    }
13368                }
13369            }
13370
13371            if let Some(start) = cs.start {
13372                self.write_space();
13373                self.write_keyword("START WITH");
13374                self.write(&format!(" {}", start));
13375            }
13376
13377            if let Some(cache) = cs.cache {
13378                self.write_space();
13379                self.write_keyword("CACHE");
13380                self.write(&format!(" {}", cache));
13381            }
13382
13383            if cs.cycle {
13384                self.write_space();
13385                self.write_keyword("CYCLE");
13386            }
13387
13388            if let Some(owned) = &cs.owned_by {
13389                self.write_space();
13390                self.write_keyword("OWNED BY");
13391                self.write_space();
13392                self.generate_table(owned)?;
13393            }
13394        }
13395
13396        Ok(())
13397    }
13398
13399    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
13400        self.write_keyword("DROP SEQUENCE");
13401
13402        if ds.if_exists {
13403            self.write_space();
13404            self.write_keyword("IF EXISTS");
13405        }
13406
13407        self.write_space();
13408        self.generate_table(&ds.name)?;
13409
13410        if ds.cascade {
13411            self.write_space();
13412            self.write_keyword("CASCADE");
13413        }
13414
13415        Ok(())
13416    }
13417
13418    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
13419        self.write_keyword("ALTER SEQUENCE");
13420
13421        if als.if_exists {
13422            self.write_space();
13423            self.write_keyword("IF EXISTS");
13424        }
13425
13426        self.write_space();
13427        self.generate_table(&als.name)?;
13428
13429        if let Some(inc) = als.increment {
13430            self.write_space();
13431            self.write_keyword("INCREMENT BY");
13432            self.write(&format!(" {}", inc));
13433        }
13434
13435        if let Some(min) = &als.minvalue {
13436            self.write_space();
13437            match min {
13438                SequenceBound::Value(v) => {
13439                    self.write_keyword("MINVALUE");
13440                    self.write(&format!(" {}", v));
13441                }
13442                SequenceBound::None => {
13443                    self.write_keyword("NO MINVALUE");
13444                }
13445            }
13446        }
13447
13448        if let Some(max) = &als.maxvalue {
13449            self.write_space();
13450            match max {
13451                SequenceBound::Value(v) => {
13452                    self.write_keyword("MAXVALUE");
13453                    self.write(&format!(" {}", v));
13454                }
13455                SequenceBound::None => {
13456                    self.write_keyword("NO MAXVALUE");
13457                }
13458            }
13459        }
13460
13461        if let Some(start) = als.start {
13462            self.write_space();
13463            self.write_keyword("START WITH");
13464            self.write(&format!(" {}", start));
13465        }
13466
13467        if let Some(restart) = &als.restart {
13468            self.write_space();
13469            self.write_keyword("RESTART");
13470            if let Some(val) = restart {
13471                self.write_keyword(" WITH");
13472                self.write(&format!(" {}", val));
13473            }
13474        }
13475
13476        if let Some(cache) = als.cache {
13477            self.write_space();
13478            self.write_keyword("CACHE");
13479            self.write(&format!(" {}", cache));
13480        }
13481
13482        if let Some(cycle) = als.cycle {
13483            self.write_space();
13484            if cycle {
13485                self.write_keyword("CYCLE");
13486            } else {
13487                self.write_keyword("NO CYCLE");
13488            }
13489        }
13490
13491        if let Some(owned) = &als.owned_by {
13492            self.write_space();
13493            self.write_keyword("OWNED BY");
13494            self.write_space();
13495            if let Some(table) = owned {
13496                self.generate_table(table)?;
13497            } else {
13498                self.write_keyword("NONE");
13499            }
13500        }
13501
13502        Ok(())
13503    }
13504
13505    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
13506        self.write_keyword("CREATE");
13507
13508        if ct.or_alter {
13509            self.write_space();
13510            self.write_keyword("OR ALTER");
13511        } else if ct.or_replace {
13512            self.write_space();
13513            self.write_keyword("OR REPLACE");
13514        }
13515
13516        if ct.constraint {
13517            self.write_space();
13518            self.write_keyword("CONSTRAINT");
13519        }
13520
13521        self.write_space();
13522        self.write_keyword("TRIGGER");
13523        self.write_space();
13524        self.generate_identifier(&ct.name)?;
13525
13526        self.write_space();
13527        match ct.timing {
13528            TriggerTiming::Before => self.write_keyword("BEFORE"),
13529            TriggerTiming::After => self.write_keyword("AFTER"),
13530            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
13531        }
13532
13533        // Events
13534        for (i, event) in ct.events.iter().enumerate() {
13535            if i > 0 {
13536                self.write_keyword(" OR");
13537            }
13538            self.write_space();
13539            match event {
13540                TriggerEvent::Insert => self.write_keyword("INSERT"),
13541                TriggerEvent::Update(cols) => {
13542                    self.write_keyword("UPDATE");
13543                    if let Some(cols) = cols {
13544                        self.write_space();
13545                        self.write_keyword("OF");
13546                        for (j, col) in cols.iter().enumerate() {
13547                            if j > 0 {
13548                                self.write(",");
13549                            }
13550                            self.write_space();
13551                            self.generate_identifier(col)?;
13552                        }
13553                    }
13554                }
13555                TriggerEvent::Delete => self.write_keyword("DELETE"),
13556                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
13557            }
13558        }
13559
13560        self.write_space();
13561        self.write_keyword("ON");
13562        self.write_space();
13563        self.generate_table(&ct.table)?;
13564
13565        // Referencing clause
13566        if let Some(ref_clause) = &ct.referencing {
13567            self.write_space();
13568            self.write_keyword("REFERENCING");
13569            if let Some(old_table) = &ref_clause.old_table {
13570                self.write_space();
13571                self.write_keyword("OLD TABLE AS");
13572                self.write_space();
13573                self.generate_identifier(old_table)?;
13574            }
13575            if let Some(new_table) = &ref_clause.new_table {
13576                self.write_space();
13577                self.write_keyword("NEW TABLE AS");
13578                self.write_space();
13579                self.generate_identifier(new_table)?;
13580            }
13581            if let Some(old_row) = &ref_clause.old_row {
13582                self.write_space();
13583                self.write_keyword("OLD ROW AS");
13584                self.write_space();
13585                self.generate_identifier(old_row)?;
13586            }
13587            if let Some(new_row) = &ref_clause.new_row {
13588                self.write_space();
13589                self.write_keyword("NEW ROW AS");
13590                self.write_space();
13591                self.generate_identifier(new_row)?;
13592            }
13593        }
13594
13595        // Deferrable options for constraint triggers (must come before FOR EACH)
13596        if let Some(deferrable) = ct.deferrable {
13597            self.write_space();
13598            if deferrable {
13599                self.write_keyword("DEFERRABLE");
13600            } else {
13601                self.write_keyword("NOT DEFERRABLE");
13602            }
13603        }
13604
13605        if let Some(initially) = ct.initially_deferred {
13606            self.write_space();
13607            self.write_keyword("INITIALLY");
13608            self.write_space();
13609            if initially {
13610                self.write_keyword("DEFERRED");
13611            } else {
13612                self.write_keyword("IMMEDIATE");
13613            }
13614        }
13615
13616        if let Some(for_each) = ct.for_each {
13617            self.write_space();
13618            self.write_keyword("FOR EACH");
13619            self.write_space();
13620            match for_each {
13621                TriggerForEach::Row => self.write_keyword("ROW"),
13622                TriggerForEach::Statement => self.write_keyword("STATEMENT"),
13623            }
13624        }
13625
13626        // When clause
13627        if let Some(when) = &ct.when {
13628            self.write_space();
13629            self.write_keyword("WHEN");
13630            if ct.when_paren {
13631                self.write(" (");
13632                self.generate_expression(when)?;
13633                self.write(")");
13634            } else {
13635                self.write_space();
13636                self.generate_expression(when)?;
13637            }
13638        }
13639
13640        // Body
13641        self.write_space();
13642        match &ct.body {
13643            TriggerBody::Execute { function, args } => {
13644                self.write_keyword("EXECUTE FUNCTION");
13645                self.write_space();
13646                self.generate_table(function)?;
13647                self.write("(");
13648                for (i, arg) in args.iter().enumerate() {
13649                    if i > 0 {
13650                        self.write(", ");
13651                    }
13652                    self.generate_expression(arg)?;
13653                }
13654                self.write(")");
13655            }
13656            TriggerBody::Block(block) => {
13657                self.write_keyword("BEGIN");
13658                self.write_space();
13659                self.write(block);
13660                self.write_space();
13661                self.write_keyword("END");
13662            }
13663        }
13664
13665        Ok(())
13666    }
13667
13668    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
13669        self.write_keyword("DROP TRIGGER");
13670
13671        if dt.if_exists {
13672            self.write_space();
13673            self.write_keyword("IF EXISTS");
13674        }
13675
13676        self.write_space();
13677        self.generate_identifier(&dt.name)?;
13678
13679        if let Some(table) = &dt.table {
13680            self.write_space();
13681            self.write_keyword("ON");
13682            self.write_space();
13683            self.generate_table(table)?;
13684        }
13685
13686        if dt.cascade {
13687            self.write_space();
13688            self.write_keyword("CASCADE");
13689        }
13690
13691        Ok(())
13692    }
13693
13694    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
13695        self.write_keyword("CREATE TYPE");
13696
13697        if ct.if_not_exists {
13698            self.write_space();
13699            self.write_keyword("IF NOT EXISTS");
13700        }
13701
13702        self.write_space();
13703        self.generate_table(&ct.name)?;
13704
13705        self.write_space();
13706        self.write_keyword("AS");
13707        self.write_space();
13708
13709        match &ct.definition {
13710            TypeDefinition::Enum(values) => {
13711                self.write_keyword("ENUM");
13712                self.write(" (");
13713                for (i, val) in values.iter().enumerate() {
13714                    if i > 0 {
13715                        self.write(", ");
13716                    }
13717                    self.write(&format!("'{}'", val));
13718                }
13719                self.write(")");
13720            }
13721            TypeDefinition::Composite(attrs) => {
13722                self.write("(");
13723                for (i, attr) in attrs.iter().enumerate() {
13724                    if i > 0 {
13725                        self.write(", ");
13726                    }
13727                    self.generate_identifier(&attr.name)?;
13728                    self.write_space();
13729                    self.generate_data_type(&attr.data_type)?;
13730                    if let Some(collate) = &attr.collate {
13731                        self.write_space();
13732                        self.write_keyword("COLLATE");
13733                        self.write_space();
13734                        self.generate_identifier(collate)?;
13735                    }
13736                }
13737                self.write(")");
13738            }
13739            TypeDefinition::Range {
13740                subtype,
13741                subtype_diff,
13742                canonical,
13743            } => {
13744                self.write_keyword("RANGE");
13745                self.write(" (");
13746                self.write_keyword("SUBTYPE");
13747                self.write(" = ");
13748                self.generate_data_type(subtype)?;
13749                if let Some(diff) = subtype_diff {
13750                    self.write(", ");
13751                    self.write_keyword("SUBTYPE_DIFF");
13752                    self.write(" = ");
13753                    self.write(diff);
13754                }
13755                if let Some(canon) = canonical {
13756                    self.write(", ");
13757                    self.write_keyword("CANONICAL");
13758                    self.write(" = ");
13759                    self.write(canon);
13760                }
13761                self.write(")");
13762            }
13763            TypeDefinition::Base {
13764                input,
13765                output,
13766                internallength,
13767            } => {
13768                self.write("(");
13769                self.write_keyword("INPUT");
13770                self.write(" = ");
13771                self.write(input);
13772                self.write(", ");
13773                self.write_keyword("OUTPUT");
13774                self.write(" = ");
13775                self.write(output);
13776                if let Some(len) = internallength {
13777                    self.write(", ");
13778                    self.write_keyword("INTERNALLENGTH");
13779                    self.write(" = ");
13780                    self.write(&len.to_string());
13781                }
13782                self.write(")");
13783            }
13784            TypeDefinition::Domain {
13785                base_type,
13786                default,
13787                constraints,
13788            } => {
13789                self.generate_data_type(base_type)?;
13790                if let Some(def) = default {
13791                    self.write_space();
13792                    self.write_keyword("DEFAULT");
13793                    self.write_space();
13794                    self.generate_expression(def)?;
13795                }
13796                for constr in constraints {
13797                    self.write_space();
13798                    if let Some(name) = &constr.name {
13799                        self.write_keyword("CONSTRAINT");
13800                        self.write_space();
13801                        self.generate_identifier(name)?;
13802                        self.write_space();
13803                    }
13804                    self.write_keyword("CHECK");
13805                    self.write(" (");
13806                    self.generate_expression(&constr.check)?;
13807                    self.write(")");
13808                }
13809            }
13810        }
13811
13812        Ok(())
13813    }
13814
13815    fn generate_create_task(&mut self, task: &crate::expressions::CreateTask) -> Result<()> {
13816        self.write_keyword("CREATE");
13817        if task.or_replace {
13818            self.write_space();
13819            self.write_keyword("OR REPLACE");
13820        }
13821        self.write_space();
13822        self.write_keyword("TASK");
13823        if task.if_not_exists {
13824            self.write_space();
13825            self.write_keyword("IF NOT EXISTS");
13826        }
13827        self.write_space();
13828        self.write(&task.name);
13829        if !task.properties.is_empty() {
13830            // Properties already include leading whitespace from tokens_to_sql
13831            if !task.properties.starts_with('\n') && !task.properties.starts_with(' ') {
13832                self.write_space();
13833            }
13834            self.write(&task.properties);
13835        }
13836        self.write_space();
13837        self.write_keyword("AS");
13838        self.write_space();
13839        self.generate_expression(&task.body)?;
13840        Ok(())
13841    }
13842
13843    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
13844        self.write_keyword("DROP TYPE");
13845
13846        if dt.if_exists {
13847            self.write_space();
13848            self.write_keyword("IF EXISTS");
13849        }
13850
13851        self.write_space();
13852        self.generate_table(&dt.name)?;
13853
13854        if dt.cascade {
13855            self.write_space();
13856            self.write_keyword("CASCADE");
13857        }
13858
13859        Ok(())
13860    }
13861
13862    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
13863        // Athena: DESCRIBE uses Hive engine (backticks)
13864        let saved_athena_hive_context = self.athena_hive_context;
13865        if matches!(
13866            self.config.dialect,
13867            Some(crate::dialects::DialectType::Athena)
13868        ) {
13869            self.athena_hive_context = true;
13870        }
13871
13872        // Output leading comments before DESCRIBE
13873        for comment in &d.leading_comments {
13874            self.write_formatted_comment(comment);
13875            self.write(" ");
13876        }
13877
13878        self.write_keyword("DESCRIBE");
13879
13880        if d.extended {
13881            self.write_space();
13882            self.write_keyword("EXTENDED");
13883        } else if d.formatted {
13884            self.write_space();
13885            self.write_keyword("FORMATTED");
13886        }
13887
13888        // Output style like ANALYZE, HISTORY
13889        if let Some(ref style) = d.style {
13890            self.write_space();
13891            self.write_keyword(style);
13892        }
13893
13894        // Handle object kind (TABLE, VIEW) based on dialect
13895        let should_output_kind = match self.config.dialect {
13896            // Spark doesn't use TABLE/VIEW after DESCRIBE
13897            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
13898                false
13899            }
13900            // Snowflake always includes TABLE
13901            Some(DialectType::Snowflake) => true,
13902            _ => d.kind.is_some(),
13903        };
13904        if should_output_kind {
13905            if let Some(ref kind) = d.kind {
13906                self.write_space();
13907                self.write_keyword(kind);
13908            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
13909                self.write_space();
13910                self.write_keyword("TABLE");
13911            }
13912        }
13913
13914        self.write_space();
13915        self.generate_expression(&d.target)?;
13916
13917        // Output parenthesized parameter types for PROCEDURE/FUNCTION
13918        if !d.params.is_empty() {
13919            self.write("(");
13920            for (i, param) in d.params.iter().enumerate() {
13921                if i > 0 {
13922                    self.write(", ");
13923                }
13924                self.write(param);
13925            }
13926            self.write(")");
13927        }
13928
13929        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
13930        if let Some(ref partition) = d.partition {
13931            self.write_space();
13932            self.generate_expression(partition)?;
13933        }
13934
13935        // Databricks: AS JSON
13936        if d.as_json {
13937            self.write_space();
13938            self.write_keyword("AS JSON");
13939        }
13940
13941        // Output properties like type=stage
13942        for (name, value) in &d.properties {
13943            self.write_space();
13944            self.write(name);
13945            self.write("=");
13946            self.write(value);
13947        }
13948
13949        // Restore Athena Hive context
13950        self.athena_hive_context = saved_athena_hive_context;
13951
13952        Ok(())
13953    }
13954
13955    /// Generate SHOW statement (Snowflake, MySQL, etc.)
13956    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
13957    fn generate_show(&mut self, s: &Show) -> Result<()> {
13958        self.write_keyword("SHOW");
13959        self.write_space();
13960
13961        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
13962        // where TERSE is syntactically valid but has no effect on output
13963        let show_terse = s.terse
13964            && !matches!(
13965                s.this.as_str(),
13966                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
13967            );
13968        if show_terse {
13969            self.write_keyword("TERSE");
13970            self.write_space();
13971        }
13972
13973        // Object type (USERS, TABLES, DATABASES, etc.)
13974        self.write_keyword(&s.this);
13975
13976        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
13977        if let Some(ref target_expr) = s.target {
13978            self.write_space();
13979            self.generate_expression(target_expr)?;
13980        }
13981
13982        // HISTORY keyword
13983        if s.history {
13984            self.write_space();
13985            self.write_keyword("HISTORY");
13986        }
13987
13988        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
13989        if let Some(ref for_target) = s.for_target {
13990            self.write_space();
13991            self.write_keyword("FOR");
13992            self.write_space();
13993            self.generate_expression(for_target)?;
13994        }
13995
13996        // Determine ordering based on dialect:
13997        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
13998        // - MySQL: IN, FROM, LIKE (when FROM is present)
13999        use crate::dialects::DialectType;
14000        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
14001
14002        if !is_snowflake && s.from.is_some() {
14003            // MySQL ordering: IN, FROM, LIKE
14004
14005            // IN scope_kind [scope]
14006            if let Some(ref scope_kind) = s.scope_kind {
14007                self.write_space();
14008                self.write_keyword("IN");
14009                self.write_space();
14010                self.write_keyword(scope_kind);
14011                if let Some(ref scope) = s.scope {
14012                    self.write_space();
14013                    self.generate_expression(scope)?;
14014                }
14015            } else if let Some(ref scope) = s.scope {
14016                self.write_space();
14017                self.write_keyword("IN");
14018                self.write_space();
14019                self.generate_expression(scope)?;
14020            }
14021
14022            // FROM clause
14023            if let Some(ref from) = s.from {
14024                self.write_space();
14025                self.write_keyword("FROM");
14026                self.write_space();
14027                self.generate_expression(from)?;
14028            }
14029
14030            // Second FROM clause (db name)
14031            if let Some(ref db) = s.db {
14032                self.write_space();
14033                self.write_keyword("FROM");
14034                self.write_space();
14035                self.generate_expression(db)?;
14036            }
14037
14038            // LIKE pattern
14039            if let Some(ref like) = s.like {
14040                self.write_space();
14041                self.write_keyword("LIKE");
14042                self.write_space();
14043                self.generate_expression(like)?;
14044            }
14045        } else {
14046            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
14047
14048            // LIKE pattern
14049            if let Some(ref like) = s.like {
14050                self.write_space();
14051                self.write_keyword("LIKE");
14052                self.write_space();
14053                self.generate_expression(like)?;
14054            }
14055
14056            // IN scope_kind [scope]
14057            if let Some(ref scope_kind) = s.scope_kind {
14058                self.write_space();
14059                self.write_keyword("IN");
14060                self.write_space();
14061                self.write_keyword(scope_kind);
14062                if let Some(ref scope) = s.scope {
14063                    self.write_space();
14064                    self.generate_expression(scope)?;
14065                }
14066            } else if let Some(ref scope) = s.scope {
14067                self.write_space();
14068                self.write_keyword("IN");
14069                self.write_space();
14070                self.generate_expression(scope)?;
14071            }
14072        }
14073
14074        // STARTS WITH pattern
14075        if let Some(ref starts_with) = s.starts_with {
14076            self.write_space();
14077            self.write_keyword("STARTS WITH");
14078            self.write_space();
14079            self.generate_expression(starts_with)?;
14080        }
14081
14082        // LIMIT clause
14083        if let Some(ref limit) = s.limit {
14084            self.write_space();
14085            self.generate_limit(limit)?;
14086        }
14087
14088        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
14089        if is_snowflake {
14090            if let Some(ref from) = s.from {
14091                self.write_space();
14092                self.write_keyword("FROM");
14093                self.write_space();
14094                self.generate_expression(from)?;
14095            }
14096        }
14097
14098        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
14099        if let Some(ref where_clause) = s.where_clause {
14100            self.write_space();
14101            self.write_keyword("WHERE");
14102            self.write_space();
14103            self.generate_expression(where_clause)?;
14104        }
14105
14106        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
14107        if let Some(is_mutex) = s.mutex {
14108            self.write_space();
14109            if is_mutex {
14110                self.write_keyword("MUTEX");
14111            } else {
14112                self.write_keyword("STATUS");
14113            }
14114        }
14115
14116        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
14117        if !s.privileges.is_empty() {
14118            self.write_space();
14119            self.write_keyword("WITH PRIVILEGES");
14120            self.write_space();
14121            for (i, priv_name) in s.privileges.iter().enumerate() {
14122                if i > 0 {
14123                    self.write(", ");
14124                }
14125                self.write_keyword(priv_name);
14126            }
14127        }
14128
14129        Ok(())
14130    }
14131
14132    // ==================== End DDL Generation ====================
14133
14134    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
14135        use crate::dialects::DialectType;
14136        match lit {
14137            Literal::String(s) => {
14138                self.generate_string_literal(s)?;
14139            }
14140            Literal::Number(n) => {
14141                if matches!(self.config.dialect, Some(DialectType::MySQL))
14142                    && n.len() > 2
14143                    && (n.starts_with("0x") || n.starts_with("0X"))
14144                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
14145                {
14146                    return self.generate_identifier(&Identifier {
14147                        name: n.clone(),
14148                        quoted: true,
14149                        trailing_comments: Vec::new(),
14150                        span: None,
14151                    });
14152                }
14153                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
14154                // for dialects that don't support them (MySQL interprets as identifier).
14155                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
14156                let n = if n.contains('_')
14157                    && !matches!(
14158                        self.config.dialect,
14159                        Some(DialectType::ClickHouse)
14160                            | Some(DialectType::DuckDB)
14161                            | Some(DialectType::PostgreSQL)
14162                            | Some(DialectType::Hive)
14163                            | Some(DialectType::Spark)
14164                            | Some(DialectType::Databricks)
14165                    ) {
14166                    std::borrow::Cow::Owned(n.replace('_', ""))
14167                } else {
14168                    std::borrow::Cow::Borrowed(n.as_str())
14169                };
14170                // Normalize numbers starting with decimal point to have leading zero
14171                // e.g., .25 -> 0.25 (matches sqlglot behavior)
14172                if n.starts_with('.') {
14173                    self.write("0");
14174                    self.write(&n);
14175                } else if n.starts_with("-.") {
14176                    // Handle negative numbers like -.25 -> -0.25
14177                    self.write("-0");
14178                    self.write(&n[1..]);
14179                } else {
14180                    self.write(&n);
14181                }
14182            }
14183            Literal::HexString(h) => {
14184                // Most dialects use lowercase x'...' for hex literals; Spark/Databricks/Teradata use uppercase X'...'
14185                match self.config.dialect {
14186                    Some(DialectType::Spark)
14187                    | Some(DialectType::Databricks)
14188                    | Some(DialectType::Teradata) => self.write("X'"),
14189                    _ => self.write("x'"),
14190                }
14191                self.write(h);
14192                self.write("'");
14193            }
14194            Literal::HexNumber(h) => {
14195                // Hex number (0xA) - integer in hex notation (from BigQuery)
14196                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
14197                // For other dialects, convert to decimal integer
14198                match self.config.dialect {
14199                    Some(DialectType::BigQuery)
14200                    | Some(DialectType::TSQL)
14201                    | Some(DialectType::Fabric) => {
14202                        self.write("0x");
14203                        self.write(h);
14204                    }
14205                    _ => {
14206                        // Convert hex to decimal
14207                        if let Ok(val) = u64::from_str_radix(h, 16) {
14208                            self.write(&val.to_string());
14209                        } else {
14210                            // Fallback: keep as 0x notation
14211                            self.write("0x");
14212                            self.write(h);
14213                        }
14214                    }
14215                }
14216            }
14217            Literal::BitString(b) => {
14218                // Bit string B'0101...'
14219                self.write("B'");
14220                self.write(b);
14221                self.write("'");
14222            }
14223            Literal::ByteString(b) => {
14224                // Byte string b'...' (BigQuery style)
14225                self.write("b'");
14226                // Escape special characters for output
14227                self.write_escaped_byte_string(b);
14228                self.write("'");
14229            }
14230            Literal::NationalString(s) => {
14231                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
14232                // Other dialects strip the N prefix and output as regular string
14233                let keep_n_prefix = matches!(
14234                    self.config.dialect,
14235                    Some(DialectType::TSQL)
14236                        | Some(DialectType::Oracle)
14237                        | Some(DialectType::MySQL)
14238                        | None
14239                );
14240                if keep_n_prefix {
14241                    self.write("N'");
14242                } else {
14243                    self.write("'");
14244                }
14245                self.write(s);
14246                self.write("'");
14247            }
14248            Literal::Date(d) => {
14249                self.generate_date_literal(d)?;
14250            }
14251            Literal::Time(t) => {
14252                self.generate_time_literal(t)?;
14253            }
14254            Literal::Timestamp(ts) => {
14255                self.generate_timestamp_literal(ts)?;
14256            }
14257            Literal::Datetime(dt) => {
14258                self.generate_datetime_literal(dt)?;
14259            }
14260            Literal::TripleQuotedString(s, _quote_char) => {
14261                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
14262                if matches!(
14263                    self.config.dialect,
14264                    Some(crate::dialects::DialectType::BigQuery)
14265                        | Some(crate::dialects::DialectType::DuckDB)
14266                        | Some(crate::dialects::DialectType::Snowflake)
14267                        | Some(crate::dialects::DialectType::Spark)
14268                        | Some(crate::dialects::DialectType::Hive)
14269                        | Some(crate::dialects::DialectType::Presto)
14270                        | Some(crate::dialects::DialectType::Trino)
14271                        | Some(crate::dialects::DialectType::PostgreSQL)
14272                        | Some(crate::dialects::DialectType::MySQL)
14273                        | Some(crate::dialects::DialectType::Redshift)
14274                        | Some(crate::dialects::DialectType::TSQL)
14275                        | Some(crate::dialects::DialectType::Oracle)
14276                        | Some(crate::dialects::DialectType::ClickHouse)
14277                        | Some(crate::dialects::DialectType::Databricks)
14278                        | Some(crate::dialects::DialectType::SQLite)
14279                ) {
14280                    self.generate_string_literal(s)?;
14281                } else {
14282                    // Preserve triple-quoted string syntax for generic/unknown dialects
14283                    let quotes = format!("{0}{0}{0}", _quote_char);
14284                    self.write(&quotes);
14285                    self.write(s);
14286                    self.write(&quotes);
14287                }
14288            }
14289            Literal::EscapeString(s) => {
14290                // PostgreSQL escape string: e'...' or E'...'
14291                // Token text format is "e:content" or "E:content"
14292                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
14293                use crate::dialects::DialectType;
14294                let content = if let Some(c) = s.strip_prefix("e:") {
14295                    c
14296                } else if let Some(c) = s.strip_prefix("E:") {
14297                    c
14298                } else {
14299                    s.as_str()
14300                };
14301
14302                // MySQL: output the content without quotes or prefix
14303                if matches!(
14304                    self.config.dialect,
14305                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
14306                ) {
14307                    self.write(content);
14308                } else {
14309                    // Some dialects use lowercase e' prefix
14310                    let prefix = if matches!(
14311                        self.config.dialect,
14312                        Some(DialectType::SingleStore)
14313                            | Some(DialectType::DuckDB)
14314                            | Some(DialectType::PostgreSQL)
14315                            | Some(DialectType::CockroachDB)
14316                            | Some(DialectType::Materialize)
14317                            | Some(DialectType::RisingWave)
14318                    ) {
14319                        "e'"
14320                    } else {
14321                        "E'"
14322                    };
14323
14324                    // Normalize \' to '' for output
14325                    let normalized = content.replace("\\'", "''");
14326                    self.write(prefix);
14327                    self.write(&normalized);
14328                    self.write("'");
14329                }
14330            }
14331            Literal::DollarString(s) => {
14332                // Convert dollar-quoted strings to single-quoted strings
14333                // (like Python sqlglot's rawstring_sql)
14334                use crate::dialects::DialectType;
14335                // Extract content from tag\x00content format
14336                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
14337                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
14338                let escape_backslash = matches!(self.config.dialect, Some(DialectType::Snowflake));
14339                // Step 2: Determine quote escaping style
14340                // Snowflake: ' -> \' (backslash escape)
14341                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
14342                let use_backslash_quote =
14343                    matches!(self.config.dialect, Some(DialectType::Snowflake));
14344
14345                let mut escaped = String::with_capacity(content.len() + 4);
14346                for ch in content.chars() {
14347                    if escape_backslash && ch == '\\' {
14348                        // Escape backslash first (before quote escaping)
14349                        escaped.push('\\');
14350                        escaped.push('\\');
14351                    } else if ch == '\'' {
14352                        if use_backslash_quote {
14353                            escaped.push('\\');
14354                            escaped.push('\'');
14355                        } else {
14356                            escaped.push('\'');
14357                            escaped.push('\'');
14358                        }
14359                    } else {
14360                        escaped.push(ch);
14361                    }
14362                }
14363                self.write("'");
14364                self.write(&escaped);
14365                self.write("'");
14366            }
14367            Literal::RawString(s) => {
14368                // Raw strings (r"..." or r'...') contain literal backslashes.
14369                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
14370                // 1. If \\ is in STRING_ESCAPES, double all backslashes
14371                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
14372                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
14373                use crate::dialects::DialectType;
14374
14375                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
14376                let escape_backslash = matches!(
14377                    self.config.dialect,
14378                    Some(DialectType::BigQuery)
14379                        | Some(DialectType::MySQL)
14380                        | Some(DialectType::SingleStore)
14381                        | Some(DialectType::TiDB)
14382                        | Some(DialectType::Hive)
14383                        | Some(DialectType::Spark)
14384                        | Some(DialectType::Databricks)
14385                        | Some(DialectType::Drill)
14386                        | Some(DialectType::Snowflake)
14387                        | Some(DialectType::Redshift)
14388                        | Some(DialectType::ClickHouse)
14389                );
14390
14391                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
14392                // These escape quotes as \' instead of ''
14393                let backslash_escapes_quote = matches!(
14394                    self.config.dialect,
14395                    Some(DialectType::BigQuery)
14396                        | Some(DialectType::Hive)
14397                        | Some(DialectType::Spark)
14398                        | Some(DialectType::Databricks)
14399                        | Some(DialectType::Drill)
14400                        | Some(DialectType::Snowflake)
14401                        | Some(DialectType::Redshift)
14402                );
14403
14404                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
14405                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
14406                let supports_escape_sequences = escape_backslash;
14407
14408                let mut escaped = String::with_capacity(s.len() + 4);
14409                for ch in s.chars() {
14410                    if escape_backslash && ch == '\\' {
14411                        // Double the backslash for the target dialect
14412                        escaped.push('\\');
14413                        escaped.push('\\');
14414                    } else if ch == '\'' {
14415                        if backslash_escapes_quote {
14416                            // Use backslash to escape the quote: \'
14417                            escaped.push('\\');
14418                            escaped.push('\'');
14419                        } else {
14420                            // Use SQL standard quote doubling: ''
14421                            escaped.push('\'');
14422                            escaped.push('\'');
14423                        }
14424                    } else if supports_escape_sequences {
14425                        // Apply ESCAPED_SEQUENCES mapping for special chars
14426                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
14427                        match ch {
14428                            '\n' => {
14429                                escaped.push('\\');
14430                                escaped.push('n');
14431                            }
14432                            '\r' => {
14433                                escaped.push('\\');
14434                                escaped.push('r');
14435                            }
14436                            '\t' => {
14437                                escaped.push('\\');
14438                                escaped.push('t');
14439                            }
14440                            '\x07' => {
14441                                escaped.push('\\');
14442                                escaped.push('a');
14443                            }
14444                            '\x08' => {
14445                                escaped.push('\\');
14446                                escaped.push('b');
14447                            }
14448                            '\x0C' => {
14449                                escaped.push('\\');
14450                                escaped.push('f');
14451                            }
14452                            '\x0B' => {
14453                                escaped.push('\\');
14454                                escaped.push('v');
14455                            }
14456                            _ => escaped.push(ch),
14457                        }
14458                    } else {
14459                        escaped.push(ch);
14460                    }
14461                }
14462                self.write("'");
14463                self.write(&escaped);
14464                self.write("'");
14465            }
14466        }
14467        Ok(())
14468    }
14469
14470    /// Generate a DATE literal with dialect-specific formatting
14471    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
14472        use crate::dialects::DialectType;
14473
14474        match self.config.dialect {
14475            // SQL Server uses CONVERT or CAST
14476            Some(DialectType::TSQL) => {
14477                self.write("CAST('");
14478                self.write(d);
14479                self.write("' AS DATE)");
14480            }
14481            // BigQuery uses CAST syntax for type literals
14482            // DATE 'value' -> CAST('value' AS DATE)
14483            Some(DialectType::BigQuery) => {
14484                self.write("CAST('");
14485                self.write(d);
14486                self.write("' AS DATE)");
14487            }
14488            // Exasol uses CAST syntax for DATE literals
14489            // DATE 'value' -> CAST('value' AS DATE)
14490            Some(DialectType::Exasol) => {
14491                self.write("CAST('");
14492                self.write(d);
14493                self.write("' AS DATE)");
14494            }
14495            // Snowflake uses CAST syntax for DATE literals
14496            // DATE 'value' -> CAST('value' AS DATE)
14497            Some(DialectType::Snowflake) => {
14498                self.write("CAST('");
14499                self.write(d);
14500                self.write("' AS DATE)");
14501            }
14502            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
14503            Some(DialectType::PostgreSQL)
14504            | Some(DialectType::MySQL)
14505            | Some(DialectType::SingleStore)
14506            | Some(DialectType::TiDB)
14507            | Some(DialectType::Redshift) => {
14508                self.write("CAST('");
14509                self.write(d);
14510                self.write("' AS DATE)");
14511            }
14512            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
14513            Some(DialectType::DuckDB)
14514            | Some(DialectType::Presto)
14515            | Some(DialectType::Trino)
14516            | Some(DialectType::Athena)
14517            | Some(DialectType::Spark)
14518            | Some(DialectType::Databricks)
14519            | Some(DialectType::Hive) => {
14520                self.write("CAST('");
14521                self.write(d);
14522                self.write("' AS DATE)");
14523            }
14524            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
14525            Some(DialectType::Oracle) => {
14526                self.write("TO_DATE('");
14527                self.write(d);
14528                self.write("', 'YYYY-MM-DD')");
14529            }
14530            // Standard SQL: DATE '...'
14531            _ => {
14532                self.write_keyword("DATE");
14533                self.write(" '");
14534                self.write(d);
14535                self.write("'");
14536            }
14537        }
14538        Ok(())
14539    }
14540
14541    /// Generate a TIME literal with dialect-specific formatting
14542    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
14543        use crate::dialects::DialectType;
14544
14545        match self.config.dialect {
14546            // SQL Server uses CONVERT or CAST
14547            Some(DialectType::TSQL) => {
14548                self.write("CAST('");
14549                self.write(t);
14550                self.write("' AS TIME)");
14551            }
14552            // Standard SQL: TIME '...'
14553            _ => {
14554                self.write_keyword("TIME");
14555                self.write(" '");
14556                self.write(t);
14557                self.write("'");
14558            }
14559        }
14560        Ok(())
14561    }
14562
14563    /// Generate a date expression for Dremio, converting DATE literals to CAST
14564    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
14565        use crate::expressions::Literal;
14566
14567        match expr {
14568            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
14569                let Literal::Date(d) = lit.as_ref() else {
14570                    unreachable!()
14571                };
14572                // DATE 'value' -> CAST('value' AS DATE)
14573                self.write("CAST('");
14574                self.write(d);
14575                self.write("' AS DATE)");
14576            }
14577            _ => {
14578                // For all other expressions, generate normally
14579                self.generate_expression(expr)?;
14580            }
14581        }
14582        Ok(())
14583    }
14584
14585    /// Generate a TIMESTAMP literal with dialect-specific formatting
14586    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
14587        use crate::dialects::DialectType;
14588
14589        match self.config.dialect {
14590            // SQL Server uses CONVERT or CAST
14591            Some(DialectType::TSQL) => {
14592                self.write("CAST('");
14593                self.write(ts);
14594                self.write("' AS DATETIME2)");
14595            }
14596            // BigQuery uses CAST syntax for type literals
14597            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14598            Some(DialectType::BigQuery) => {
14599                self.write("CAST('");
14600                self.write(ts);
14601                self.write("' AS TIMESTAMP)");
14602            }
14603            // Snowflake uses CAST syntax for TIMESTAMP literals
14604            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14605            Some(DialectType::Snowflake) => {
14606                self.write("CAST('");
14607                self.write(ts);
14608                self.write("' AS TIMESTAMP)");
14609            }
14610            // Dremio uses CAST syntax for TIMESTAMP literals
14611            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14612            Some(DialectType::Dremio) => {
14613                self.write("CAST('");
14614                self.write(ts);
14615                self.write("' AS TIMESTAMP)");
14616            }
14617            // Exasol uses CAST syntax for TIMESTAMP literals
14618            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14619            Some(DialectType::Exasol) => {
14620                self.write("CAST('");
14621                self.write(ts);
14622                self.write("' AS TIMESTAMP)");
14623            }
14624            // Oracle prefers TO_TIMESTAMP function call
14625            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
14626            Some(DialectType::Oracle) => {
14627                self.write("TO_TIMESTAMP('");
14628                self.write(ts);
14629                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
14630            }
14631            // Presto/Trino: always use CAST for TIMESTAMP literals
14632            Some(DialectType::Presto) | Some(DialectType::Trino) => {
14633                if Self::timestamp_has_timezone(ts) {
14634                    self.write("CAST('");
14635                    self.write(ts);
14636                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
14637                } else {
14638                    self.write("CAST('");
14639                    self.write(ts);
14640                    self.write("' AS TIMESTAMP)");
14641                }
14642            }
14643            // ClickHouse: CAST('...' AS Nullable(DateTime))
14644            Some(DialectType::ClickHouse) => {
14645                self.write("CAST('");
14646                self.write(ts);
14647                self.write("' AS Nullable(DateTime))");
14648            }
14649            // Spark: CAST('...' AS TIMESTAMP)
14650            Some(DialectType::Spark) => {
14651                self.write("CAST('");
14652                self.write(ts);
14653                self.write("' AS TIMESTAMP)");
14654            }
14655            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
14656            // but TIMESTAMP '...' for special values like 'epoch'
14657            Some(DialectType::Redshift) => {
14658                if ts == "epoch" {
14659                    self.write_keyword("TIMESTAMP");
14660                    self.write(" '");
14661                    self.write(ts);
14662                    self.write("'");
14663                } else {
14664                    self.write("CAST('");
14665                    self.write(ts);
14666                    self.write("' AS TIMESTAMP)");
14667                }
14668            }
14669            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
14670            Some(DialectType::PostgreSQL)
14671            | Some(DialectType::Hive)
14672            | Some(DialectType::SQLite)
14673            | Some(DialectType::DuckDB)
14674            | Some(DialectType::Athena)
14675            | Some(DialectType::Drill)
14676            | Some(DialectType::Teradata) => {
14677                self.write("CAST('");
14678                self.write(ts);
14679                self.write("' AS TIMESTAMP)");
14680            }
14681            // MySQL/StarRocks: CAST('...' AS DATETIME)
14682            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
14683                self.write("CAST('");
14684                self.write(ts);
14685                self.write("' AS DATETIME)");
14686            }
14687            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
14688            Some(DialectType::Databricks) => {
14689                self.write("CAST('");
14690                self.write(ts);
14691                self.write("' AS TIMESTAMP_NTZ)");
14692            }
14693            // Standard SQL: TIMESTAMP '...'
14694            _ => {
14695                self.write_keyword("TIMESTAMP");
14696                self.write(" '");
14697                self.write(ts);
14698                self.write("'");
14699            }
14700        }
14701        Ok(())
14702    }
14703
14704    /// Check if a timestamp string contains a timezone identifier
14705    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
14706    fn timestamp_has_timezone(ts: &str) -> bool {
14707        // Check for common IANA timezone patterns: Continent/City format
14708        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
14709        // Also handles: UTC, GMT, Etc/GMT+0, etc.
14710        let ts_lower = ts.to_ascii_lowercase();
14711
14712        // Check for Continent/City pattern (most common)
14713        let continent_prefixes = [
14714            "africa/",
14715            "america/",
14716            "antarctica/",
14717            "arctic/",
14718            "asia/",
14719            "atlantic/",
14720            "australia/",
14721            "europe/",
14722            "indian/",
14723            "pacific/",
14724            "etc/",
14725            "brazil/",
14726            "canada/",
14727            "chile/",
14728            "mexico/",
14729            "us/",
14730        ];
14731
14732        for prefix in &continent_prefixes {
14733            if ts_lower.contains(prefix) {
14734                return true;
14735            }
14736        }
14737
14738        // Check for standalone timezone abbreviations at the end
14739        // These typically appear after the time portion
14740        let tz_abbrevs = [
14741            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
14742            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
14743            " sgt", " aest", " aedt", " acst", " acdt", " awst",
14744        ];
14745
14746        for abbrev in &tz_abbrevs {
14747            if ts_lower.ends_with(abbrev) {
14748                return true;
14749            }
14750        }
14751
14752        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
14753        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
14754        // Look for pattern: space followed by + or - and digits (optionally with :)
14755        let trimmed = ts.trim();
14756        if let Some(last_space) = trimmed.rfind(' ') {
14757            let suffix = &trimmed[last_space + 1..];
14758            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
14759                // Check if rest is numeric (possibly with : for hh:mm format)
14760                let rest = &suffix[1..];
14761                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
14762                    return true;
14763                }
14764            }
14765        }
14766
14767        false
14768    }
14769
14770    /// Generate a DATETIME literal with dialect-specific formatting
14771    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
14772        use crate::dialects::DialectType;
14773
14774        match self.config.dialect {
14775            // BigQuery uses CAST syntax for type literals
14776            // DATETIME 'value' -> CAST('value' AS DATETIME)
14777            Some(DialectType::BigQuery) => {
14778                self.write("CAST('");
14779                self.write(dt);
14780                self.write("' AS DATETIME)");
14781            }
14782            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
14783            Some(DialectType::DuckDB) => {
14784                self.write("CAST('");
14785                self.write(dt);
14786                self.write("' AS TIMESTAMP)");
14787            }
14788            // DATETIME is primarily a BigQuery type
14789            // Output as DATETIME '...' for dialects that support it
14790            _ => {
14791                self.write_keyword("DATETIME");
14792                self.write(" '");
14793                self.write(dt);
14794                self.write("'");
14795            }
14796        }
14797        Ok(())
14798    }
14799
14800    /// Generate a string literal with dialect-specific escaping
14801    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
14802        use crate::dialects::DialectType;
14803
14804        match self.config.dialect {
14805            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
14806            // and backslash escaping for special characters like newlines
14807            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
14808            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
14809                // Hive/Spark use backslash escaping for quotes (\') and special chars
14810                self.write("'");
14811                for c in s.chars() {
14812                    match c {
14813                        '\'' => self.write("\\'"),
14814                        '\\' => self.write("\\\\"),
14815                        '\n' => self.write("\\n"),
14816                        '\r' => self.write("\\r"),
14817                        '\t' => self.write("\\t"),
14818                        '\0' => self.write("\\0"),
14819                        _ => self.output.push(c),
14820                    }
14821                }
14822                self.write("'");
14823            }
14824            Some(DialectType::Drill) => {
14825                // Drill uses SQL-standard quote doubling ('') for quotes,
14826                // but backslash escaping for special characters
14827                self.write("'");
14828                for c in s.chars() {
14829                    match c {
14830                        '\'' => self.write("''"),
14831                        '\\' => self.write("\\\\"),
14832                        '\n' => self.write("\\n"),
14833                        '\r' => self.write("\\r"),
14834                        '\t' => self.write("\\t"),
14835                        '\0' => self.write("\\0"),
14836                        _ => self.output.push(c),
14837                    }
14838                }
14839                self.write("'");
14840            }
14841            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
14842                self.write("'");
14843                for c in s.chars() {
14844                    match c {
14845                        // MySQL uses SQL standard quote doubling
14846                        '\'' => self.write("''"),
14847                        '\\' => self.write("\\\\"),
14848                        '\n' => self.write("\\n"),
14849                        '\r' => self.write("\\r"),
14850                        '\t' => self.write("\\t"),
14851                        // sqlglot writes a literal NUL for this case
14852                        '\0' => self.output.push('\0'),
14853                        _ => self.output.push(c),
14854                    }
14855                }
14856                self.write("'");
14857            }
14858            // BigQuery: Uses backslash escaping
14859            Some(DialectType::BigQuery) => {
14860                self.write("'");
14861                for c in s.chars() {
14862                    match c {
14863                        '\'' => self.write("\\'"),
14864                        '\\' => self.write("\\\\"),
14865                        '\n' => self.write("\\n"),
14866                        '\r' => self.write("\\r"),
14867                        '\t' => self.write("\\t"),
14868                        '\0' => self.write("\\0"),
14869                        '\x07' => self.write("\\a"),
14870                        '\x08' => self.write("\\b"),
14871                        '\x0C' => self.write("\\f"),
14872                        '\x0B' => self.write("\\v"),
14873                        _ => self.output.push(c),
14874                    }
14875                }
14876                self.write("'");
14877            }
14878            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
14879            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
14880            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
14881            Some(DialectType::Athena) => {
14882                if self.athena_hive_context {
14883                    // Hive-style: backslash escaping
14884                    self.write("'");
14885                    for c in s.chars() {
14886                        match c {
14887                            '\'' => self.write("\\'"),
14888                            '\\' => self.write("\\\\"),
14889                            '\n' => self.write("\\n"),
14890                            '\r' => self.write("\\r"),
14891                            '\t' => self.write("\\t"),
14892                            '\0' => self.write("\\0"),
14893                            _ => self.output.push(c),
14894                        }
14895                    }
14896                    self.write("'");
14897                } else {
14898                    // Trino-style: SQL-standard escaping, preserve backslashes
14899                    self.write("'");
14900                    for c in s.chars() {
14901                        match c {
14902                            '\'' => self.write("''"),
14903                            // Preserve backslashes literally (no re-escaping)
14904                            _ => self.output.push(c),
14905                        }
14906                    }
14907                    self.write("'");
14908                }
14909            }
14910            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
14911            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
14912            // becomes string value '\\'), so we should NOT re-escape backslashes.
14913            // We only need to escape single quotes.
14914            Some(DialectType::Snowflake) => {
14915                self.write("'");
14916                for c in s.chars() {
14917                    match c {
14918                        '\'' => self.write("\\'"),
14919                        // Backslashes are already escaped in the tokenized string, don't re-escape
14920                        // Only escape special characters that might not have been escaped
14921                        '\n' => self.write("\\n"),
14922                        '\r' => self.write("\\r"),
14923                        '\t' => self.write("\\t"),
14924                        _ => self.output.push(c),
14925                    }
14926                }
14927                self.write("'");
14928            }
14929            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
14930            Some(DialectType::PostgreSQL) => {
14931                self.write("'");
14932                for c in s.chars() {
14933                    match c {
14934                        '\'' => self.write("''"),
14935                        _ => self.output.push(c),
14936                    }
14937                }
14938                self.write("'");
14939            }
14940            // Redshift: Uses backslash escaping for single quotes
14941            Some(DialectType::Redshift) => {
14942                self.write("'");
14943                for c in s.chars() {
14944                    match c {
14945                        '\'' => self.write("\\'"),
14946                        _ => self.output.push(c),
14947                    }
14948                }
14949                self.write("'");
14950            }
14951            // Oracle: Uses standard double single-quote escaping
14952            Some(DialectType::Oracle) => {
14953                self.write("'");
14954                for ch in s.chars() {
14955                    if ch == '\'' {
14956                        self.output.push_str("''");
14957                    } else {
14958                        self.output.push(ch);
14959                    }
14960                }
14961                self.write("'");
14962            }
14963            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
14964            // backslash escaping for backslashes and special characters
14965            Some(DialectType::ClickHouse) => {
14966                self.write("'");
14967                for c in s.chars() {
14968                    match c {
14969                        '\'' => self.write("''"),
14970                        '\\' => self.write("\\\\"),
14971                        '\n' => self.write("\\n"),
14972                        '\r' => self.write("\\r"),
14973                        '\t' => self.write("\\t"),
14974                        '\0' => self.write("\\0"),
14975                        '\x07' => self.write("\\a"),
14976                        '\x08' => self.write("\\b"),
14977                        '\x0C' => self.write("\\f"),
14978                        '\x0B' => self.write("\\v"),
14979                        // Non-printable characters: emit as \xNN hex escapes
14980                        c if c.is_control() || (c as u32) < 0x20 => {
14981                            let byte = c as u32;
14982                            if byte < 256 {
14983                                self.write(&format!("\\x{:02X}", byte));
14984                            } else {
14985                                self.output.push(c);
14986                            }
14987                        }
14988                        _ => self.output.push(c),
14989                    }
14990                }
14991                self.write("'");
14992            }
14993            // Default: SQL standard double single quotes (works for most dialects)
14994            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
14995            _ => {
14996                self.write("'");
14997                for ch in s.chars() {
14998                    if ch == '\'' {
14999                        self.output.push_str("''");
15000                    } else {
15001                        self.output.push(ch);
15002                    }
15003                }
15004                self.write("'");
15005            }
15006        }
15007        Ok(())
15008    }
15009
15010    /// Write a byte string with proper escaping for BigQuery-style byte literals
15011    /// Escapes characters as \xNN hex escapes where needed
15012    fn write_escaped_byte_string(&mut self, s: &str) {
15013        for c in s.chars() {
15014            match c {
15015                // Escape single quotes
15016                '\'' => self.write("\\'"),
15017                // Escape backslashes
15018                '\\' => self.write("\\\\"),
15019                // Keep all printable characters (including non-ASCII) as-is
15020                _ if !c.is_control() => self.output.push(c),
15021                // Escape control characters as hex
15022                _ => {
15023                    let byte = c as u32;
15024                    if byte < 256 {
15025                        self.write(&format!("\\x{:02x}", byte));
15026                    } else {
15027                        // For unicode characters, write each UTF-8 byte
15028                        for b in c.to_string().as_bytes() {
15029                            self.write(&format!("\\x{:02x}", b));
15030                        }
15031                    }
15032                }
15033            }
15034        }
15035    }
15036
15037    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
15038        use crate::dialects::DialectType;
15039
15040        // Different dialects have different boolean literal formats
15041        match self.config.dialect {
15042            // SQL Server typically uses 1/0 for boolean literals in many contexts
15043            // However, TRUE/FALSE also works in modern versions
15044            Some(DialectType::TSQL) => {
15045                self.write(if b.value { "1" } else { "0" });
15046            }
15047            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
15048            Some(DialectType::Oracle) => {
15049                self.write(if b.value { "1" } else { "0" });
15050            }
15051            // MySQL accepts TRUE/FALSE as aliases for 1/0
15052            Some(DialectType::MySQL) => {
15053                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
15054            }
15055            // Most other dialects support TRUE/FALSE
15056            _ => {
15057                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
15058            }
15059        }
15060        Ok(())
15061    }
15062
15063    /// Generate an identifier that's used as an alias name
15064    /// This quotes reserved keywords in addition to already-quoted identifiers
15065    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
15066        let name = &id.name;
15067        let quote_style = &self.config.identifier_quote_style;
15068
15069        // For aliases, quote if:
15070        // 1. The identifier was explicitly quoted in the source
15071        // 2. The identifier is a reserved keyword for the current dialect
15072        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
15073
15074        // Normalize identifier if configured
15075        let output_name = if self.config.normalize_identifiers && !id.quoted {
15076            name.to_ascii_lowercase()
15077        } else {
15078            name.to_string()
15079        };
15080
15081        if needs_quoting {
15082            // Escape any quote characters within the identifier
15083            let escaped_name = if quote_style.start == quote_style.end {
15084                output_name.replace(
15085                    quote_style.end,
15086                    &format!("{}{}", quote_style.end, quote_style.end),
15087                )
15088            } else {
15089                output_name.replace(
15090                    quote_style.end,
15091                    &format!("{}{}", quote_style.end, quote_style.end),
15092                )
15093            };
15094            self.write(&format!(
15095                "{}{}{}",
15096                quote_style.start, escaped_name, quote_style.end
15097            ));
15098        } else {
15099            self.write(&output_name);
15100        }
15101
15102        // Output trailing comments
15103        for comment in &id.trailing_comments {
15104            self.write(" ");
15105            self.write_formatted_comment(comment);
15106        }
15107        Ok(())
15108    }
15109
15110    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
15111        use crate::dialects::DialectType;
15112
15113        let name = &id.name;
15114
15115        // For Athena, use backticks in Hive context, double quotes in Trino context
15116        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
15117            && self.athena_hive_context
15118        {
15119            &IdentifierQuoteStyle::BACKTICK
15120        } else {
15121            &self.config.identifier_quote_style
15122        };
15123
15124        // Quote if:
15125        // 1. The identifier was explicitly quoted in the source
15126        // 2. The identifier is a reserved keyword for the current dialect
15127        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
15128        // This matches Python sqlglot's identifier_sql behavior
15129        // Also quote identifiers starting with digits if the target dialect doesn't support them
15130        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
15131        let needs_digit_quoting = starts_with_digit
15132            && !self.config.identifiers_can_start_with_digit
15133            && self.config.dialect.is_some();
15134        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
15135            && name.len() > 2
15136            && (name.starts_with("0x") || name.starts_with("0X"))
15137            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
15138        let needs_quoting = id.quoted
15139            || self.is_reserved_keyword(name)
15140            || self.config.always_quote_identifiers
15141            || needs_digit_quoting
15142            || mysql_invalid_hex_identifier;
15143
15144        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
15145        // When quoted, we need to output `name`(16) not `name(16)`
15146        let (base_name, suffix) = if needs_quoting {
15147            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
15148            if let Some(paren_pos) = name.find('(') {
15149                let base = &name[..paren_pos];
15150                let rest = &name[paren_pos..];
15151                // Verify it looks like (digits) or (digits) ASC/DESC
15152                if rest.starts_with('(')
15153                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
15154                {
15155                    // Check if content between parens is all digits
15156                    let close_paren = rest.find(')').unwrap_or(rest.len());
15157                    let inside = &rest[1..close_paren];
15158                    if inside.chars().all(|c| c.is_ascii_digit()) {
15159                        (base.to_string(), rest.to_string())
15160                    } else {
15161                        (name.to_string(), String::new())
15162                    }
15163                } else {
15164                    (name.to_string(), String::new())
15165                }
15166            } else if name.ends_with(" ASC") {
15167                let base = &name[..name.len() - 4];
15168                (base.to_string(), " ASC".to_string())
15169            } else if name.ends_with(" DESC") {
15170                let base = &name[..name.len() - 5];
15171                (base.to_string(), " DESC".to_string())
15172            } else {
15173                (name.to_string(), String::new())
15174            }
15175        } else {
15176            (name.to_string(), String::new())
15177        };
15178
15179        // Normalize identifier if configured, with special handling for Exasol
15180        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
15181        // should be uppercased when not already quoted (to match Python sqlglot behavior)
15182        let output_name = if self.config.normalize_identifiers && !id.quoted {
15183            base_name.to_ascii_lowercase()
15184        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
15185            && !id.quoted
15186            && self.is_reserved_keyword(name)
15187        {
15188            // Exasol: uppercase reserved keywords when quoting them
15189            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
15190            base_name.to_ascii_uppercase()
15191        } else {
15192            base_name
15193        };
15194
15195        if needs_quoting {
15196            // Escape any quote characters within the identifier
15197            let escaped_name = if quote_style.start == quote_style.end {
15198                // Same start/end char (e.g., " or `) - double the quote char
15199                output_name.replace(
15200                    quote_style.end,
15201                    &format!("{}{}", quote_style.end, quote_style.end),
15202                )
15203            } else {
15204                // Different start/end (e.g., [ and ]) - escape only the end char
15205                output_name.replace(
15206                    quote_style.end,
15207                    &format!("{}{}", quote_style.end, quote_style.end),
15208                )
15209            };
15210            self.write(&format!(
15211                "{}{}{}{}",
15212                quote_style.start, escaped_name, quote_style.end, suffix
15213            ));
15214        } else {
15215            self.write(&output_name);
15216        }
15217
15218        // Output trailing comments
15219        for comment in &id.trailing_comments {
15220            self.write(" ");
15221            self.write_formatted_comment(comment);
15222        }
15223        Ok(())
15224    }
15225
15226    fn generate_column(&mut self, col: &Column) -> Result<()> {
15227        use crate::dialects::DialectType;
15228
15229        if let Some(table) = &col.table {
15230            // Exasol special case: LOCAL as column table prefix should NOT be quoted
15231            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
15232            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
15233            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
15234                && !table.quoted
15235                && table.name.eq_ignore_ascii_case("LOCAL");
15236
15237            if is_exasol_local_prefix {
15238                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
15239                self.write("LOCAL");
15240            } else {
15241                self.generate_identifier(table)?;
15242            }
15243            self.write(".");
15244        }
15245        self.generate_identifier(&col.name)?;
15246        // Oracle-style join marker (+)
15247        // Only output if dialect supports it (Oracle, Exasol)
15248        if col.join_mark && self.config.supports_column_join_marks {
15249            self.write(" (+)");
15250        }
15251        // Output trailing comments
15252        for comment in &col.trailing_comments {
15253            self.write_space();
15254            self.write_formatted_comment(comment);
15255        }
15256        Ok(())
15257    }
15258
15259    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
15260    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
15261    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
15262        use crate::dialects::DialectType;
15263        use crate::expressions::PseudocolumnType;
15264
15265        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
15266        if pc.kind == PseudocolumnType::Sysdate
15267            && !matches!(
15268                self.config.dialect,
15269                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
15270            )
15271        {
15272            self.write_keyword("CURRENT_TIMESTAMP");
15273            // Add () for dialects that expect it
15274            if matches!(
15275                self.config.dialect,
15276                Some(DialectType::MySQL)
15277                    | Some(DialectType::ClickHouse)
15278                    | Some(DialectType::Spark)
15279                    | Some(DialectType::Databricks)
15280                    | Some(DialectType::Hive)
15281            ) {
15282                self.write("()");
15283            }
15284        } else {
15285            self.write(pc.kind.as_str());
15286        }
15287        Ok(())
15288    }
15289
15290    /// Generate CONNECT BY clause (Oracle hierarchical queries)
15291    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
15292        use crate::dialects::DialectType;
15293
15294        // Generate native CONNECT BY for Oracle and Snowflake
15295        // For other dialects, add a comment noting manual conversion needed
15296        let supports_connect_by = matches!(
15297            self.config.dialect,
15298            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
15299        );
15300
15301        if !supports_connect_by && self.config.dialect.is_some() {
15302            // Add comment for unsupported dialects
15303            if self.config.pretty {
15304                self.write_newline();
15305            } else {
15306                self.write_space();
15307            }
15308            self.write_unsupported_comment(
15309                "CONNECT BY requires manual conversion to recursive CTE",
15310            )?;
15311        }
15312
15313        // Generate START WITH if present (before CONNECT BY)
15314        if let Some(start) = &connect.start {
15315            if self.config.pretty {
15316                self.write_newline();
15317            } else {
15318                self.write_space();
15319            }
15320            self.write_keyword("START WITH");
15321            self.write_space();
15322            self.generate_expression(start)?;
15323        }
15324
15325        // Generate CONNECT BY
15326        if self.config.pretty {
15327            self.write_newline();
15328        } else {
15329            self.write_space();
15330        }
15331        self.write_keyword("CONNECT BY");
15332        if connect.nocycle {
15333            self.write_space();
15334            self.write_keyword("NOCYCLE");
15335        }
15336        self.write_space();
15337        self.generate_expression(&connect.connect)?;
15338
15339        Ok(())
15340    }
15341
15342    /// Generate Connect expression (for Expression::Connect variant)
15343    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
15344        self.generate_connect(connect)
15345    }
15346
15347    /// Generate PRIOR expression
15348    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
15349        self.write_keyword("PRIOR");
15350        self.write_space();
15351        self.generate_expression(&prior.this)?;
15352        Ok(())
15353    }
15354
15355    /// Generate CONNECT_BY_ROOT function
15356    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
15357    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
15358        self.write_keyword("CONNECT_BY_ROOT");
15359        self.write_space();
15360        self.generate_expression(&cbr.this)?;
15361        Ok(())
15362    }
15363
15364    /// Generate MATCH_RECOGNIZE clause
15365    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
15366        use crate::dialects::DialectType;
15367
15368        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
15369        let supports_match_recognize = matches!(
15370            self.config.dialect,
15371            Some(DialectType::Oracle)
15372                | Some(DialectType::Snowflake)
15373                | Some(DialectType::Presto)
15374                | Some(DialectType::Trino)
15375        );
15376
15377        // Generate the source table first
15378        if let Some(source) = &mr.this {
15379            self.generate_expression(source)?;
15380        }
15381
15382        if !supports_match_recognize {
15383            self.write_unsupported_comment("MATCH_RECOGNIZE not supported in this dialect")?;
15384            return Ok(());
15385        }
15386
15387        // In pretty mode, MATCH_RECOGNIZE should be on a new line
15388        if self.config.pretty {
15389            self.write_newline();
15390        } else {
15391            self.write_space();
15392        }
15393
15394        self.write_keyword("MATCH_RECOGNIZE");
15395        self.write(" (");
15396
15397        if self.config.pretty {
15398            self.indent_level += 1;
15399        }
15400
15401        let mut needs_separator = false;
15402
15403        // PARTITION BY
15404        if let Some(partition_by) = &mr.partition_by {
15405            if !partition_by.is_empty() {
15406                if self.config.pretty {
15407                    self.write_newline();
15408                    self.write_indent();
15409                }
15410                self.write_keyword("PARTITION BY");
15411                self.write_space();
15412                for (i, expr) in partition_by.iter().enumerate() {
15413                    if i > 0 {
15414                        self.write(", ");
15415                    }
15416                    self.generate_expression(expr)?;
15417                }
15418                needs_separator = true;
15419            }
15420        }
15421
15422        // ORDER BY
15423        if let Some(order_by) = &mr.order_by {
15424            if !order_by.is_empty() {
15425                if needs_separator {
15426                    if self.config.pretty {
15427                        self.write_newline();
15428                        self.write_indent();
15429                    } else {
15430                        self.write_space();
15431                    }
15432                } else if self.config.pretty {
15433                    self.write_newline();
15434                    self.write_indent();
15435                }
15436                self.write_keyword("ORDER BY");
15437                // In pretty mode, put each ORDER BY column on a new indented line
15438                if self.config.pretty {
15439                    self.indent_level += 1;
15440                    for (i, ordered) in order_by.iter().enumerate() {
15441                        if i > 0 {
15442                            self.write(",");
15443                        }
15444                        self.write_newline();
15445                        self.write_indent();
15446                        self.generate_ordered(ordered)?;
15447                    }
15448                    self.indent_level -= 1;
15449                } else {
15450                    self.write_space();
15451                    for (i, ordered) in order_by.iter().enumerate() {
15452                        if i > 0 {
15453                            self.write(", ");
15454                        }
15455                        self.generate_ordered(ordered)?;
15456                    }
15457                }
15458                needs_separator = true;
15459            }
15460        }
15461
15462        // MEASURES
15463        if let Some(measures) = &mr.measures {
15464            if !measures.is_empty() {
15465                if needs_separator {
15466                    if self.config.pretty {
15467                        self.write_newline();
15468                        self.write_indent();
15469                    } else {
15470                        self.write_space();
15471                    }
15472                } else if self.config.pretty {
15473                    self.write_newline();
15474                    self.write_indent();
15475                }
15476                self.write_keyword("MEASURES");
15477                // In pretty mode, put each MEASURE on a new indented line
15478                if self.config.pretty {
15479                    self.indent_level += 1;
15480                    for (i, measure) in measures.iter().enumerate() {
15481                        if i > 0 {
15482                            self.write(",");
15483                        }
15484                        self.write_newline();
15485                        self.write_indent();
15486                        // Handle RUNNING/FINAL prefix
15487                        if let Some(semantics) = &measure.window_frame {
15488                            match semantics {
15489                                MatchRecognizeSemantics::Running => {
15490                                    self.write_keyword("RUNNING");
15491                                    self.write_space();
15492                                }
15493                                MatchRecognizeSemantics::Final => {
15494                                    self.write_keyword("FINAL");
15495                                    self.write_space();
15496                                }
15497                            }
15498                        }
15499                        self.generate_expression(&measure.this)?;
15500                    }
15501                    self.indent_level -= 1;
15502                } else {
15503                    self.write_space();
15504                    for (i, measure) in measures.iter().enumerate() {
15505                        if i > 0 {
15506                            self.write(", ");
15507                        }
15508                        // Handle RUNNING/FINAL prefix
15509                        if let Some(semantics) = &measure.window_frame {
15510                            match semantics {
15511                                MatchRecognizeSemantics::Running => {
15512                                    self.write_keyword("RUNNING");
15513                                    self.write_space();
15514                                }
15515                                MatchRecognizeSemantics::Final => {
15516                                    self.write_keyword("FINAL");
15517                                    self.write_space();
15518                                }
15519                            }
15520                        }
15521                        self.generate_expression(&measure.this)?;
15522                    }
15523                }
15524                needs_separator = true;
15525            }
15526        }
15527
15528        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
15529        if let Some(rows) = &mr.rows {
15530            if needs_separator {
15531                if self.config.pretty {
15532                    self.write_newline();
15533                    self.write_indent();
15534                } else {
15535                    self.write_space();
15536                }
15537            } else if self.config.pretty {
15538                self.write_newline();
15539                self.write_indent();
15540            }
15541            match rows {
15542                MatchRecognizeRows::OneRowPerMatch => {
15543                    self.write_keyword("ONE ROW PER MATCH");
15544                }
15545                MatchRecognizeRows::AllRowsPerMatch => {
15546                    self.write_keyword("ALL ROWS PER MATCH");
15547                }
15548                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
15549                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
15550                }
15551                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
15552                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
15553                }
15554                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
15555                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
15556                }
15557            }
15558            needs_separator = true;
15559        }
15560
15561        // AFTER MATCH SKIP
15562        if let Some(after) = &mr.after {
15563            if needs_separator {
15564                if self.config.pretty {
15565                    self.write_newline();
15566                    self.write_indent();
15567                } else {
15568                    self.write_space();
15569                }
15570            } else if self.config.pretty {
15571                self.write_newline();
15572                self.write_indent();
15573            }
15574            match after {
15575                MatchRecognizeAfter::PastLastRow => {
15576                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
15577                }
15578                MatchRecognizeAfter::ToNextRow => {
15579                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
15580                }
15581                MatchRecognizeAfter::ToFirst(ident) => {
15582                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
15583                    self.write_space();
15584                    self.generate_identifier(ident)?;
15585                }
15586                MatchRecognizeAfter::ToLast(ident) => {
15587                    self.write_keyword("AFTER MATCH SKIP TO LAST");
15588                    self.write_space();
15589                    self.generate_identifier(ident)?;
15590                }
15591            }
15592            needs_separator = true;
15593        }
15594
15595        // PATTERN
15596        if let Some(pattern) = &mr.pattern {
15597            if needs_separator {
15598                if self.config.pretty {
15599                    self.write_newline();
15600                    self.write_indent();
15601                } else {
15602                    self.write_space();
15603                }
15604            } else if self.config.pretty {
15605                self.write_newline();
15606                self.write_indent();
15607            }
15608            self.write_keyword("PATTERN");
15609            self.write_space();
15610            self.write("(");
15611            self.write(pattern);
15612            self.write(")");
15613            needs_separator = true;
15614        }
15615
15616        // DEFINE
15617        if let Some(define) = &mr.define {
15618            if !define.is_empty() {
15619                if needs_separator {
15620                    if self.config.pretty {
15621                        self.write_newline();
15622                        self.write_indent();
15623                    } else {
15624                        self.write_space();
15625                    }
15626                } else if self.config.pretty {
15627                    self.write_newline();
15628                    self.write_indent();
15629                }
15630                self.write_keyword("DEFINE");
15631                // In pretty mode, put each DEFINE on a new indented line
15632                if self.config.pretty {
15633                    self.indent_level += 1;
15634                    for (i, (name, expr)) in define.iter().enumerate() {
15635                        if i > 0 {
15636                            self.write(",");
15637                        }
15638                        self.write_newline();
15639                        self.write_indent();
15640                        self.generate_identifier(name)?;
15641                        self.write(" AS ");
15642                        self.generate_expression(expr)?;
15643                    }
15644                    self.indent_level -= 1;
15645                } else {
15646                    self.write_space();
15647                    for (i, (name, expr)) in define.iter().enumerate() {
15648                        if i > 0 {
15649                            self.write(", ");
15650                        }
15651                        self.generate_identifier(name)?;
15652                        self.write(" AS ");
15653                        self.generate_expression(expr)?;
15654                    }
15655                }
15656            }
15657        }
15658
15659        if self.config.pretty {
15660            self.indent_level -= 1;
15661            self.write_newline();
15662        }
15663        self.write(")");
15664
15665        // Alias - only include AS if it was explicitly present in the input
15666        if let Some(alias) = &mr.alias {
15667            self.write(" ");
15668            if mr.alias_explicit_as {
15669                self.write_keyword("AS");
15670                self.write(" ");
15671            }
15672            self.generate_identifier(alias)?;
15673        }
15674
15675        Ok(())
15676    }
15677
15678    /// Generate a query hint /*+ ... */
15679    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
15680        use crate::dialects::DialectType;
15681
15682        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
15683        let supports_hints = matches!(
15684            self.config.dialect,
15685            None |  // No dialect = preserve everything
15686            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
15687            Some(DialectType::Spark) | Some(DialectType::Hive) |
15688            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
15689        );
15690
15691        if !supports_hints || hint.expressions.is_empty() {
15692            return Ok(());
15693        }
15694
15695        // First, expand raw hint text into individual hint strings
15696        // This handles the case where the parser stored multiple hints as a single raw string
15697        let mut hint_strings: Vec<String> = Vec::new();
15698        for expr in &hint.expressions {
15699            match expr {
15700                HintExpression::Raw(text) => {
15701                    // Parse raw hint text into individual hint function calls
15702                    let parsed = self.parse_raw_hint_text(text);
15703                    hint_strings.extend(parsed);
15704                }
15705                _ => {
15706                    hint_strings.push(self.hint_expression_to_string(expr)?);
15707                }
15708            }
15709        }
15710
15711        // In pretty mode with multiple hints, always use multiline format
15712        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
15713        // always joins with newlines in pretty mode
15714        let use_multiline = self.config.pretty && hint_strings.len() > 1;
15715
15716        if use_multiline {
15717            // Pretty print with each hint on its own line
15718            self.write(" /*+ ");
15719            for (i, hint_str) in hint_strings.iter().enumerate() {
15720                if i > 0 {
15721                    self.write_newline();
15722                    self.write("  "); // 2-space indent within hint block
15723                }
15724                self.write(hint_str);
15725            }
15726            self.write(" */");
15727        } else {
15728            // Single line format
15729            self.write(" /*+ ");
15730            let sep = match self.config.dialect {
15731                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
15732                _ => " ",
15733            };
15734            for (i, hint_str) in hint_strings.iter().enumerate() {
15735                if i > 0 {
15736                    self.write(sep);
15737                }
15738                self.write(hint_str);
15739            }
15740            self.write(" */");
15741        }
15742
15743        Ok(())
15744    }
15745
15746    /// Parse raw hint text into individual hint function calls
15747    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
15748    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
15749    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
15750        let mut results = Vec::new();
15751        let mut chars = text.chars().peekable();
15752        let mut current = String::new();
15753        let mut paren_depth = 0;
15754        let mut has_unparseable_content = false;
15755        let mut position_after_last_function = 0;
15756        let mut char_position = 0;
15757
15758        while let Some(c) = chars.next() {
15759            char_position += c.len_utf8();
15760            match c {
15761                '(' => {
15762                    paren_depth += 1;
15763                    current.push(c);
15764                }
15765                ')' => {
15766                    paren_depth -= 1;
15767                    current.push(c);
15768                    // When we close the outer parenthesis, we've completed a hint function
15769                    if paren_depth == 0 {
15770                        let trimmed = current.trim().to_string();
15771                        if !trimmed.is_empty() {
15772                            // Format this hint for pretty printing if needed
15773                            let formatted = self.format_hint_function(&trimmed);
15774                            results.push(formatted);
15775                        }
15776                        current.clear();
15777                        position_after_last_function = char_position;
15778                    }
15779                }
15780                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
15781                    // Space/comma/whitespace outside parentheses - skip
15782                }
15783                _ if paren_depth == 0 => {
15784                    // Character outside parentheses - accumulate for potential hint name
15785                    current.push(c);
15786                }
15787                _ => {
15788                    current.push(c);
15789                }
15790            }
15791        }
15792
15793        // Check if there's remaining text after the last function call
15794        let remaining_text = text[position_after_last_function..].trim();
15795        if !remaining_text.is_empty() {
15796            // Check if it looks like valid hint function names
15797            // Valid hint identifiers typically are uppercase alphanumeric with underscores
15798            // If we see multiple words without parens, it's likely unparseable
15799            let words: Vec<&str> = remaining_text.split_whitespace().collect();
15800            let looks_like_hint_functions = words.iter().all(|word| {
15801                // A valid hint name followed by opening paren, or a standalone uppercase identifier
15802                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
15803            });
15804
15805            if !looks_like_hint_functions && words.len() > 1 {
15806                has_unparseable_content = true;
15807            }
15808        }
15809
15810        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
15811        if has_unparseable_content {
15812            return vec![text.trim().to_string()];
15813        }
15814
15815        // If we couldn't parse anything, return the original text as a single hint
15816        if results.is_empty() {
15817            results.push(text.trim().to_string());
15818        }
15819
15820        results
15821    }
15822
15823    /// Format a hint function for pretty printing
15824    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
15825    fn format_hint_function(&self, hint: &str) -> String {
15826        if !self.config.pretty {
15827            return hint.to_string();
15828        }
15829
15830        // Try to parse NAME(args) pattern
15831        if let Some(paren_pos) = hint.find('(') {
15832            if hint.ends_with(')') {
15833                let name = &hint[..paren_pos];
15834                let args_str = &hint[paren_pos + 1..hint.len() - 1];
15835
15836                // Parse arguments (space-separated for Oracle hints)
15837                let args: Vec<&str> = args_str.split_whitespace().collect();
15838
15839                // Calculate total width of arguments
15840                let total_args_width: usize =
15841                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
15842
15843                // If too wide, format on multiple lines
15844                if total_args_width > self.config.max_text_width && !args.is_empty() {
15845                    let mut result = format!("{}(\n", name);
15846                    for arg in &args {
15847                        result.push_str("    "); // 4-space indent for args
15848                        result.push_str(arg);
15849                        result.push('\n');
15850                    }
15851                    result.push_str("  )"); // 2-space indent for closing paren
15852                    return result;
15853                }
15854            }
15855        }
15856
15857        hint.to_string()
15858    }
15859
15860    /// Convert a hint expression to a string, handling multiline formatting for long arguments
15861    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
15862        match expr {
15863            HintExpression::Function { name, args } => {
15864                // Generate each argument to a string
15865                let arg_strings: Vec<String> = args
15866                    .iter()
15867                    .map(|arg| {
15868                        let mut gen = Generator::with_arc_config(self.config.clone());
15869                        gen.generate_expression(arg)?;
15870                        Ok(gen.output)
15871                    })
15872                    .collect::<Result<Vec<_>>>()?;
15873
15874                // Oracle hints use space-separated arguments, not comma-separated
15875                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
15876                    + arg_strings.len().saturating_sub(1); // spaces between args
15877
15878                // Check if function args need multiline formatting
15879                // Use too_wide check for argument formatting
15880                let args_multiline =
15881                    self.config.pretty && total_args_width > self.config.max_text_width;
15882
15883                if args_multiline && !arg_strings.is_empty() {
15884                    // Multiline format for long argument lists
15885                    let mut result = format!("{}(\n", name);
15886                    for arg_str in &arg_strings {
15887                        result.push_str("    "); // 4-space indent for args
15888                        result.push_str(arg_str);
15889                        result.push('\n');
15890                    }
15891                    result.push_str("  )"); // 2-space indent for closing paren
15892                    Ok(result)
15893                } else {
15894                    // Single line format with space-separated args (Oracle style)
15895                    let args_str = arg_strings.join(" ");
15896                    Ok(format!("{}({})", name, args_str))
15897                }
15898            }
15899            HintExpression::Identifier(name) => Ok(name.clone()),
15900            HintExpression::Raw(text) => {
15901                // For pretty printing, try to format the raw text
15902                if self.config.pretty {
15903                    Ok(self.format_hint_function(text))
15904                } else {
15905                    Ok(text.clone())
15906                }
15907            }
15908        }
15909    }
15910
15911    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
15912        // PostgreSQL ONLY modifier: prevents scanning child tables
15913        if table.only {
15914            self.write_keyword("ONLY");
15915            self.write_space();
15916        }
15917
15918        // Check for IDENTIFIER() (Snowflake) or OPENDATASOURCE(...).db.schema.table (TSQL)
15919        if let Some(ref identifier_func) = table.identifier_func {
15920            self.generate_expression(identifier_func)?;
15921            // If table name parts are present, emit .catalog.schema.name after the function
15922            if !table.name.name.is_empty() {
15923                if let Some(catalog) = &table.catalog {
15924                    self.write(".");
15925                    self.generate_identifier(catalog)?;
15926                }
15927                if let Some(schema) = &table.schema {
15928                    self.write(".");
15929                    self.generate_identifier(schema)?;
15930                }
15931                self.write(".");
15932                self.generate_identifier(&table.name)?;
15933            }
15934        } else {
15935            if let Some(catalog) = &table.catalog {
15936                self.generate_identifier(catalog)?;
15937                self.write(".");
15938            }
15939            if let Some(schema) = &table.schema {
15940                self.generate_identifier(schema)?;
15941                self.write(".");
15942            }
15943            self.generate_identifier(&table.name)?;
15944        }
15945
15946        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
15947        if let Some(changes) = &table.changes {
15948            self.write(" ");
15949            self.generate_changes(changes)?;
15950        }
15951
15952        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
15953        if !table.partitions.is_empty() {
15954            self.write_space();
15955            self.write_keyword("PARTITION");
15956            self.write("(");
15957            for (i, partition) in table.partitions.iter().enumerate() {
15958                if i > 0 {
15959                    self.write(", ");
15960                }
15961                self.generate_identifier(partition)?;
15962            }
15963            self.write(")");
15964        }
15965
15966        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
15967        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
15968        if table.changes.is_none() {
15969            if let Some(when) = &table.when {
15970                self.write_space();
15971                self.generate_historical_data(when)?;
15972            }
15973        }
15974
15975        // Output TSQL FOR SYSTEM_TIME temporal clause (before alias, except BigQuery)
15976        let system_time_post_alias = matches!(self.config.dialect, Some(DialectType::BigQuery));
15977        if !system_time_post_alias {
15978            if let Some(ref system_time) = table.system_time {
15979                self.write_space();
15980                self.write(system_time);
15981            }
15982        }
15983
15984        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
15985        if let Some(ref version) = table.version {
15986            self.write_space();
15987            self.generate_version(version)?;
15988        }
15989
15990        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
15991        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
15992        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
15993        let alias_post_tablesample = self.config.alias_post_tablesample;
15994
15995        if alias_post_tablesample {
15996            // TABLESAMPLE before alias (Oracle, Hive, Spark)
15997            self.generate_table_sample_clause(table)?;
15998        }
15999
16000        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
16001        // For SQLite, INDEXED BY hints come after the alias, so skip here
16002        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
16003            && table.hints.iter().any(|h| {
16004                if let Expression::Identifier(id) = h {
16005                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
16006                } else {
16007                    false
16008                }
16009            });
16010        if !table.hints.is_empty() && !is_sqlite_hint {
16011            for hint in &table.hints {
16012                self.write_space();
16013                self.generate_expression(hint)?;
16014            }
16015        }
16016
16017        if let Some(alias) = &table.alias {
16018            self.write_space();
16019            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
16020            // Generic mode and most dialects always use AS for table aliases
16021            let always_use_as = self.config.dialect.is_none()
16022                || matches!(
16023                    self.config.dialect,
16024                    Some(DialectType::Generic)
16025                        | Some(DialectType::PostgreSQL)
16026                        | Some(DialectType::Redshift)
16027                        | Some(DialectType::Snowflake)
16028                        | Some(DialectType::BigQuery)
16029                        | Some(DialectType::DuckDB)
16030                        | Some(DialectType::Presto)
16031                        | Some(DialectType::Trino)
16032                        | Some(DialectType::TSQL)
16033                        | Some(DialectType::Fabric)
16034                        | Some(DialectType::MySQL)
16035                        | Some(DialectType::Spark)
16036                        | Some(DialectType::Hive)
16037                        | Some(DialectType::SQLite)
16038                        | Some(DialectType::Drill)
16039                );
16040            let is_stage_ref = table.name.name.starts_with('@');
16041            // Oracle never uses AS for table aliases
16042            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
16043            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
16044                self.write_keyword("AS");
16045                self.write_space();
16046            }
16047            self.generate_identifier(alias)?;
16048
16049            // Output column aliases if present: AS t(c1, c2)
16050            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
16051            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
16052                self.write("(");
16053                for (i, col_alias) in table.column_aliases.iter().enumerate() {
16054                    if i > 0 {
16055                        self.write(", ");
16056                    }
16057                    self.generate_identifier(col_alias)?;
16058                }
16059                self.write(")");
16060            }
16061        }
16062
16063        // BigQuery: FOR SYSTEM_TIME AS OF after alias
16064        if system_time_post_alias {
16065            if let Some(ref system_time) = table.system_time {
16066                self.write_space();
16067                self.write(system_time);
16068            }
16069        }
16070
16071        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
16072        if !alias_post_tablesample {
16073            self.generate_table_sample_clause(table)?;
16074        }
16075
16076        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
16077        if is_sqlite_hint {
16078            for hint in &table.hints {
16079                self.write_space();
16080                self.generate_expression(hint)?;
16081            }
16082        }
16083
16084        // ClickHouse FINAL modifier
16085        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
16086            self.write_space();
16087            self.write_keyword("FINAL");
16088        }
16089
16090        // Output trailing comments
16091        for comment in &table.trailing_comments {
16092            self.write_space();
16093            self.write_formatted_comment(comment);
16094        }
16095        // Note: leading_comments (from before table in FROM clause) are intentionally NOT
16096        // output here - they are output by the FROM/PIVOT generator after the full expression
16097
16098        Ok(())
16099    }
16100
16101    /// Helper to output TABLESAMPLE clause for a table reference
16102    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
16103        if let Some(ref ts) = table.table_sample {
16104            self.write_space();
16105            if ts.is_using_sample {
16106                self.write_keyword("USING SAMPLE");
16107            } else {
16108                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
16109                self.write_keyword(self.config.tablesample_keywords);
16110            }
16111            self.generate_sample_body(ts)?;
16112            // Seed for table-level sample - use dialect's configured keyword
16113            if let Some(ref seed) = ts.seed {
16114                self.write_space();
16115                self.write_keyword(self.config.tablesample_seed_keyword);
16116                self.write(" (");
16117                self.generate_expression(seed)?;
16118                self.write(")");
16119            }
16120        }
16121        Ok(())
16122    }
16123
16124    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
16125        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
16126        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
16127
16128        if sr.quoted {
16129            self.write("'");
16130        }
16131
16132        self.write(&sr.name);
16133        if let Some(path) = &sr.path {
16134            self.write(path);
16135        }
16136
16137        if sr.quoted {
16138            self.write("'");
16139        }
16140
16141        // Output FILE_FORMAT and PATTERN if present
16142        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
16143        if has_options {
16144            self.write(" (");
16145            let mut first = true;
16146
16147            if let Some(file_format) = &sr.file_format {
16148                if !first {
16149                    self.write(", ");
16150                }
16151                self.write_keyword("FILE_FORMAT");
16152                self.write(" => ");
16153                self.generate_expression(file_format)?;
16154                first = false;
16155            }
16156
16157            if let Some(pattern) = &sr.pattern {
16158                if !first {
16159                    self.write(", ");
16160                }
16161                self.write_keyword("PATTERN");
16162                self.write(" => '");
16163                self.write(pattern);
16164                self.write("'");
16165            }
16166
16167            self.write(")");
16168        }
16169        Ok(())
16170    }
16171
16172    fn generate_star(&mut self, star: &Star) -> Result<()> {
16173        use crate::dialects::DialectType;
16174
16175        if let Some(table) = &star.table {
16176            self.generate_identifier(table)?;
16177            self.write(".");
16178        }
16179        self.write("*");
16180
16181        // Generate EXCLUDE/EXCEPT clause based on dialect
16182        if let Some(except) = &star.except {
16183            if !except.is_empty() {
16184                self.write_space();
16185                // Use dialect-appropriate keyword
16186                match self.config.dialect {
16187                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
16188                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
16189                        self.write_keyword("EXCLUDE")
16190                    }
16191                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
16192                }
16193                self.write(" (");
16194                for (i, col) in except.iter().enumerate() {
16195                    if i > 0 {
16196                        self.write(", ");
16197                    }
16198                    self.generate_identifier(col)?;
16199                }
16200                self.write(")");
16201            }
16202        }
16203
16204        // Generate REPLACE clause
16205        if let Some(replace) = &star.replace {
16206            if !replace.is_empty() {
16207                self.write_space();
16208                self.write_keyword("REPLACE");
16209                self.write(" (");
16210                for (i, alias) in replace.iter().enumerate() {
16211                    if i > 0 {
16212                        self.write(", ");
16213                    }
16214                    self.generate_expression(&alias.this)?;
16215                    self.write_space();
16216                    self.write_keyword("AS");
16217                    self.write_space();
16218                    self.generate_identifier(&alias.alias)?;
16219                }
16220                self.write(")");
16221            }
16222        }
16223
16224        // Generate RENAME clause (Snowflake specific)
16225        if let Some(rename) = &star.rename {
16226            if !rename.is_empty() {
16227                self.write_space();
16228                self.write_keyword("RENAME");
16229                self.write(" (");
16230                for (i, (old_name, new_name)) in rename.iter().enumerate() {
16231                    if i > 0 {
16232                        self.write(", ");
16233                    }
16234                    self.generate_identifier(old_name)?;
16235                    self.write_space();
16236                    self.write_keyword("AS");
16237                    self.write_space();
16238                    self.generate_identifier(new_name)?;
16239                }
16240                self.write(")");
16241            }
16242        }
16243
16244        // Output trailing comments
16245        for comment in &star.trailing_comments {
16246            self.write_space();
16247            self.write_formatted_comment(comment);
16248        }
16249
16250        Ok(())
16251    }
16252
16253    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
16254    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
16255        self.write("{");
16256        match expr {
16257            Expression::Star(star) => {
16258                // Generate the star (table.* or just * with optional EXCLUDE)
16259                self.generate_star(star)?;
16260            }
16261            Expression::ILike(ilike) => {
16262                // {* ILIKE 'pattern'} syntax
16263                self.generate_expression(&ilike.left)?;
16264                self.write_space();
16265                self.write_keyword("ILIKE");
16266                self.write_space();
16267                self.generate_expression(&ilike.right)?;
16268            }
16269            _ => {
16270                self.generate_expression(expr)?;
16271            }
16272        }
16273        self.write("}");
16274        Ok(())
16275    }
16276
16277    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
16278        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
16279        // to avoid duplication (comments are captured as both Column.trailing_comments
16280        // and Alias.pre_alias_comments during parsing)
16281        match &alias.this {
16282            Expression::Column(col) => {
16283                // Generate column without trailing comments - they're in pre_alias_comments
16284                if let Some(table) = &col.table {
16285                    self.generate_identifier(table)?;
16286                    self.write(".");
16287                }
16288                self.generate_identifier(&col.name)?;
16289            }
16290            _ => {
16291                self.generate_expression(&alias.this)?;
16292            }
16293        }
16294
16295        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
16296        // moves pre-alias comments to after the alias. When there are also trailing_comments,
16297        // keep pre-alias comments in their original position (between expression and AS).
16298        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
16299            for comment in &alias.pre_alias_comments {
16300                self.write_space();
16301                self.write_formatted_comment(comment);
16302            }
16303        }
16304
16305        use crate::dialects::DialectType;
16306
16307        // Determine if we should skip AS keyword for table-valued function aliases
16308        // Oracle and some other dialects don't use AS for table aliases
16309        // Note: We specifically use TableFromRows here, NOT Function, because Function
16310        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
16311        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
16312        let is_table_source = matches!(
16313            &alias.this,
16314            Expression::JSONTable(_)
16315                | Expression::XMLTable(_)
16316                | Expression::TableFromRows(_)
16317                | Expression::Unnest(_)
16318                | Expression::MatchRecognize(_)
16319                | Expression::Select(_)
16320                | Expression::Subquery(_)
16321                | Expression::Paren(_)
16322        );
16323        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
16324        let skip_as = is_table_source && dialect_skips_table_alias_as;
16325
16326        self.write_space();
16327        if !skip_as {
16328            self.write_keyword("AS");
16329            self.write_space();
16330        }
16331
16332        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
16333        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
16334
16335        // Check if we have column aliases only (no table alias name)
16336        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
16337            // Generate AS (col1, col2, ...)
16338            self.write("(");
16339            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
16340                if i > 0 {
16341                    self.write(", ");
16342                }
16343                self.generate_alias_identifier(col_alias)?;
16344            }
16345            self.write(")");
16346        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
16347            // Generate AS alias(col1, col2, ...)
16348            self.generate_alias_identifier(&alias.alias)?;
16349            self.write("(");
16350            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
16351                if i > 0 {
16352                    self.write(", ");
16353                }
16354                self.generate_alias_identifier(col_alias)?;
16355            }
16356            self.write(")");
16357        } else {
16358            // Simple alias (or BigQuery without column aliases)
16359            self.generate_alias_identifier(&alias.alias)?;
16360        }
16361
16362        // Output trailing comments (comments after the alias)
16363        for comment in &alias.trailing_comments {
16364            self.write_space();
16365            self.write_formatted_comment(comment);
16366        }
16367
16368        // Output pre-alias comments: when there are no trailing_comments, sqlglot
16369        // moves pre-alias comments to after the alias. When there are trailing_comments,
16370        // the pre-alias comments were already lost (consumed as column trailing comments
16371        // that were then used as pre_alias_comments). We always emit them after alias.
16372        if alias.trailing_comments.is_empty() {
16373            for comment in &alias.pre_alias_comments {
16374                self.write_space();
16375                self.write_formatted_comment(comment);
16376            }
16377        }
16378
16379        Ok(())
16380    }
16381
16382    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
16383        use crate::dialects::DialectType;
16384
16385        // SingleStore uses :> syntax
16386        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
16387            self.generate_expression(&cast.this)?;
16388            self.write(" :> ");
16389            self.generate_data_type(&cast.to)?;
16390            return Ok(());
16391        }
16392
16393        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
16394        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
16395            let is_unknown_type = matches!(cast.to, DataType::Unknown)
16396                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
16397            if is_unknown_type {
16398                if let Some(format) = &cast.format {
16399                    self.write_keyword("CAST");
16400                    self.write("(");
16401                    self.generate_expression(&cast.this)?;
16402                    self.write_space();
16403                    self.write_keyword("AS");
16404                    self.write_space();
16405                    self.write_keyword("FORMAT");
16406                    self.write_space();
16407                    self.generate_expression(format)?;
16408                    self.write(")");
16409                    return Ok(());
16410                }
16411            }
16412        }
16413
16414        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
16415        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
16416        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
16417            if let Some(format) = &cast.format {
16418                // Check if target type is DATE or TIMESTAMP
16419                let is_date = matches!(cast.to, DataType::Date);
16420                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
16421
16422                if is_date || is_timestamp {
16423                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
16424                    self.write_keyword(func_name);
16425                    self.write("(");
16426                    self.generate_expression(&cast.this)?;
16427                    self.write(", ");
16428
16429                    // Normalize format string for Oracle (HH -> HH12)
16430                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
16431                    if let Expression::Literal(lit) = format.as_ref() {
16432                        if let Literal::String(fmt_str) = lit.as_ref() {
16433                            let normalized = self.normalize_oracle_format(fmt_str);
16434                            self.write("'");
16435                            self.write(&normalized);
16436                            self.write("'");
16437                        }
16438                    } else {
16439                        self.generate_expression(format)?;
16440                    }
16441
16442                    self.write(")");
16443                    return Ok(());
16444                }
16445            }
16446        }
16447
16448        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
16449        // This preserves sqlglot's typed inline array literal output.
16450        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
16451            if let Expression::Array(arr) = &cast.this {
16452                self.generate_data_type(&cast.to)?;
16453                // Output just the bracket content [values] without the ARRAY prefix
16454                self.write("[");
16455                for (i, expr) in arr.expressions.iter().enumerate() {
16456                    if i > 0 {
16457                        self.write(", ");
16458                    }
16459                    self.generate_expression(expr)?;
16460                }
16461                self.write("]");
16462                return Ok(());
16463            }
16464            if matches!(&cast.this, Expression::ArrayFunc(_)) {
16465                self.generate_data_type(&cast.to)?;
16466                self.generate_expression(&cast.this)?;
16467                return Ok(());
16468            }
16469        }
16470
16471        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
16472        // convert the inner Struct to ROW(values...) format
16473        if matches!(
16474            self.config.dialect,
16475            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
16476        ) {
16477            if let Expression::Struct(ref s) = cast.this {
16478                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
16479                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
16480                    self.write_keyword("CAST");
16481                    self.write("(");
16482                    self.generate_struct_as_row(s)?;
16483                    self.write_space();
16484                    self.write_keyword("AS");
16485                    self.write_space();
16486                    self.generate_data_type(&cast.to)?;
16487                    self.write(")");
16488                    return Ok(());
16489                }
16490            }
16491        }
16492
16493        // Determine if we should use :: syntax based on dialect
16494        // PostgreSQL prefers :: for identity, most others prefer CAST()
16495        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
16496
16497        if use_double_colon {
16498            // PostgreSQL :: syntax: expr::type
16499            self.generate_expression(&cast.this)?;
16500            self.write("::");
16501            self.generate_data_type(&cast.to)?;
16502        } else {
16503            // Standard CAST() syntax
16504            self.write_keyword("CAST");
16505            self.write("(");
16506            self.generate_expression(&cast.this)?;
16507            self.write_space();
16508            self.write_keyword("AS");
16509            self.write_space();
16510            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
16511            // This matches Python sqlglot's CAST_MAPPING behavior
16512            if matches!(
16513                self.config.dialect,
16514                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
16515            ) {
16516                match &cast.to {
16517                    DataType::Custom { ref name } => {
16518                        if name.eq_ignore_ascii_case("LONGTEXT")
16519                            || name.eq_ignore_ascii_case("MEDIUMTEXT")
16520                            || name.eq_ignore_ascii_case("TINYTEXT")
16521                            || name.eq_ignore_ascii_case("LONGBLOB")
16522                            || name.eq_ignore_ascii_case("MEDIUMBLOB")
16523                            || name.eq_ignore_ascii_case("TINYBLOB")
16524                        {
16525                            self.write_keyword("CHAR");
16526                        } else {
16527                            self.generate_data_type(&cast.to)?;
16528                        }
16529                    }
16530                    DataType::VarChar { length, .. } => {
16531                        // MySQL CAST: VARCHAR -> CHAR
16532                        self.write_keyword("CHAR");
16533                        if let Some(n) = length {
16534                            self.write(&format!("({})", n));
16535                        }
16536                    }
16537                    DataType::Text => {
16538                        // MySQL CAST: TEXT -> CHAR
16539                        self.write_keyword("CHAR");
16540                    }
16541                    DataType::Timestamp {
16542                        precision,
16543                        timezone: false,
16544                    } => {
16545                        // MySQL CAST: TIMESTAMP -> DATETIME
16546                        self.write_keyword("DATETIME");
16547                        if let Some(p) = precision {
16548                            self.write(&format!("({})", p));
16549                        }
16550                    }
16551                    _ => {
16552                        self.generate_data_type(&cast.to)?;
16553                    }
16554                }
16555            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
16556                // Snowflake CAST: STRING -> VARCHAR
16557                match &cast.to {
16558                    DataType::String { length } => {
16559                        self.write_keyword("VARCHAR");
16560                        if let Some(n) = length {
16561                            self.write(&format!("({})", n));
16562                        }
16563                    }
16564                    _ => {
16565                        self.generate_data_type(&cast.to)?;
16566                    }
16567                }
16568            } else {
16569                self.generate_data_type(&cast.to)?;
16570            }
16571
16572            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
16573            if let Some(default) = &cast.default {
16574                self.write_space();
16575                self.write_keyword("DEFAULT");
16576                self.write_space();
16577                self.generate_expression(default)?;
16578                self.write_space();
16579                self.write_keyword("ON");
16580                self.write_space();
16581                self.write_keyword("CONVERSION");
16582                self.write_space();
16583                self.write_keyword("ERROR");
16584            }
16585
16586            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
16587            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
16588            if let Some(format) = &cast.format {
16589                // Check if Oracle dialect - use comma syntax
16590                if matches!(
16591                    self.config.dialect,
16592                    Some(crate::dialects::DialectType::Oracle)
16593                ) {
16594                    self.write(", ");
16595                } else {
16596                    self.write_space();
16597                    self.write_keyword("FORMAT");
16598                    self.write_space();
16599                }
16600                self.generate_expression(format)?;
16601            }
16602
16603            self.write(")");
16604            // Output trailing comments
16605            for comment in &cast.trailing_comments {
16606                self.write_space();
16607                self.write_formatted_comment(comment);
16608            }
16609        }
16610        Ok(())
16611    }
16612
16613    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
16614    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
16615    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
16616        self.write_keyword("ROW");
16617        self.write("(");
16618        for (i, (_, expr)) in s.fields.iter().enumerate() {
16619            if i > 0 {
16620                self.write(", ");
16621            }
16622            // Recursively convert inner Struct to ROW format
16623            if let Expression::Struct(ref inner_s) = expr {
16624                self.generate_struct_as_row(inner_s)?;
16625            } else {
16626                self.generate_expression(expr)?;
16627            }
16628        }
16629        self.write(")");
16630        Ok(())
16631    }
16632
16633    /// Normalize Oracle date/time format strings
16634    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
16635    fn normalize_oracle_format(&self, format: &str) -> String {
16636        // Replace standalone HH with HH12 (but not HH12 or HH24)
16637        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
16638        let mut result = String::new();
16639        let chars: Vec<char> = format.chars().collect();
16640        let mut i = 0;
16641
16642        while i < chars.len() {
16643            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
16644                // Check what follows HH
16645                if i + 2 < chars.len() {
16646                    let next = chars[i + 2];
16647                    if next == '1' || next == '2' {
16648                        // This is HH12 or HH24, keep as is
16649                        result.push('H');
16650                        result.push('H');
16651                        i += 2;
16652                        continue;
16653                    }
16654                }
16655                // Standalone HH -> HH12
16656                result.push_str("HH12");
16657                i += 2;
16658            } else {
16659                result.push(chars[i]);
16660                i += 1;
16661            }
16662        }
16663
16664        result
16665    }
16666
16667    /// Check if the current dialect prefers :: cast syntax
16668    /// Note: Python sqlglot normalizes all :: to CAST() for output, even for PostgreSQL
16669    /// So we return false for all dialects to match Python sqlglot's behavior
16670    fn dialect_prefers_double_colon(&self) -> bool {
16671        // Python sqlglot normalizes :: syntax to CAST() for all dialects
16672        // Even PostgreSQL outputs CAST() not ::
16673        false
16674    }
16675
16676    /// Generate MOD function - uses % operator for Snowflake/MySQL/Presto/Trino, MOD() for others
16677    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
16678        use crate::dialects::DialectType;
16679
16680        // Snowflake, MySQL, Presto, Trino, PostgreSQL, and DuckDB prefer x % y instead of MOD(x, y)
16681        let use_percent_operator = matches!(
16682            self.config.dialect,
16683            Some(DialectType::Snowflake)
16684                | Some(DialectType::MySQL)
16685                | Some(DialectType::Presto)
16686                | Some(DialectType::Trino)
16687                | Some(DialectType::PostgreSQL)
16688                | Some(DialectType::DuckDB)
16689                | Some(DialectType::Hive)
16690                | Some(DialectType::Spark)
16691                | Some(DialectType::Databricks)
16692                | Some(DialectType::Athena)
16693        );
16694
16695        if use_percent_operator {
16696            // Wrap complex expressions in parens to preserve precedence
16697            // Since % has higher precedence than +/-, we need parens for Add/Sub on either side
16698            let needs_paren = |e: &Expression| matches!(e, Expression::Add(_) | Expression::Sub(_));
16699            if needs_paren(&f.this) {
16700                self.write("(");
16701                self.generate_expression(&f.this)?;
16702                self.write(")");
16703            } else {
16704                self.generate_expression(&f.this)?;
16705            }
16706            self.write(" % ");
16707            if needs_paren(&f.expression) {
16708                self.write("(");
16709                self.generate_expression(&f.expression)?;
16710                self.write(")");
16711            } else {
16712                self.generate_expression(&f.expression)?;
16713            }
16714            Ok(())
16715        } else {
16716            self.generate_binary_func("MOD", &f.this, &f.expression)
16717        }
16718    }
16719
16720    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
16721    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
16722        use crate::dialects::DialectType;
16723
16724        // Snowflake normalizes IFNULL to COALESCE
16725        let func_name = match self.config.dialect {
16726            Some(DialectType::Snowflake) => "COALESCE",
16727            _ => "IFNULL",
16728        };
16729
16730        self.generate_binary_func(func_name, &f.this, &f.expression)
16731    }
16732
16733    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
16734    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
16735        // Use original function name if preserved (for identity tests)
16736        if let Some(ref original_name) = f.original_name {
16737            return self.generate_binary_func(original_name, &f.this, &f.expression);
16738        }
16739
16740        // Otherwise, use dialect-specific function names
16741        use crate::dialects::DialectType;
16742        let func_name = match self.config.dialect {
16743            Some(DialectType::Snowflake)
16744            | Some(DialectType::ClickHouse)
16745            | Some(DialectType::PostgreSQL)
16746            | Some(DialectType::Presto)
16747            | Some(DialectType::Trino)
16748            | Some(DialectType::Athena)
16749            | Some(DialectType::DuckDB)
16750            | Some(DialectType::BigQuery)
16751            | Some(DialectType::Spark)
16752            | Some(DialectType::Databricks)
16753            | Some(DialectType::Hive) => "COALESCE",
16754            Some(DialectType::MySQL)
16755            | Some(DialectType::Doris)
16756            | Some(DialectType::StarRocks)
16757            | Some(DialectType::SingleStore)
16758            | Some(DialectType::TiDB) => "IFNULL",
16759            _ => "NVL",
16760        };
16761
16762        self.generate_binary_func(func_name, &f.this, &f.expression)
16763    }
16764
16765    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
16766    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
16767        use crate::dialects::DialectType;
16768
16769        // Snowflake normalizes STDDEV_SAMP to STDDEV
16770        let func_name = match self.config.dialect {
16771            Some(DialectType::Snowflake) => "STDDEV",
16772            _ => "STDDEV_SAMP",
16773        };
16774
16775        self.generate_agg_func(func_name, f)
16776    }
16777
16778    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
16779        self.generate_expression(&coll.this)?;
16780        self.write_space();
16781        self.write_keyword("COLLATE");
16782        self.write_space();
16783        if coll.quoted {
16784            // Single-quoted string: COLLATE 'de_DE'
16785            self.write("'");
16786            self.write(&coll.collation);
16787            self.write("'");
16788        } else if coll.double_quoted {
16789            // Double-quoted identifier: COLLATE "de_DE"
16790            self.write("\"");
16791            self.write(&coll.collation);
16792            self.write("\"");
16793        } else {
16794            // Unquoted identifier: COLLATE de_DE
16795            self.write(&coll.collation);
16796        }
16797        Ok(())
16798    }
16799
16800    fn generate_case(&mut self, case: &Case) -> Result<()> {
16801        // In pretty mode, decide whether to expand based on total text width
16802        let multiline_case = if self.config.pretty {
16803            // Build the flat representation to check width
16804            let mut statements: Vec<String> = Vec::new();
16805            let operand_str = if let Some(operand) = &case.operand {
16806                let s = self.generate_to_string(operand)?;
16807                statements.push(format!("CASE {}", s));
16808                s
16809            } else {
16810                statements.push("CASE".to_string());
16811                String::new()
16812            };
16813            let _ = operand_str;
16814            for (condition, result) in &case.whens {
16815                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
16816                statements.push(format!("THEN {}", self.generate_to_string(result)?));
16817            }
16818            if let Some(else_) = &case.else_ {
16819                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
16820            }
16821            statements.push("END".to_string());
16822            self.too_wide(&statements)
16823        } else {
16824            false
16825        };
16826
16827        self.write_keyword("CASE");
16828        if let Some(operand) = &case.operand {
16829            self.write_space();
16830            self.generate_expression(operand)?;
16831        }
16832        if multiline_case {
16833            self.indent_level += 1;
16834        }
16835        for (condition, result) in &case.whens {
16836            if multiline_case {
16837                self.write_newline();
16838                self.write_indent();
16839            } else {
16840                self.write_space();
16841            }
16842            self.write_keyword("WHEN");
16843            self.write_space();
16844            self.generate_expression(condition)?;
16845            if multiline_case {
16846                self.write_newline();
16847                self.write_indent();
16848            } else {
16849                self.write_space();
16850            }
16851            self.write_keyword("THEN");
16852            self.write_space();
16853            self.generate_expression(result)?;
16854        }
16855        if let Some(else_) = &case.else_ {
16856            if multiline_case {
16857                self.write_newline();
16858                self.write_indent();
16859            } else {
16860                self.write_space();
16861            }
16862            self.write_keyword("ELSE");
16863            self.write_space();
16864            self.generate_expression(else_)?;
16865        }
16866        if multiline_case {
16867            self.indent_level -= 1;
16868            self.write_newline();
16869            self.write_indent();
16870        } else {
16871            self.write_space();
16872        }
16873        self.write_keyword("END");
16874        // Emit any comments that were attached to the CASE keyword
16875        for comment in &case.comments {
16876            self.write(" ");
16877            self.write_formatted_comment(comment);
16878        }
16879        Ok(())
16880    }
16881
16882    fn generate_function(&mut self, func: &Function) -> Result<()> {
16883        // Normalize function name based on dialect settings
16884        let normalized_name = self.normalize_func_name(&func.name);
16885
16886        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
16887        if matches!(self.config.dialect, Some(DialectType::DuckDB))
16888            && func.name.eq_ignore_ascii_case("ARRAY_CONSTRUCT_COMPACT")
16889        {
16890            self.write("LIST_FILTER(");
16891            self.write("[");
16892            for (i, arg) in func.args.iter().enumerate() {
16893                if i > 0 {
16894                    self.write(", ");
16895                }
16896                self.generate_expression(arg)?;
16897            }
16898            self.write("], _u -> NOT _u IS NULL)");
16899            return Ok(());
16900        }
16901
16902        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
16903        if func.name.eq_ignore_ascii_case("STRUCT")
16904            && !matches!(
16905                self.config.dialect,
16906                Some(DialectType::BigQuery)
16907                    | Some(DialectType::Spark)
16908                    | Some(DialectType::Databricks)
16909                    | Some(DialectType::Hive)
16910                    | None
16911            )
16912        {
16913            return self.generate_struct_function_cross_dialect(func);
16914        }
16915
16916        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
16917        // This is an internal marker function for ::? JSON path syntax
16918        if func.name.eq_ignore_ascii_case("__SS_JSON_PATH_QMARK__") && func.args.len() == 2 {
16919            self.generate_expression(&func.args[0])?;
16920            self.write("::?");
16921            // Extract the key from the string literal
16922            if let Expression::Literal(lit) = &func.args[1] {
16923                if let crate::expressions::Literal::String(key) = lit.as_ref() {
16924                    self.write(key);
16925                }
16926            } else {
16927                self.generate_expression(&func.args[1])?;
16928            }
16929            return Ok(());
16930        }
16931
16932        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
16933        if func.name.eq_ignore_ascii_case("__PG_BITWISE_XOR__") && func.args.len() == 2 {
16934            self.generate_expression(&func.args[0])?;
16935            self.write(" # ");
16936            self.generate_expression(&func.args[1])?;
16937            return Ok(());
16938        }
16939
16940        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
16941        if matches!(
16942            self.config.dialect,
16943            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
16944        ) && func.name.eq_ignore_ascii_case("TRY")
16945            && func.args.len() == 1
16946        {
16947            self.generate_expression(&func.args[0])?;
16948            return Ok(());
16949        }
16950
16951        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
16952        if self.config.dialect == Some(DialectType::ClickHouse)
16953            && func.name.eq_ignore_ascii_case("TOSTARTOFDAY")
16954            && func.args.len() == 1
16955        {
16956            self.write("dateTrunc('DAY', ");
16957            self.generate_expression(&func.args[0])?;
16958            self.write(")");
16959            return Ok(());
16960        }
16961
16962        // Redshift: CONCAT(a, b, ...) -> a || b || ...
16963        if self.config.dialect == Some(DialectType::Redshift)
16964            && func.name.eq_ignore_ascii_case("CONCAT")
16965            && func.args.len() >= 2
16966        {
16967            for (i, arg) in func.args.iter().enumerate() {
16968                if i > 0 {
16969                    self.write(" || ");
16970                }
16971                self.generate_expression(arg)?;
16972            }
16973            return Ok(());
16974        }
16975
16976        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
16977        if self.config.dialect == Some(DialectType::Redshift)
16978            && func.name.eq_ignore_ascii_case("CONCAT_WS")
16979            && func.args.len() >= 2
16980        {
16981            let sep = &func.args[0];
16982            for (i, arg) in func.args.iter().skip(1).enumerate() {
16983                if i > 0 {
16984                    self.write(" || ");
16985                    self.generate_expression(sep)?;
16986                    self.write(" || ");
16987                }
16988                self.generate_expression(arg)?;
16989            }
16990            return Ok(());
16991        }
16992
16993        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
16994        // Unit should be unquoted uppercase identifier
16995        if self.config.dialect == Some(DialectType::Redshift)
16996            && (func.name.eq_ignore_ascii_case("DATEDIFF")
16997                || func.name.eq_ignore_ascii_case("DATE_DIFF"))
16998            && func.args.len() == 3
16999        {
17000            self.write_keyword("DATEDIFF");
17001            self.write("(");
17002            // First arg is unit - normalize to unquoted uppercase
17003            self.write_redshift_date_part(&func.args[0]);
17004            self.write(", ");
17005            self.generate_expression(&func.args[1])?;
17006            self.write(", ");
17007            self.generate_expression(&func.args[2])?;
17008            self.write(")");
17009            return Ok(());
17010        }
17011
17012        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
17013        // Unit should be unquoted uppercase identifier
17014        if self.config.dialect == Some(DialectType::Redshift)
17015            && (func.name.eq_ignore_ascii_case("DATEADD")
17016                || func.name.eq_ignore_ascii_case("DATE_ADD"))
17017            && func.args.len() == 3
17018        {
17019            self.write_keyword("DATEADD");
17020            self.write("(");
17021            // First arg is unit - normalize to unquoted uppercase
17022            self.write_redshift_date_part(&func.args[0]);
17023            self.write(", ");
17024            self.generate_expression(&func.args[1])?;
17025            self.write(", ");
17026            self.generate_expression(&func.args[2])?;
17027            self.write(")");
17028            return Ok(());
17029        }
17030
17031        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function (dropping args)
17032        if func.name.eq_ignore_ascii_case("UUID_STRING")
17033            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
17034        {
17035            let func_name = match self.config.dialect {
17036                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
17037                Some(DialectType::BigQuery) => "GENERATE_UUID",
17038                _ => "UUID",
17039            };
17040            self.write_keyword(func_name);
17041            self.write("()");
17042            return Ok(());
17043        }
17044
17045        // Snowflake: GENERATOR(val) -> GENERATOR(ROWCOUNT => val)
17046        // GENERATOR(val1, val2) -> GENERATOR(ROWCOUNT => val1, TIMELIMIT => val2)
17047        // Positional args are mapped to named parameters.
17048        if matches!(self.config.dialect, Some(DialectType::Snowflake))
17049            && func.name.eq_ignore_ascii_case("GENERATOR")
17050        {
17051            let has_positional_args =
17052                !func.args.is_empty() && !matches!(&func.args[0], Expression::NamedArgument(_));
17053            if has_positional_args {
17054                let param_names = ["ROWCOUNT", "TIMELIMIT"];
17055                self.write_keyword("GENERATOR");
17056                self.write("(");
17057                for (i, arg) in func.args.iter().enumerate() {
17058                    if i > 0 {
17059                        self.write(", ");
17060                    }
17061                    if i < param_names.len() {
17062                        self.write_keyword(param_names[i]);
17063                        self.write(" => ");
17064                        self.generate_expression(arg)?;
17065                    } else {
17066                        self.generate_expression(arg)?;
17067                    }
17068                }
17069                self.write(")");
17070                return Ok(());
17071            }
17072        }
17073
17074        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
17075        // Unit should be quoted uppercase string
17076        if self.config.dialect == Some(DialectType::Redshift)
17077            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
17078            && func.args.len() == 2
17079        {
17080            self.write_keyword("DATE_TRUNC");
17081            self.write("(");
17082            // First arg is unit - normalize to quoted uppercase
17083            self.write_redshift_date_part_quoted(&func.args[0]);
17084            self.write(", ");
17085            self.generate_expression(&func.args[1])?;
17086            self.write(")");
17087            return Ok(());
17088        }
17089
17090        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
17091        if matches!(
17092            self.config.dialect,
17093            Some(DialectType::TSQL) | Some(DialectType::Fabric)
17094        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
17095            || func.name.eq_ignore_ascii_case("DATEPART"))
17096            && func.args.len() == 2
17097        {
17098            self.write_keyword("DATEPART");
17099            self.write("(");
17100            self.generate_expression(&func.args[0])?;
17101            self.write(", ");
17102            self.generate_expression(&func.args[1])?;
17103            self.write(")");
17104            return Ok(());
17105        }
17106
17107        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
17108        if matches!(
17109            self.config.dialect,
17110            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
17111        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
17112            || func.name.eq_ignore_ascii_case("DATEPART"))
17113            && func.args.len() == 2
17114        {
17115            self.write_keyword("EXTRACT");
17116            self.write("(");
17117            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
17118            match &func.args[0] {
17119                Expression::Literal(lit)
17120                    if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
17121                {
17122                    let crate::expressions::Literal::String(s) = lit.as_ref() else {
17123                        unreachable!()
17124                    };
17125                    self.write(&s.to_ascii_lowercase());
17126                }
17127                _ => self.generate_expression(&func.args[0])?,
17128            }
17129            self.write_space();
17130            self.write_keyword("FROM");
17131            self.write_space();
17132            self.generate_expression(&func.args[1])?;
17133            self.write(")");
17134            return Ok(());
17135        }
17136
17137        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
17138        // Also DATE literals in Dremio should be CAST(...AS DATE)
17139        if self.config.dialect == Some(DialectType::Dremio)
17140            && (func.name.eq_ignore_ascii_case("DATE_PART")
17141                || func.name.eq_ignore_ascii_case("DATEPART"))
17142            && func.args.len() == 2
17143        {
17144            self.write_keyword("EXTRACT");
17145            self.write("(");
17146            self.generate_expression(&func.args[0])?;
17147            self.write_space();
17148            self.write_keyword("FROM");
17149            self.write_space();
17150            // For Dremio, DATE literals should become CAST('value' AS DATE)
17151            self.generate_dremio_date_expression(&func.args[1])?;
17152            self.write(")");
17153            return Ok(());
17154        }
17155
17156        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
17157        if self.config.dialect == Some(DialectType::Dremio)
17158            && func.name.eq_ignore_ascii_case("CURRENT_DATE_UTC")
17159            && func.args.is_empty()
17160        {
17161            self.write_keyword("CURRENT_DATE_UTC");
17162            return Ok(());
17163        }
17164
17165        // Dremio: DATETYPE(year, month, day) transformation
17166        // - If all args are integer literals: DATE('YYYY-MM-DD')
17167        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
17168        if self.config.dialect == Some(DialectType::Dremio)
17169            && func.name.eq_ignore_ascii_case("DATETYPE")
17170            && func.args.len() == 3
17171        {
17172            // Helper function to extract integer from number literal
17173            fn get_int_literal(expr: &Expression) -> Option<i64> {
17174                if let Expression::Literal(lit) = expr {
17175                    if let crate::expressions::Literal::Number(s) = lit.as_ref() {
17176                        s.parse::<i64>().ok()
17177                    } else {
17178                        None
17179                    }
17180                } else {
17181                    None
17182                }
17183            }
17184
17185            // Check if all arguments are integer literals
17186            if let (Some(year), Some(month), Some(day)) = (
17187                get_int_literal(&func.args[0]),
17188                get_int_literal(&func.args[1]),
17189                get_int_literal(&func.args[2]),
17190            ) {
17191                // All are integer literals: DATE('YYYY-MM-DD')
17192                self.write_keyword("DATE");
17193                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
17194                return Ok(());
17195            }
17196
17197            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
17198            self.write_keyword("CAST");
17199            self.write("(");
17200            self.write_keyword("CONCAT");
17201            self.write("(");
17202            self.generate_expression(&func.args[0])?;
17203            self.write(", '-', ");
17204            self.generate_expression(&func.args[1])?;
17205            self.write(", '-', ");
17206            self.generate_expression(&func.args[2])?;
17207            self.write(")");
17208            self.write_space();
17209            self.write_keyword("AS");
17210            self.write_space();
17211            self.write_keyword("DATE");
17212            self.write(")");
17213            return Ok(());
17214        }
17215
17216        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
17217        // when it's not an integer literal
17218        let is_presto_like = matches!(
17219            self.config.dialect,
17220            Some(DialectType::Presto) | Some(DialectType::Trino)
17221        );
17222        if is_presto_like && func.name.eq_ignore_ascii_case("DATE_ADD") && func.args.len() == 3 {
17223            self.write_keyword("DATE_ADD");
17224            self.write("(");
17225            // First arg: unit (pass through as-is, e.g., 'DAY')
17226            self.generate_expression(&func.args[0])?;
17227            self.write(", ");
17228            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
17229            let interval = &func.args[1];
17230            let needs_cast = !self.returns_integer_type(interval);
17231            if needs_cast {
17232                self.write_keyword("CAST");
17233                self.write("(");
17234            }
17235            self.generate_expression(interval)?;
17236            if needs_cast {
17237                self.write_space();
17238                self.write_keyword("AS");
17239                self.write_space();
17240                self.write_keyword("BIGINT");
17241                self.write(")");
17242            }
17243            self.write(", ");
17244            // Third arg: date
17245            self.generate_expression(&func.args[2])?;
17246            self.write(")");
17247            return Ok(());
17248        }
17249
17250        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
17251        let use_brackets = func.use_bracket_syntax;
17252
17253        // Special case: functions WITH ORDINALITY need special output order
17254        // Input: FUNC(args) WITH ORDINALITY
17255        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
17256        // Output must be: FUNC(args) WITH ORDINALITY
17257        let has_ordinality = func.name.len() >= 16
17258            && func.name[func.name.len() - 16..].eq_ignore_ascii_case(" WITH ORDINALITY");
17259        let output_name = if has_ordinality {
17260            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
17261            self.normalize_func_name(base_name)
17262        } else {
17263            normalized_name.clone()
17264        };
17265
17266        // For qualified names (schema.function or object.method), preserve original case
17267        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
17268        if func.name.contains('.') && !has_ordinality {
17269            // Don't normalize qualified functions - preserve original case
17270            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
17271            if func.quoted {
17272                self.write("`");
17273                self.write(&func.name);
17274                self.write("`");
17275            } else {
17276                self.write(&func.name);
17277            }
17278        } else {
17279            self.write(&output_name);
17280        }
17281
17282        // If no_parens is true and there are no args, output just the function name
17283        // Unless the target dialect requires parens for this function
17284        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
17285            let needs_parens = if func.name.eq_ignore_ascii_case("CURRENT_USER")
17286                || func.name.eq_ignore_ascii_case("SESSION_USER")
17287                || func.name.eq_ignore_ascii_case("SYSTEM_USER")
17288            {
17289                matches!(
17290                    self.config.dialect,
17291                    Some(DialectType::Snowflake)
17292                        | Some(DialectType::Spark)
17293                        | Some(DialectType::Databricks)
17294                        | Some(DialectType::Hive)
17295                )
17296            } else {
17297                false
17298            };
17299            !needs_parens
17300        };
17301        if force_parens {
17302            // Output trailing comments
17303            for comment in &func.trailing_comments {
17304                self.write_space();
17305                self.write_formatted_comment(comment);
17306            }
17307            return Ok(());
17308        }
17309
17310        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
17311        if func.name.eq_ignore_ascii_case("CUBE")
17312            || func.name.eq_ignore_ascii_case("ROLLUP")
17313            || func.name.eq_ignore_ascii_case("GROUPING SETS")
17314        {
17315            self.write(" (");
17316        } else if use_brackets {
17317            self.write("[");
17318        } else {
17319            self.write("(");
17320        }
17321        if func.distinct {
17322            self.write_keyword("DISTINCT");
17323            self.write_space();
17324        }
17325
17326        // Check if arguments should be split onto multiple lines (pretty + too wide)
17327        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
17328            && (func.name.eq_ignore_ascii_case("TABLE")
17329                || func.name.eq_ignore_ascii_case("FLATTEN"));
17330        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
17331        let is_grouping_func = func.name.eq_ignore_ascii_case("GROUPING SETS")
17332            || func.name.eq_ignore_ascii_case("CUBE")
17333            || func.name.eq_ignore_ascii_case("ROLLUP");
17334        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
17335            if is_grouping_func {
17336                true
17337            } else {
17338                // Pre-render arguments to check total width
17339                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
17340                for arg in &func.args {
17341                    let mut temp_gen = Generator::with_arc_config(self.config.clone());
17342                    Arc::make_mut(&mut temp_gen.config).pretty = false; // Don't recurse into pretty
17343                    temp_gen.generate_expression(arg)?;
17344                    expr_strings.push(temp_gen.output);
17345                }
17346                self.too_wide(&expr_strings)
17347            }
17348        } else {
17349            false
17350        };
17351
17352        if should_split {
17353            // Split onto multiple lines
17354            self.write_newline();
17355            self.indent_level += 1;
17356            for (i, arg) in func.args.iter().enumerate() {
17357                self.write_indent();
17358                self.generate_expression(arg)?;
17359                if i + 1 < func.args.len() {
17360                    self.write(",");
17361                }
17362                self.write_newline();
17363            }
17364            self.indent_level -= 1;
17365            self.write_indent();
17366        } else {
17367            // All on one line
17368            for (i, arg) in func.args.iter().enumerate() {
17369                if i > 0 {
17370                    self.write(", ");
17371                }
17372                self.generate_expression(arg)?;
17373            }
17374        }
17375
17376        if use_brackets {
17377            self.write("]");
17378        } else {
17379            self.write(")");
17380        }
17381        // Append WITH ORDINALITY after closing paren for table-valued functions
17382        if has_ordinality {
17383            self.write_space();
17384            self.write_keyword("WITH ORDINALITY");
17385        }
17386        // Output trailing comments
17387        for comment in &func.trailing_comments {
17388            self.write_space();
17389            self.write_formatted_comment(comment);
17390        }
17391        Ok(())
17392    }
17393
17394    fn generate_function_emits(&mut self, fe: &FunctionEmits) -> Result<()> {
17395        self.generate_expression(&fe.this)?;
17396        self.write_keyword(" EMITS ");
17397        self.generate_expression(&fe.emits)?;
17398        Ok(())
17399    }
17400
17401    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
17402        // Normalize function name based on dialect settings
17403        let mut normalized_name = self.normalize_func_name(&func.name);
17404
17405        // Dialect-specific name mappings for aggregate functions
17406        if func.name.eq_ignore_ascii_case("MAX_BY") || func.name.eq_ignore_ascii_case("MIN_BY") {
17407            let is_max = func.name.eq_ignore_ascii_case("MAX_BY");
17408            match self.config.dialect {
17409                Some(DialectType::ClickHouse) => {
17410                    normalized_name = if is_max {
17411                        Cow::Borrowed("argMax")
17412                    } else {
17413                        Cow::Borrowed("argMin")
17414                    };
17415                }
17416                Some(DialectType::DuckDB) => {
17417                    normalized_name = if is_max {
17418                        Cow::Borrowed("ARG_MAX")
17419                    } else {
17420                        Cow::Borrowed("ARG_MIN")
17421                    };
17422                }
17423                _ => {}
17424            }
17425        }
17426        self.write(normalized_name.as_ref());
17427        self.write("(");
17428        if func.distinct {
17429            self.write_keyword("DISTINCT");
17430            self.write_space();
17431        }
17432
17433        // Check if we need to transform multi-arg COUNT DISTINCT
17434        // When dialect doesn't support multi_arg_distinct, transform:
17435        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
17436        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
17437        let needs_multi_arg_transform =
17438            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
17439
17440        if needs_multi_arg_transform {
17441            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
17442            self.write_keyword("CASE");
17443            for arg in &func.args {
17444                self.write_space();
17445                self.write_keyword("WHEN");
17446                self.write_space();
17447                self.generate_expression(arg)?;
17448                self.write_space();
17449                self.write_keyword("IS NULL THEN NULL");
17450            }
17451            self.write_space();
17452            self.write_keyword("ELSE");
17453            self.write(" (");
17454            for (i, arg) in func.args.iter().enumerate() {
17455                if i > 0 {
17456                    self.write(", ");
17457                }
17458                self.generate_expression(arg)?;
17459            }
17460            self.write(")");
17461            self.write_space();
17462            self.write_keyword("END");
17463        } else {
17464            for (i, arg) in func.args.iter().enumerate() {
17465                if i > 0 {
17466                    self.write(", ");
17467                }
17468                self.generate_expression(arg)?;
17469            }
17470        }
17471
17472        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
17473        if self.config.ignore_nulls_in_func
17474            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
17475        {
17476            if let Some(ignore) = func.ignore_nulls {
17477                self.write_space();
17478                if ignore {
17479                    self.write_keyword("IGNORE NULLS");
17480                } else {
17481                    self.write_keyword("RESPECT NULLS");
17482                }
17483            }
17484        }
17485
17486        // ORDER BY inside aggregate
17487        if !func.order_by.is_empty() {
17488            self.write_space();
17489            self.write_keyword("ORDER BY");
17490            self.write_space();
17491            for (i, ord) in func.order_by.iter().enumerate() {
17492                if i > 0 {
17493                    self.write(", ");
17494                }
17495                self.generate_ordered(ord)?;
17496            }
17497        }
17498
17499        // LIMIT inside aggregate
17500        if let Some(limit) = &func.limit {
17501            self.write_space();
17502            self.write_keyword("LIMIT");
17503            self.write_space();
17504            // Check if this is a Tuple representing LIMIT offset, count
17505            if let Expression::Tuple(t) = limit.as_ref() {
17506                if t.expressions.len() == 2 {
17507                    self.generate_expression(&t.expressions[0])?;
17508                    self.write(", ");
17509                    self.generate_expression(&t.expressions[1])?;
17510                } else {
17511                    self.generate_expression(limit)?;
17512                }
17513            } else {
17514                self.generate_expression(limit)?;
17515            }
17516        }
17517
17518        self.write(")");
17519
17520        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
17521        if !self.config.ignore_nulls_in_func
17522            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
17523        {
17524            if let Some(ignore) = func.ignore_nulls {
17525                self.write_space();
17526                if ignore {
17527                    self.write_keyword("IGNORE NULLS");
17528                } else {
17529                    self.write_keyword("RESPECT NULLS");
17530                }
17531            }
17532        }
17533
17534        if let Some(filter) = &func.filter {
17535            self.write_space();
17536            self.write_keyword("FILTER");
17537            self.write("(");
17538            self.write_keyword("WHERE");
17539            self.write_space();
17540            self.generate_expression(filter)?;
17541            self.write(")");
17542        }
17543
17544        Ok(())
17545    }
17546
17547    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
17548        self.generate_expression(&wf.this)?;
17549
17550        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
17551        if let Some(keep) = &wf.keep {
17552            self.write_space();
17553            self.write_keyword("KEEP");
17554            self.write(" (");
17555            self.write_keyword("DENSE_RANK");
17556            self.write_space();
17557            if keep.first {
17558                self.write_keyword("FIRST");
17559            } else {
17560                self.write_keyword("LAST");
17561            }
17562            self.write_space();
17563            self.write_keyword("ORDER BY");
17564            self.write_space();
17565            for (i, ord) in keep.order_by.iter().enumerate() {
17566                if i > 0 {
17567                    self.write(", ");
17568                }
17569                self.generate_ordered(ord)?;
17570            }
17571            self.write(")");
17572        }
17573
17574        // Check if there's any OVER clause content
17575        let has_over = !wf.over.partition_by.is_empty()
17576            || !wf.over.order_by.is_empty()
17577            || wf.over.frame.is_some()
17578            || wf.over.window_name.is_some();
17579
17580        // Only output OVER if there's actual window specification (not just KEEP alone)
17581        if has_over {
17582            self.write_space();
17583            self.write_keyword("OVER");
17584
17585            // Check if this is just a bare named window reference (no parens needed)
17586            let has_specs = !wf.over.partition_by.is_empty()
17587                || !wf.over.order_by.is_empty()
17588                || wf.over.frame.is_some();
17589
17590            if wf.over.window_name.is_some() && !has_specs {
17591                // OVER window_name (without parentheses)
17592                self.write_space();
17593                self.write(&wf.over.window_name.as_ref().unwrap().name);
17594            } else {
17595                // OVER (...) or OVER (window_name ...)
17596                self.write(" (");
17597                self.generate_over(&wf.over)?;
17598                self.write(")");
17599            }
17600        } else if wf.keep.is_none() {
17601            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
17602            self.write_space();
17603            self.write_keyword("OVER");
17604            self.write(" ()");
17605        }
17606
17607        Ok(())
17608    }
17609
17610    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
17611    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
17612        self.generate_expression(&wg.this)?;
17613        self.write_space();
17614        self.write_keyword("WITHIN GROUP");
17615        self.write(" (");
17616        self.write_keyword("ORDER BY");
17617        self.write_space();
17618        for (i, ord) in wg.order_by.iter().enumerate() {
17619            if i > 0 {
17620                self.write(", ");
17621            }
17622            self.generate_ordered(ord)?;
17623        }
17624        self.write(")");
17625        Ok(())
17626    }
17627
17628    /// Generate the contents of an OVER clause (without parentheses)
17629    fn generate_over(&mut self, over: &Over) -> Result<()> {
17630        let mut has_content = false;
17631
17632        // Named window reference
17633        if let Some(name) = &over.window_name {
17634            self.write(&name.name);
17635            has_content = true;
17636        }
17637
17638        // PARTITION BY
17639        if !over.partition_by.is_empty() {
17640            if has_content {
17641                self.write_space();
17642            }
17643            self.write_keyword("PARTITION BY");
17644            self.write_space();
17645            for (i, expr) in over.partition_by.iter().enumerate() {
17646                if i > 0 {
17647                    self.write(", ");
17648                }
17649                self.generate_expression(expr)?;
17650            }
17651            has_content = true;
17652        }
17653
17654        // ORDER BY
17655        if !over.order_by.is_empty() {
17656            if has_content {
17657                self.write_space();
17658            }
17659            self.write_keyword("ORDER BY");
17660            self.write_space();
17661            for (i, ordered) in over.order_by.iter().enumerate() {
17662                if i > 0 {
17663                    self.write(", ");
17664                }
17665                self.generate_ordered(ordered)?;
17666            }
17667            has_content = true;
17668        }
17669
17670        // Window frame
17671        if let Some(frame) = &over.frame {
17672            if has_content {
17673                self.write_space();
17674            }
17675            self.generate_window_frame(frame)?;
17676        }
17677
17678        Ok(())
17679    }
17680
17681    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
17682        // Exasol uses lowercase for frame kind (rows/range/groups)
17683        let lowercase_frame = self.config.lowercase_window_frame_keywords;
17684
17685        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
17686        if !lowercase_frame {
17687            if let Some(kind_text) = &frame.kind_text {
17688                self.write(kind_text);
17689            } else {
17690                match frame.kind {
17691                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
17692                    WindowFrameKind::Range => self.write_keyword("RANGE"),
17693                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
17694                }
17695            }
17696        } else {
17697            match frame.kind {
17698                WindowFrameKind::Rows => self.write("rows"),
17699                WindowFrameKind::Range => self.write("range"),
17700                WindowFrameKind::Groups => self.write("groups"),
17701            }
17702        }
17703
17704        // Use BETWEEN format only when there's an explicit end bound,
17705        // or when normalize_window_frame_between is enabled and the start is a directional bound
17706        self.write_space();
17707        let should_normalize = self.config.normalize_window_frame_between
17708            && frame.end.is_none()
17709            && matches!(
17710                frame.start,
17711                WindowFrameBound::Preceding(_)
17712                    | WindowFrameBound::Following(_)
17713                    | WindowFrameBound::UnboundedPreceding
17714                    | WindowFrameBound::UnboundedFollowing
17715            );
17716
17717        if let Some(end) = &frame.end {
17718            // BETWEEN format: RANGE BETWEEN start AND end
17719            self.write_keyword("BETWEEN");
17720            self.write_space();
17721            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
17722            self.write_space();
17723            self.write_keyword("AND");
17724            self.write_space();
17725            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
17726        } else if should_normalize {
17727            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
17728            self.write_keyword("BETWEEN");
17729            self.write_space();
17730            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
17731            self.write_space();
17732            self.write_keyword("AND");
17733            self.write_space();
17734            self.write_keyword("CURRENT ROW");
17735        } else {
17736            // Single bound format: RANGE CURRENT ROW
17737            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
17738        }
17739
17740        // EXCLUDE clause
17741        if let Some(exclude) = &frame.exclude {
17742            self.write_space();
17743            self.write_keyword("EXCLUDE");
17744            self.write_space();
17745            match exclude {
17746                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
17747                WindowFrameExclude::Group => self.write_keyword("GROUP"),
17748                WindowFrameExclude::Ties => self.write_keyword("TIES"),
17749                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
17750            }
17751        }
17752
17753        Ok(())
17754    }
17755
17756    fn generate_window_frame_bound(
17757        &mut self,
17758        bound: &WindowFrameBound,
17759        side_text: Option<&str>,
17760    ) -> Result<()> {
17761        // Exasol uses lowercase for preceding/following
17762        let lowercase_frame = self.config.lowercase_window_frame_keywords;
17763
17764        match bound {
17765            WindowFrameBound::CurrentRow => {
17766                self.write_keyword("CURRENT ROW");
17767            }
17768            WindowFrameBound::UnboundedPreceding => {
17769                self.write_keyword("UNBOUNDED");
17770                self.write_space();
17771                if lowercase_frame {
17772                    self.write("preceding");
17773                } else if let Some(text) = side_text {
17774                    self.write(text);
17775                } else {
17776                    self.write_keyword("PRECEDING");
17777                }
17778            }
17779            WindowFrameBound::UnboundedFollowing => {
17780                self.write_keyword("UNBOUNDED");
17781                self.write_space();
17782                if lowercase_frame {
17783                    self.write("following");
17784                } else if let Some(text) = side_text {
17785                    self.write(text);
17786                } else {
17787                    self.write_keyword("FOLLOWING");
17788                }
17789            }
17790            WindowFrameBound::Preceding(expr) => {
17791                self.generate_expression(expr)?;
17792                self.write_space();
17793                if lowercase_frame {
17794                    self.write("preceding");
17795                } else if let Some(text) = side_text {
17796                    self.write(text);
17797                } else {
17798                    self.write_keyword("PRECEDING");
17799                }
17800            }
17801            WindowFrameBound::Following(expr) => {
17802                self.generate_expression(expr)?;
17803                self.write_space();
17804                if lowercase_frame {
17805                    self.write("following");
17806                } else if let Some(text) = side_text {
17807                    self.write(text);
17808                } else {
17809                    self.write_keyword("FOLLOWING");
17810                }
17811            }
17812            WindowFrameBound::BarePreceding => {
17813                if lowercase_frame {
17814                    self.write("preceding");
17815                } else if let Some(text) = side_text {
17816                    self.write(text);
17817                } else {
17818                    self.write_keyword("PRECEDING");
17819                }
17820            }
17821            WindowFrameBound::BareFollowing => {
17822                if lowercase_frame {
17823                    self.write("following");
17824                } else if let Some(text) = side_text {
17825                    self.write(text);
17826                } else {
17827                    self.write_keyword("FOLLOWING");
17828                }
17829            }
17830            WindowFrameBound::Value(expr) => {
17831                // Bare numeric bound without PRECEDING/FOLLOWING
17832                self.generate_expression(expr)?;
17833            }
17834        }
17835        Ok(())
17836    }
17837
17838    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
17839        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
17840        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
17841        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
17842            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
17843            && !matches!(&interval.this, Some(Expression::Literal(_)));
17844
17845        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
17846        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
17847        if self.config.single_string_interval {
17848            if let (
17849                Some(Expression::Literal(lit)),
17850                Some(IntervalUnitSpec::Simple {
17851                    ref unit,
17852                    ref use_plural,
17853                }),
17854            ) = (&interval.this, &interval.unit)
17855            {
17856                if let Literal::String(ref val) = lit.as_ref() {
17857                    self.write_keyword("INTERVAL");
17858                    self.write_space();
17859                    let effective_plural = *use_plural && self.config.interval_allows_plural_form;
17860                    let unit_str = self.interval_unit_str(unit, effective_plural);
17861                    self.write("'");
17862                    self.write(val);
17863                    self.write(" ");
17864                    self.write(&unit_str);
17865                    self.write("'");
17866                    return Ok(());
17867                }
17868            }
17869        }
17870
17871        if !skip_interval_keyword {
17872            self.write_keyword("INTERVAL");
17873        }
17874
17875        // Generate value if present
17876        if let Some(ref value) = interval.this {
17877            if !skip_interval_keyword {
17878                self.write_space();
17879            }
17880            // If the value is a complex expression (not a literal/column/function call)
17881            // and there's a unit, wrap it in parentheses
17882            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
17883            let needs_parens = interval.unit.is_some()
17884                && matches!(
17885                    value,
17886                    Expression::Add(_)
17887                        | Expression::Sub(_)
17888                        | Expression::Mul(_)
17889                        | Expression::Div(_)
17890                        | Expression::Mod(_)
17891                        | Expression::BitwiseAnd(_)
17892                        | Expression::BitwiseOr(_)
17893                        | Expression::BitwiseXor(_)
17894                );
17895            if needs_parens {
17896                self.write("(");
17897            }
17898            self.generate_expression(value)?;
17899            if needs_parens {
17900                self.write(")");
17901            }
17902        }
17903
17904        // Generate unit if present
17905        if let Some(ref unit_spec) = interval.unit {
17906            self.write_space();
17907            self.write_interval_unit_spec(unit_spec)?;
17908        }
17909
17910        Ok(())
17911    }
17912
17913    /// Return the string representation of an interval unit
17914    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
17915        match (unit, use_plural) {
17916            (IntervalUnit::Year, false) => "YEAR",
17917            (IntervalUnit::Year, true) => "YEARS",
17918            (IntervalUnit::Quarter, false) => "QUARTER",
17919            (IntervalUnit::Quarter, true) => "QUARTERS",
17920            (IntervalUnit::Month, false) => "MONTH",
17921            (IntervalUnit::Month, true) => "MONTHS",
17922            (IntervalUnit::Week, false) => "WEEK",
17923            (IntervalUnit::Week, true) => "WEEKS",
17924            (IntervalUnit::Day, false) => "DAY",
17925            (IntervalUnit::Day, true) => "DAYS",
17926            (IntervalUnit::Hour, false) => "HOUR",
17927            (IntervalUnit::Hour, true) => "HOURS",
17928            (IntervalUnit::Minute, false) => "MINUTE",
17929            (IntervalUnit::Minute, true) => "MINUTES",
17930            (IntervalUnit::Second, false) => "SECOND",
17931            (IntervalUnit::Second, true) => "SECONDS",
17932            (IntervalUnit::Millisecond, false) => "MILLISECOND",
17933            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
17934            (IntervalUnit::Microsecond, false) => "MICROSECOND",
17935            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
17936            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
17937            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
17938        }
17939    }
17940
17941    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
17942        match unit_spec {
17943            IntervalUnitSpec::Simple { unit, use_plural } => {
17944                // If dialect doesn't allow plural forms, force singular
17945                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
17946                self.write_simple_interval_unit(unit, effective_plural);
17947            }
17948            IntervalUnitSpec::Span(span) => {
17949                self.write_simple_interval_unit(&span.this, false);
17950                self.write_space();
17951                self.write_keyword("TO");
17952                self.write_space();
17953                self.write_simple_interval_unit(&span.expression, false);
17954            }
17955            IntervalUnitSpec::ExprSpan(span) => {
17956                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
17957                self.generate_expression(&span.this)?;
17958                self.write_space();
17959                self.write_keyword("TO");
17960                self.write_space();
17961                self.generate_expression(&span.expression)?;
17962            }
17963            IntervalUnitSpec::Expr(expr) => {
17964                self.generate_expression(expr)?;
17965            }
17966        }
17967        Ok(())
17968    }
17969
17970    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
17971        // Output interval unit, respecting plural preference
17972        match (unit, use_plural) {
17973            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
17974            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
17975            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
17976            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
17977            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
17978            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
17979            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
17980            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
17981            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
17982            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
17983            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
17984            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
17985            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
17986            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
17987            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
17988            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
17989            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
17990            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
17991            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
17992            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
17993            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
17994            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
17995        }
17996    }
17997
17998    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
17999    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
18000    fn write_redshift_date_part(&mut self, expr: &Expression) {
18001        let part_str = self.extract_date_part_string(expr);
18002        if let Some(part) = part_str {
18003            let normalized = self.normalize_date_part(&part);
18004            self.write_keyword(&normalized);
18005        } else {
18006            // If we can't extract a date part string, fall back to generating the expression
18007            let _ = self.generate_expression(expr);
18008        }
18009    }
18010
18011    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
18012    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
18013    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
18014        let part_str = self.extract_date_part_string(expr);
18015        if let Some(part) = part_str {
18016            let normalized = self.normalize_date_part(&part);
18017            self.write("'");
18018            self.write(&normalized);
18019            self.write("'");
18020        } else {
18021            // If we can't extract a date part string, fall back to generating the expression
18022            let _ = self.generate_expression(expr);
18023        }
18024    }
18025
18026    /// Extract date part string from expression (handles string literals and identifiers)
18027    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
18028        match expr {
18029            Expression::Literal(lit)
18030                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
18031            {
18032                let crate::expressions::Literal::String(s) = lit.as_ref() else {
18033                    unreachable!()
18034                };
18035                Some(s.clone())
18036            }
18037            Expression::Identifier(id) => Some(id.name.clone()),
18038            Expression::Var(v) => Some(v.this.clone()),
18039            Expression::Column(col) if col.table.is_none() => {
18040                // Simple column reference without table prefix, treat as identifier
18041                Some(col.name.name.clone())
18042            }
18043            _ => None,
18044        }
18045    }
18046
18047    /// Normalize date part to uppercase singular form
18048    /// days -> DAY, months -> MONTH, etc.
18049    fn normalize_date_part(&self, part: &str) -> String {
18050        let mut buf = [0u8; 64];
18051        let lower: &str = if part.len() <= 64 {
18052            for (i, b) in part.bytes().enumerate() {
18053                buf[i] = b.to_ascii_lowercase();
18054            }
18055            std::str::from_utf8(&buf[..part.len()]).unwrap_or(part)
18056        } else {
18057            return part.to_ascii_uppercase();
18058        };
18059        match lower {
18060            "day" | "days" | "d" => "DAY".to_string(),
18061            "month" | "months" | "mon" | "mm" => "MONTH".to_string(),
18062            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
18063            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
18064            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
18065            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
18066            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
18067            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
18068            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
18069            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
18070            _ => part.to_ascii_uppercase(),
18071        }
18072    }
18073
18074    fn write_datetime_field(&mut self, field: &DateTimeField) {
18075        match field {
18076            DateTimeField::Year => self.write_keyword("YEAR"),
18077            DateTimeField::Month => self.write_keyword("MONTH"),
18078            DateTimeField::Day => self.write_keyword("DAY"),
18079            DateTimeField::Hour => self.write_keyword("HOUR"),
18080            DateTimeField::Minute => self.write_keyword("MINUTE"),
18081            DateTimeField::Second => self.write_keyword("SECOND"),
18082            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
18083            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
18084            DateTimeField::DayOfWeek => {
18085                let name = match self.config.dialect {
18086                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
18087                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => "WEEKDAY",
18088                    _ => "DOW",
18089                };
18090                self.write_keyword(name);
18091            }
18092            DateTimeField::DayOfYear => {
18093                let name = match self.config.dialect {
18094                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
18095                    _ => "DOY",
18096                };
18097                self.write_keyword(name);
18098            }
18099            DateTimeField::Week => self.write_keyword("WEEK"),
18100            DateTimeField::WeekWithModifier(modifier) => {
18101                self.write_keyword("WEEK");
18102                self.write("(");
18103                self.write(modifier);
18104                self.write(")");
18105            }
18106            DateTimeField::Quarter => self.write_keyword("QUARTER"),
18107            DateTimeField::Epoch => self.write_keyword("EPOCH"),
18108            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
18109            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
18110            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
18111            DateTimeField::Date => self.write_keyword("DATE"),
18112            DateTimeField::Time => self.write_keyword("TIME"),
18113            DateTimeField::Custom(name) => self.write(name),
18114        }
18115    }
18116
18117    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
18118    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
18119        match field {
18120            DateTimeField::Year => self.write("year"),
18121            DateTimeField::Month => self.write("month"),
18122            DateTimeField::Day => self.write("day"),
18123            DateTimeField::Hour => self.write("hour"),
18124            DateTimeField::Minute => self.write("minute"),
18125            DateTimeField::Second => self.write("second"),
18126            DateTimeField::Millisecond => self.write("millisecond"),
18127            DateTimeField::Microsecond => self.write("microsecond"),
18128            DateTimeField::DayOfWeek => self.write("dow"),
18129            DateTimeField::DayOfYear => self.write("doy"),
18130            DateTimeField::Week => self.write("week"),
18131            DateTimeField::WeekWithModifier(modifier) => {
18132                self.write("week(");
18133                self.write(modifier);
18134                self.write(")");
18135            }
18136            DateTimeField::Quarter => self.write("quarter"),
18137            DateTimeField::Epoch => self.write("epoch"),
18138            DateTimeField::Timezone => self.write("timezone"),
18139            DateTimeField::TimezoneHour => self.write("timezone_hour"),
18140            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
18141            DateTimeField::Date => self.write("date"),
18142            DateTimeField::Time => self.write("time"),
18143            DateTimeField::Custom(name) => self.write(name),
18144        }
18145    }
18146
18147    // Helper function generators
18148
18149    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
18150        self.write_keyword(name);
18151        self.write("(");
18152        self.generate_expression(arg)?;
18153        self.write(")");
18154        Ok(())
18155    }
18156
18157    /// Generate a unary function, using the original name if available for round-trip preservation
18158    fn generate_unary_func(
18159        &mut self,
18160        default_name: &str,
18161        f: &crate::expressions::UnaryFunc,
18162    ) -> Result<()> {
18163        let name = f.original_name.as_deref().unwrap_or(default_name);
18164        self.write_keyword(name);
18165        self.write("(");
18166        self.generate_expression(&f.this)?;
18167        self.write(")");
18168        Ok(())
18169    }
18170
18171    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
18172    fn generate_sqrt_cbrt(
18173        &mut self,
18174        f: &crate::expressions::UnaryFunc,
18175        func_name: &str,
18176        _op: &str,
18177    ) -> Result<()> {
18178        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
18179        // Always use function syntax for consistency
18180        self.write_keyword(func_name);
18181        self.write("(");
18182        self.generate_expression(&f.this)?;
18183        self.write(")");
18184        Ok(())
18185    }
18186
18187    fn generate_binary_func(
18188        &mut self,
18189        name: &str,
18190        arg1: &Expression,
18191        arg2: &Expression,
18192    ) -> Result<()> {
18193        self.write_keyword(name);
18194        self.write("(");
18195        self.generate_expression(arg1)?;
18196        self.write(", ");
18197        self.generate_expression(arg2)?;
18198        self.write(")");
18199        Ok(())
18200    }
18201
18202    /// Generate CHAR/CHR function with optional USING charset
18203    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
18204    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
18205    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
18206        // Use stored name if available, otherwise default to CHAR
18207        let func_name = f.name.as_deref().unwrap_or("CHAR");
18208        self.write_keyword(func_name);
18209        self.write("(");
18210        for (i, arg) in f.args.iter().enumerate() {
18211            if i > 0 {
18212                self.write(", ");
18213            }
18214            self.generate_expression(arg)?;
18215        }
18216        if let Some(ref charset) = f.charset {
18217            self.write(" ");
18218            self.write_keyword("USING");
18219            self.write(" ");
18220            self.write(charset);
18221        }
18222        self.write(")");
18223        Ok(())
18224    }
18225
18226    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
18227        use crate::dialects::DialectType;
18228
18229        match self.config.dialect {
18230            Some(DialectType::Teradata) => {
18231                // Teradata uses ** operator for exponentiation
18232                self.generate_expression(&f.this)?;
18233                self.write(" ** ");
18234                self.generate_expression(&f.expression)?;
18235                Ok(())
18236            }
18237            _ => {
18238                // Other dialects use POWER function
18239                self.generate_binary_func("POWER", &f.this, &f.expression)
18240            }
18241        }
18242    }
18243
18244    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
18245        self.write_func_name(name);
18246        self.write("(");
18247        for (i, arg) in args.iter().enumerate() {
18248            if i > 0 {
18249                self.write(", ");
18250            }
18251            self.generate_expression(arg)?;
18252        }
18253        self.write(")");
18254        Ok(())
18255    }
18256
18257    // String function generators
18258
18259    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
18260        self.write_keyword("CONCAT_WS");
18261        self.write("(");
18262        self.generate_expression(&f.separator)?;
18263        for expr in &f.expressions {
18264            self.write(", ");
18265            self.generate_expression(expr)?;
18266        }
18267        self.write(")");
18268        Ok(())
18269    }
18270
18271    fn collect_concat_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
18272        if let Expression::Concat(op) = expr {
18273            Self::collect_concat_operands(&op.left, out);
18274            Self::collect_concat_operands(&op.right, out);
18275        } else {
18276            out.push(expr);
18277        }
18278    }
18279
18280    fn generate_mysql_concat_from_concat(&mut self, op: &BinaryOp) -> Result<()> {
18281        let mut operands = Vec::new();
18282        Self::collect_concat_operands(&op.left, &mut operands);
18283        Self::collect_concat_operands(&op.right, &mut operands);
18284
18285        self.write_keyword("CONCAT");
18286        self.write("(");
18287        for (i, operand) in operands.iter().enumerate() {
18288            if i > 0 {
18289                self.write(", ");
18290            }
18291            self.generate_expression(operand)?;
18292        }
18293        self.write(")");
18294        Ok(())
18295    }
18296
18297    fn collect_dpipe_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
18298        if let Expression::DPipe(dpipe) = expr {
18299            Self::collect_dpipe_operands(&dpipe.this, out);
18300            Self::collect_dpipe_operands(&dpipe.expression, out);
18301        } else {
18302            out.push(expr);
18303        }
18304    }
18305
18306    fn generate_mysql_concat_from_dpipe(&mut self, e: &DPipe) -> Result<()> {
18307        let mut operands = Vec::new();
18308        Self::collect_dpipe_operands(&e.this, &mut operands);
18309        Self::collect_dpipe_operands(&e.expression, &mut operands);
18310
18311        self.write_keyword("CONCAT");
18312        self.write("(");
18313        for (i, operand) in operands.iter().enumerate() {
18314            if i > 0 {
18315                self.write(", ");
18316            }
18317            self.generate_expression(operand)?;
18318        }
18319        self.write(")");
18320        Ok(())
18321    }
18322
18323    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
18324        // Oracle uses SUBSTR; most others use SUBSTRING
18325        let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
18326        if is_oracle {
18327            self.write_keyword("SUBSTR");
18328        } else {
18329            self.write_keyword("SUBSTRING");
18330        }
18331        self.write("(");
18332        self.generate_expression(&f.this)?;
18333        // PostgreSQL always uses FROM/FOR syntax
18334        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
18335        // Spark/Hive use comma syntax, not FROM/FOR syntax
18336        let use_comma_syntax = matches!(
18337            self.config.dialect,
18338            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
18339        );
18340        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
18341            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
18342            self.write_space();
18343            self.write_keyword("FROM");
18344            self.write_space();
18345            self.generate_expression(&f.start)?;
18346            if let Some(length) = &f.length {
18347                self.write_space();
18348                self.write_keyword("FOR");
18349                self.write_space();
18350                self.generate_expression(length)?;
18351            }
18352        } else {
18353            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
18354            self.write(", ");
18355            self.generate_expression(&f.start)?;
18356            if let Some(length) = &f.length {
18357                self.write(", ");
18358                self.generate_expression(length)?;
18359            }
18360        }
18361        self.write(")");
18362        Ok(())
18363    }
18364
18365    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
18366        self.write_keyword("OVERLAY");
18367        self.write("(");
18368        self.generate_expression(&f.this)?;
18369        self.write_space();
18370        self.write_keyword("PLACING");
18371        self.write_space();
18372        self.generate_expression(&f.replacement)?;
18373        self.write_space();
18374        self.write_keyword("FROM");
18375        self.write_space();
18376        self.generate_expression(&f.from)?;
18377        if let Some(length) = &f.length {
18378            self.write_space();
18379            self.write_keyword("FOR");
18380            self.write_space();
18381            self.generate_expression(length)?;
18382        }
18383        self.write(")");
18384        Ok(())
18385    }
18386
18387    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
18388        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
18389        // when no characters are specified (PostgreSQL style)
18390        if f.position_explicit && f.characters.is_none() {
18391            match f.position {
18392                TrimPosition::Leading => {
18393                    self.write_keyword("LTRIM");
18394                    self.write("(");
18395                    self.generate_expression(&f.this)?;
18396                    self.write(")");
18397                    return Ok(());
18398                }
18399                TrimPosition::Trailing => {
18400                    self.write_keyword("RTRIM");
18401                    self.write("(");
18402                    self.generate_expression(&f.this)?;
18403                    self.write(")");
18404                    return Ok(());
18405                }
18406                TrimPosition::Both => {
18407                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
18408                    // Fall through to standard TRIM handling
18409                }
18410            }
18411        }
18412
18413        self.write_keyword("TRIM");
18414        self.write("(");
18415        // When BOTH is specified without trim characters, simplify to just TRIM(str)
18416        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
18417        let force_standard = f.characters.is_some()
18418            && !f.sql_standard_syntax
18419            && matches!(
18420                self.config.dialect,
18421                Some(DialectType::Hive)
18422                    | Some(DialectType::Spark)
18423                    | Some(DialectType::Databricks)
18424                    | Some(DialectType::ClickHouse)
18425            );
18426        let use_standard = (f.sql_standard_syntax || force_standard)
18427            && !(f.position_explicit
18428                && f.characters.is_none()
18429                && matches!(f.position, TrimPosition::Both));
18430        if use_standard {
18431            // SQL standard syntax: TRIM(BOTH chars FROM str)
18432            // Only output position if it was explicitly specified
18433            if f.position_explicit {
18434                match f.position {
18435                    TrimPosition::Both => self.write_keyword("BOTH"),
18436                    TrimPosition::Leading => self.write_keyword("LEADING"),
18437                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
18438                }
18439                self.write_space();
18440            }
18441            if let Some(chars) = &f.characters {
18442                self.generate_expression(chars)?;
18443                self.write_space();
18444            }
18445            self.write_keyword("FROM");
18446            self.write_space();
18447            self.generate_expression(&f.this)?;
18448        } else {
18449            // Simple function syntax: TRIM(str) or TRIM(str, chars)
18450            self.generate_expression(&f.this)?;
18451            if let Some(chars) = &f.characters {
18452                self.write(", ");
18453                self.generate_expression(chars)?;
18454            }
18455        }
18456        self.write(")");
18457        Ok(())
18458    }
18459
18460    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
18461        self.write_keyword("REPLACE");
18462        self.write("(");
18463        self.generate_expression(&f.this)?;
18464        self.write(", ");
18465        self.generate_expression(&f.old)?;
18466        self.write(", ");
18467        self.generate_expression(&f.new)?;
18468        self.write(")");
18469        Ok(())
18470    }
18471
18472    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
18473        self.write_keyword(name);
18474        self.write("(");
18475        self.generate_expression(&f.this)?;
18476        self.write(", ");
18477        self.generate_expression(&f.length)?;
18478        self.write(")");
18479        Ok(())
18480    }
18481
18482    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
18483        self.write_keyword("REPEAT");
18484        self.write("(");
18485        self.generate_expression(&f.this)?;
18486        self.write(", ");
18487        self.generate_expression(&f.times)?;
18488        self.write(")");
18489        Ok(())
18490    }
18491
18492    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
18493        self.write_keyword(name);
18494        self.write("(");
18495        self.generate_expression(&f.this)?;
18496        self.write(", ");
18497        self.generate_expression(&f.length)?;
18498        if let Some(fill) = &f.fill {
18499            self.write(", ");
18500            self.generate_expression(fill)?;
18501        }
18502        self.write(")");
18503        Ok(())
18504    }
18505
18506    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
18507        self.write_keyword("SPLIT");
18508        self.write("(");
18509        self.generate_expression(&f.this)?;
18510        self.write(", ");
18511        self.generate_expression(&f.delimiter)?;
18512        self.write(")");
18513        Ok(())
18514    }
18515
18516    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
18517        use crate::dialects::DialectType;
18518        // PostgreSQL uses ~ operator for regex matching
18519        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
18520            self.generate_expression(&f.this)?;
18521            self.write(" ~ ");
18522            self.generate_expression(&f.pattern)?;
18523        } else if matches!(self.config.dialect, Some(DialectType::Exasol)) && f.flags.is_none() {
18524            // Exasol uses REGEXP_LIKE as infix binary operator
18525            self.generate_expression(&f.this)?;
18526            self.write_keyword(" REGEXP_LIKE ");
18527            self.generate_expression(&f.pattern)?;
18528        } else if matches!(
18529            self.config.dialect,
18530            Some(DialectType::SingleStore)
18531                | Some(DialectType::Spark)
18532                | Some(DialectType::Hive)
18533                | Some(DialectType::Databricks)
18534        ) && f.flags.is_none()
18535        {
18536            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
18537            self.generate_expression(&f.this)?;
18538            self.write_keyword(" RLIKE ");
18539            self.generate_expression(&f.pattern)?;
18540        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
18541            // StarRocks uses REGEXP function syntax
18542            self.write_keyword("REGEXP");
18543            self.write("(");
18544            self.generate_expression(&f.this)?;
18545            self.write(", ");
18546            self.generate_expression(&f.pattern)?;
18547            if let Some(flags) = &f.flags {
18548                self.write(", ");
18549                self.generate_expression(flags)?;
18550            }
18551            self.write(")");
18552        } else {
18553            self.write_keyword("REGEXP_LIKE");
18554            self.write("(");
18555            self.generate_expression(&f.this)?;
18556            self.write(", ");
18557            self.generate_expression(&f.pattern)?;
18558            if let Some(flags) = &f.flags {
18559                self.write(", ");
18560                self.generate_expression(flags)?;
18561            }
18562            self.write(")");
18563        }
18564        Ok(())
18565    }
18566
18567    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
18568        self.write_keyword("REGEXP_REPLACE");
18569        self.write("(");
18570        self.generate_expression(&f.this)?;
18571        self.write(", ");
18572        self.generate_expression(&f.pattern)?;
18573        self.write(", ");
18574        self.generate_expression(&f.replacement)?;
18575        if let Some(flags) = &f.flags {
18576            self.write(", ");
18577            self.generate_expression(flags)?;
18578        }
18579        self.write(")");
18580        Ok(())
18581    }
18582
18583    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
18584        self.write_keyword("REGEXP_EXTRACT");
18585        self.write("(");
18586        self.generate_expression(&f.this)?;
18587        self.write(", ");
18588        self.generate_expression(&f.pattern)?;
18589        if let Some(group) = &f.group {
18590            self.write(", ");
18591            self.generate_expression(group)?;
18592        }
18593        self.write(")");
18594        Ok(())
18595    }
18596
18597    // Math function generators
18598
18599    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
18600        self.write_keyword("ROUND");
18601        self.write("(");
18602        self.generate_expression(&f.this)?;
18603        if let Some(decimals) = &f.decimals {
18604            self.write(", ");
18605            self.generate_expression(decimals)?;
18606        }
18607        self.write(")");
18608        Ok(())
18609    }
18610
18611    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
18612        self.write_keyword("FLOOR");
18613        self.write("(");
18614        self.generate_expression(&f.this)?;
18615        // Handle Druid-style FLOOR(time TO unit) syntax
18616        if let Some(to) = &f.to {
18617            self.write(" ");
18618            self.write_keyword("TO");
18619            self.write(" ");
18620            self.generate_expression(to)?;
18621        } else if let Some(scale) = &f.scale {
18622            self.write(", ");
18623            self.generate_expression(scale)?;
18624        }
18625        self.write(")");
18626        Ok(())
18627    }
18628
18629    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
18630        self.write_keyword("CEIL");
18631        self.write("(");
18632        self.generate_expression(&f.this)?;
18633        // Handle Druid-style CEIL(time TO unit) syntax
18634        if let Some(to) = &f.to {
18635            self.write(" ");
18636            self.write_keyword("TO");
18637            self.write(" ");
18638            self.generate_expression(to)?;
18639        } else if let Some(decimals) = &f.decimals {
18640            self.write(", ");
18641            self.generate_expression(decimals)?;
18642        }
18643        self.write(")");
18644        Ok(())
18645    }
18646
18647    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
18648        use crate::expressions::Literal;
18649
18650        if let Some(base) = &f.base {
18651            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
18652            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
18653            if self.is_log_base_none() {
18654                if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "2"))
18655                {
18656                    self.write_func_name("LOG2");
18657                    self.write("(");
18658                    self.generate_expression(&f.this)?;
18659                    self.write(")");
18660                    return Ok(());
18661                } else if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "10"))
18662                {
18663                    self.write_func_name("LOG10");
18664                    self.write("(");
18665                    self.generate_expression(&f.this)?;
18666                    self.write(")");
18667                    return Ok(());
18668                }
18669                // Other bases: fall through to LOG(base, value) — best effort
18670            }
18671
18672            self.write_func_name("LOG");
18673            self.write("(");
18674            if self.is_log_value_first() {
18675                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
18676                self.generate_expression(&f.this)?;
18677                self.write(", ");
18678                self.generate_expression(base)?;
18679            } else {
18680                // Default (PostgreSQL, etc.): LOG(base, value)
18681                self.generate_expression(base)?;
18682                self.write(", ");
18683                self.generate_expression(&f.this)?;
18684            }
18685            self.write(")");
18686        } else {
18687            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
18688            self.write_func_name("LOG");
18689            self.write("(");
18690            self.generate_expression(&f.this)?;
18691            self.write(")");
18692        }
18693        Ok(())
18694    }
18695
18696    /// Whether the target dialect uses LOG(value, base) order (value first).
18697    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
18698    fn is_log_value_first(&self) -> bool {
18699        use crate::dialects::DialectType;
18700        matches!(
18701            self.config.dialect,
18702            Some(DialectType::BigQuery)
18703                | Some(DialectType::TSQL)
18704                | Some(DialectType::Tableau)
18705                | Some(DialectType::Fabric)
18706        )
18707    }
18708
18709    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
18710    /// Presto, Trino, ClickHouse, Athena.
18711    fn is_log_base_none(&self) -> bool {
18712        use crate::dialects::DialectType;
18713        matches!(
18714            self.config.dialect,
18715            Some(DialectType::Presto)
18716                | Some(DialectType::Trino)
18717                | Some(DialectType::ClickHouse)
18718                | Some(DialectType::Athena)
18719        )
18720    }
18721
18722    // Date/time function generators
18723
18724    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
18725        self.write_keyword("CURRENT_TIME");
18726        if let Some(precision) = f.precision {
18727            self.write(&format!("({})", precision));
18728        } else if matches!(
18729            self.config.dialect,
18730            Some(crate::dialects::DialectType::MySQL)
18731                | Some(crate::dialects::DialectType::SingleStore)
18732                | Some(crate::dialects::DialectType::TiDB)
18733        ) {
18734            self.write("()");
18735        }
18736        Ok(())
18737    }
18738
18739    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
18740        use crate::dialects::DialectType;
18741
18742        // Oracle/Redshift SYSDATE handling
18743        if f.sysdate {
18744            match self.config.dialect {
18745                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
18746                    self.write_keyword("SYSDATE");
18747                    return Ok(());
18748                }
18749                Some(DialectType::Snowflake) => {
18750                    // Snowflake uses SYSDATE() function
18751                    self.write_keyword("SYSDATE");
18752                    self.write("()");
18753                    return Ok(());
18754                }
18755                _ => {
18756                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
18757                }
18758            }
18759        }
18760
18761        self.write_keyword("CURRENT_TIMESTAMP");
18762        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
18763        if let Some(precision) = f.precision {
18764            self.write(&format!("({})", precision));
18765        } else if matches!(
18766            self.config.dialect,
18767            Some(crate::dialects::DialectType::MySQL)
18768                | Some(crate::dialects::DialectType::SingleStore)
18769                | Some(crate::dialects::DialectType::TiDB)
18770                | Some(crate::dialects::DialectType::Spark)
18771                | Some(crate::dialects::DialectType::Hive)
18772                | Some(crate::dialects::DialectType::Databricks)
18773                | Some(crate::dialects::DialectType::ClickHouse)
18774                | Some(crate::dialects::DialectType::BigQuery)
18775                | Some(crate::dialects::DialectType::Snowflake)
18776                | Some(crate::dialects::DialectType::Exasol)
18777        ) {
18778            self.write("()");
18779        }
18780        Ok(())
18781    }
18782
18783    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
18784        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
18785        if self.config.dialect == Some(DialectType::Exasol) {
18786            self.write_keyword("CONVERT_TZ");
18787            self.write("(");
18788            self.generate_expression(&f.this)?;
18789            self.write(", 'UTC', ");
18790            self.generate_expression(&f.zone)?;
18791            self.write(")");
18792            return Ok(());
18793        }
18794
18795        self.generate_expression(&f.this)?;
18796        self.write_space();
18797        self.write_keyword("AT TIME ZONE");
18798        self.write_space();
18799        self.generate_expression(&f.zone)?;
18800        Ok(())
18801    }
18802
18803    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
18804        use crate::dialects::DialectType;
18805
18806        // Presto/Trino use DATE_ADD('unit', interval, date) format
18807        // with the interval cast to BIGINT when needed
18808        let is_presto_like = matches!(
18809            self.config.dialect,
18810            Some(DialectType::Presto) | Some(DialectType::Trino)
18811        );
18812
18813        if is_presto_like {
18814            self.write_keyword(name);
18815            self.write("(");
18816            // Unit as string literal
18817            self.write("'");
18818            self.write_simple_interval_unit(&f.unit, false);
18819            self.write("'");
18820            self.write(", ");
18821            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
18822            let needs_cast = !self.returns_integer_type(&f.interval);
18823            if needs_cast {
18824                self.write_keyword("CAST");
18825                self.write("(");
18826            }
18827            self.generate_expression(&f.interval)?;
18828            if needs_cast {
18829                self.write_space();
18830                self.write_keyword("AS");
18831                self.write_space();
18832                self.write_keyword("BIGINT");
18833                self.write(")");
18834            }
18835            self.write(", ");
18836            self.generate_expression(&f.this)?;
18837            self.write(")");
18838        } else {
18839            self.write_keyword(name);
18840            self.write("(");
18841            self.generate_expression(&f.this)?;
18842            self.write(", ");
18843            self.write_keyword("INTERVAL");
18844            self.write_space();
18845            self.generate_expression(&f.interval)?;
18846            self.write_space();
18847            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
18848            self.write(")");
18849        }
18850        Ok(())
18851    }
18852
18853    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
18854    /// This is a heuristic to avoid full type inference
18855    fn returns_integer_type(&self, expr: &Expression) -> bool {
18856        use crate::expressions::{DataType, Literal};
18857        match expr {
18858            // Integer literals (no decimal point)
18859            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
18860                let Literal::Number(n) = lit.as_ref() else {
18861                    unreachable!()
18862                };
18863                !n.contains('.')
18864            }
18865
18866            // FLOOR(x) returns integer if x is integer
18867            Expression::Floor(f) => self.returns_integer_type(&f.this),
18868
18869            // ROUND(x) returns integer if x is integer
18870            Expression::Round(f) => {
18871                // Only if no decimals arg or it's returning an integer
18872                f.decimals.is_none() && self.returns_integer_type(&f.this)
18873            }
18874
18875            // SIGN returns integer if input is integer
18876            Expression::Sign(f) => self.returns_integer_type(&f.this),
18877
18878            // ABS returns the same type as input
18879            Expression::Abs(f) => self.returns_integer_type(&f.this),
18880
18881            // Arithmetic operations on integers return integers
18882            Expression::Mul(op) => {
18883                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
18884            }
18885            Expression::Add(op) => {
18886                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
18887            }
18888            Expression::Sub(op) => {
18889                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
18890            }
18891            Expression::Mod(op) => self.returns_integer_type(&op.left),
18892
18893            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
18894            Expression::Cast(c) => matches!(
18895                &c.to,
18896                DataType::BigInt { .. }
18897                    | DataType::Int { .. }
18898                    | DataType::SmallInt { .. }
18899                    | DataType::TinyInt { .. }
18900            ),
18901
18902            // Negation: -x returns integer if x is integer
18903            Expression::Neg(op) => self.returns_integer_type(&op.this),
18904
18905            // Parenthesized expression
18906            Expression::Paren(p) => self.returns_integer_type(&p.this),
18907
18908            // Column references and most expressions are assumed to need casting
18909            // since we don't have full type information
18910            _ => false,
18911        }
18912    }
18913
18914    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
18915        self.write_keyword("DATEDIFF");
18916        self.write("(");
18917        if let Some(unit) = &f.unit {
18918            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
18919            self.write(", ");
18920        }
18921        self.generate_expression(&f.this)?;
18922        self.write(", ");
18923        self.generate_expression(&f.expression)?;
18924        self.write(")");
18925        Ok(())
18926    }
18927
18928    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
18929        self.write_keyword("DATE_TRUNC");
18930        self.write("('");
18931        self.write_datetime_field(&f.unit);
18932        self.write("', ");
18933        self.generate_expression(&f.this)?;
18934        self.write(")");
18935        Ok(())
18936    }
18937
18938    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
18939        use crate::dialects::DialectType;
18940        use crate::expressions::DateTimeField;
18941
18942        self.write_keyword("LAST_DAY");
18943        self.write("(");
18944        self.generate_expression(&f.this)?;
18945        if let Some(unit) = &f.unit {
18946            self.write(", ");
18947            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
18948            // WEEK(SUNDAY) -> WEEK
18949            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
18950                if let DateTimeField::WeekWithModifier(_) = unit {
18951                    self.write_keyword("WEEK");
18952                } else {
18953                    self.write_datetime_field(unit);
18954                }
18955            } else {
18956                self.write_datetime_field(unit);
18957            }
18958        }
18959        self.write(")");
18960        Ok(())
18961    }
18962
18963    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
18964        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
18965        if matches!(
18966            self.config.dialect,
18967            Some(DialectType::TSQL) | Some(DialectType::Fabric)
18968        ) {
18969            self.write_keyword("DATEPART");
18970            self.write("(");
18971            self.write_datetime_field(&f.field);
18972            self.write(", ");
18973            self.generate_expression(&f.this)?;
18974            self.write(")");
18975            return Ok(());
18976        }
18977        self.write_keyword("EXTRACT");
18978        self.write("(");
18979        // Hive/Spark use lowercase datetime fields in EXTRACT
18980        if matches!(
18981            self.config.dialect,
18982            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
18983        ) {
18984            self.write_datetime_field_lower(&f.field);
18985        } else {
18986            self.write_datetime_field(&f.field);
18987        }
18988        self.write_space();
18989        self.write_keyword("FROM");
18990        self.write_space();
18991        self.generate_expression(&f.this)?;
18992        self.write(")");
18993        Ok(())
18994    }
18995
18996    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
18997        self.write_keyword("TO_DATE");
18998        self.write("(");
18999        self.generate_expression(&f.this)?;
19000        if let Some(format) = &f.format {
19001            self.write(", ");
19002            self.generate_expression(format)?;
19003        }
19004        self.write(")");
19005        Ok(())
19006    }
19007
19008    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
19009        self.write_keyword("TO_TIMESTAMP");
19010        self.write("(");
19011        self.generate_expression(&f.this)?;
19012        if let Some(format) = &f.format {
19013            self.write(", ");
19014            self.generate_expression(format)?;
19015        }
19016        self.write(")");
19017        Ok(())
19018    }
19019
19020    // Control flow function generators
19021
19022    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
19023        use crate::dialects::DialectType;
19024
19025        // Generic mode: normalize IF to CASE WHEN
19026        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
19027            self.write_keyword("CASE WHEN");
19028            self.write_space();
19029            self.generate_expression(&f.condition)?;
19030            self.write_space();
19031            self.write_keyword("THEN");
19032            self.write_space();
19033            self.generate_expression(&f.true_value)?;
19034            if let Some(false_val) = &f.false_value {
19035                self.write_space();
19036                self.write_keyword("ELSE");
19037                self.write_space();
19038                self.generate_expression(false_val)?;
19039            }
19040            self.write_space();
19041            self.write_keyword("END");
19042            return Ok(());
19043        }
19044
19045        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
19046        if self.config.dialect == Some(DialectType::Exasol) {
19047            self.write_keyword("IF");
19048            self.write_space();
19049            self.generate_expression(&f.condition)?;
19050            self.write_space();
19051            self.write_keyword("THEN");
19052            self.write_space();
19053            self.generate_expression(&f.true_value)?;
19054            if let Some(false_val) = &f.false_value {
19055                self.write_space();
19056                self.write_keyword("ELSE");
19057                self.write_space();
19058                self.generate_expression(false_val)?;
19059            }
19060            self.write_space();
19061            self.write_keyword("ENDIF");
19062            return Ok(());
19063        }
19064
19065        // Choose function name based on target dialect
19066        let func_name = match self.config.dialect {
19067            Some(DialectType::Snowflake) => "IFF",
19068            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
19069            Some(DialectType::Drill) => "`IF`",
19070            _ => "IF",
19071        };
19072        self.write(func_name);
19073        self.write("(");
19074        self.generate_expression(&f.condition)?;
19075        self.write(", ");
19076        self.generate_expression(&f.true_value)?;
19077        if let Some(false_val) = &f.false_value {
19078            self.write(", ");
19079            self.generate_expression(false_val)?;
19080        }
19081        self.write(")");
19082        Ok(())
19083    }
19084
19085    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
19086        self.write_keyword("NVL2");
19087        self.write("(");
19088        self.generate_expression(&f.this)?;
19089        self.write(", ");
19090        self.generate_expression(&f.true_value)?;
19091        self.write(", ");
19092        self.generate_expression(&f.false_value)?;
19093        self.write(")");
19094        Ok(())
19095    }
19096
19097    // Typed aggregate function generators
19098
19099    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
19100        // Use normalize_functions for COUNT to respect ClickHouse case preservation
19101        let count_name = match self.config.normalize_functions {
19102            NormalizeFunctions::Upper => "COUNT".to_string(),
19103            NormalizeFunctions::Lower => "count".to_string(),
19104            NormalizeFunctions::None => f
19105                .original_name
19106                .clone()
19107                .unwrap_or_else(|| "COUNT".to_string()),
19108        };
19109        self.write(&count_name);
19110        self.write("(");
19111        if f.distinct {
19112            self.write_keyword("DISTINCT");
19113            self.write_space();
19114        }
19115        if f.star {
19116            self.write("*");
19117        } else if let Some(ref expr) = f.this {
19118            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
19119            if let Expression::Tuple(tuple) = expr {
19120                // Check if we need to transform multi-arg COUNT DISTINCT
19121                // When dialect doesn't support multi_arg_distinct, transform:
19122                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
19123                let needs_transform =
19124                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
19125
19126                if needs_transform {
19127                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
19128                    self.write_keyword("CASE");
19129                    for e in &tuple.expressions {
19130                        self.write_space();
19131                        self.write_keyword("WHEN");
19132                        self.write_space();
19133                        self.generate_expression(e)?;
19134                        self.write_space();
19135                        self.write_keyword("IS NULL THEN NULL");
19136                    }
19137                    self.write_space();
19138                    self.write_keyword("ELSE");
19139                    self.write(" (");
19140                    for (i, e) in tuple.expressions.iter().enumerate() {
19141                        if i > 0 {
19142                            self.write(", ");
19143                        }
19144                        self.generate_expression(e)?;
19145                    }
19146                    self.write(")");
19147                    self.write_space();
19148                    self.write_keyword("END");
19149                } else {
19150                    for (i, e) in tuple.expressions.iter().enumerate() {
19151                        if i > 0 {
19152                            self.write(", ");
19153                        }
19154                        self.generate_expression(e)?;
19155                    }
19156                }
19157            } else {
19158                self.generate_expression(expr)?;
19159            }
19160        }
19161        // RESPECT NULLS / IGNORE NULLS
19162        if let Some(ignore) = f.ignore_nulls {
19163            self.write_space();
19164            if ignore {
19165                self.write_keyword("IGNORE NULLS");
19166            } else {
19167                self.write_keyword("RESPECT NULLS");
19168            }
19169        }
19170        self.write(")");
19171        if let Some(ref filter) = f.filter {
19172            self.write_space();
19173            self.write_keyword("FILTER");
19174            self.write("(");
19175            self.write_keyword("WHERE");
19176            self.write_space();
19177            self.generate_expression(filter)?;
19178            self.write(")");
19179        }
19180        Ok(())
19181    }
19182
19183    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
19184        // Apply function name normalization based on config
19185        let func_name: Cow<'_, str> = match self.config.normalize_functions {
19186            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
19187            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
19188            NormalizeFunctions::None => {
19189                // Use the original function name from parsing if available,
19190                // otherwise fall back to lowercase of the hardcoded constant
19191                if let Some(ref original) = f.name {
19192                    Cow::Owned(original.clone())
19193                } else {
19194                    Cow::Owned(name.to_ascii_lowercase())
19195                }
19196            }
19197        };
19198        self.write(func_name.as_ref());
19199        self.write("(");
19200        if f.distinct {
19201            self.write_keyword("DISTINCT");
19202            self.write_space();
19203        }
19204        // Skip generating the expression if it's a NULL placeholder for zero-arg aggregates like MODE()
19205        if !matches!(f.this, Expression::Null(_)) {
19206            self.generate_expression(&f.this)?;
19207        }
19208        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
19209        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
19210        if self.config.ignore_nulls_in_func
19211            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
19212        {
19213            match f.ignore_nulls {
19214                Some(true) => {
19215                    self.write_space();
19216                    self.write_keyword("IGNORE NULLS");
19217                }
19218                Some(false) => {
19219                    self.write_space();
19220                    self.write_keyword("RESPECT NULLS");
19221                }
19222                None => {}
19223            }
19224        }
19225        // Generate HAVING MAX/MIN if present (BigQuery syntax)
19226        // e.g., ANY_VALUE(fruit HAVING MAX sold)
19227        if let Some((ref expr, is_max)) = f.having_max {
19228            self.write_space();
19229            self.write_keyword("HAVING");
19230            self.write_space();
19231            if is_max {
19232                self.write_keyword("MAX");
19233            } else {
19234                self.write_keyword("MIN");
19235            }
19236            self.write_space();
19237            self.generate_expression(expr)?;
19238        }
19239        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
19240        if !f.order_by.is_empty() {
19241            self.write_space();
19242            self.write_keyword("ORDER BY");
19243            self.write_space();
19244            for (i, ord) in f.order_by.iter().enumerate() {
19245                if i > 0 {
19246                    self.write(", ");
19247                }
19248                self.generate_ordered(ord)?;
19249            }
19250        }
19251        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
19252        if let Some(ref limit) = f.limit {
19253            self.write_space();
19254            self.write_keyword("LIMIT");
19255            self.write_space();
19256            // Check if this is a Tuple representing LIMIT offset, count
19257            if let Expression::Tuple(t) = limit.as_ref() {
19258                if t.expressions.len() == 2 {
19259                    self.generate_expression(&t.expressions[0])?;
19260                    self.write(", ");
19261                    self.generate_expression(&t.expressions[1])?;
19262                } else {
19263                    self.generate_expression(limit)?;
19264                }
19265            } else {
19266                self.generate_expression(limit)?;
19267            }
19268        }
19269        self.write(")");
19270        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
19271        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
19272        if !self.config.ignore_nulls_in_func
19273            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
19274        {
19275            match f.ignore_nulls {
19276                Some(true) => {
19277                    self.write_space();
19278                    self.write_keyword("IGNORE NULLS");
19279                }
19280                Some(false) => {
19281                    self.write_space();
19282                    self.write_keyword("RESPECT NULLS");
19283                }
19284                None => {}
19285            }
19286        }
19287        if let Some(ref filter) = f.filter {
19288            self.write_space();
19289            self.write_keyword("FILTER");
19290            self.write("(");
19291            self.write_keyword("WHERE");
19292            self.write_space();
19293            self.generate_expression(filter)?;
19294            self.write(")");
19295        }
19296        Ok(())
19297    }
19298
19299    /// Generate FIRST/LAST aggregate functions with Hive/Spark2-style boolean argument
19300    /// for IGNORE NULLS. In Hive/Spark2, `FIRST(col) IGNORE NULLS` is written as `FIRST(col, TRUE)`.
19301    fn generate_agg_func_with_ignore_nulls_bool(&mut self, name: &str, f: &AggFunc) -> Result<()> {
19302        // For Hive/Spark2 dialects, convert IGNORE NULLS to boolean TRUE argument
19303        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
19304            // Create a modified copy without ignore_nulls, add TRUE as part of the output
19305            let func_name: Cow<'_, str> = match self.config.normalize_functions {
19306                NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
19307                NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
19308                NormalizeFunctions::None => {
19309                    if let Some(ref original) = f.name {
19310                        Cow::Owned(original.clone())
19311                    } else {
19312                        Cow::Owned(name.to_ascii_lowercase())
19313                    }
19314                }
19315            };
19316            self.write(func_name.as_ref());
19317            self.write("(");
19318            if f.distinct {
19319                self.write_keyword("DISTINCT");
19320                self.write_space();
19321            }
19322            if !matches!(f.this, Expression::Null(_)) {
19323                self.generate_expression(&f.this)?;
19324            }
19325            self.write(", ");
19326            self.write_keyword("TRUE");
19327            self.write(")");
19328            return Ok(());
19329        }
19330        self.generate_agg_func(name, f)
19331    }
19332
19333    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
19334        self.write_keyword("GROUP_CONCAT");
19335        self.write("(");
19336        if f.distinct {
19337            self.write_keyword("DISTINCT");
19338            self.write_space();
19339        }
19340        self.generate_expression(&f.this)?;
19341        if let Some(ref order_by) = f.order_by {
19342            self.write_space();
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        }
19352        if let Some(ref sep) = f.separator {
19353            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
19354            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
19355            if matches!(
19356                self.config.dialect,
19357                Some(crate::dialects::DialectType::SQLite)
19358            ) {
19359                self.write(", ");
19360                self.generate_expression(sep)?;
19361            } else {
19362                self.write_space();
19363                self.write_keyword("SEPARATOR");
19364                self.write_space();
19365                self.generate_expression(sep)?;
19366            }
19367        }
19368        if let Some(ref limit) = f.limit {
19369            self.write_space();
19370            self.write_keyword("LIMIT");
19371            self.write_space();
19372            self.generate_expression(limit)?;
19373        }
19374        self.write(")");
19375        if let Some(ref filter) = f.filter {
19376            self.write_space();
19377            self.write_keyword("FILTER");
19378            self.write("(");
19379            self.write_keyword("WHERE");
19380            self.write_space();
19381            self.generate_expression(filter)?;
19382            self.write(")");
19383        }
19384        Ok(())
19385    }
19386
19387    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
19388        let is_tsql = matches!(
19389            self.config.dialect,
19390            Some(crate::dialects::DialectType::TSQL)
19391        );
19392        self.write_keyword("STRING_AGG");
19393        self.write("(");
19394        if f.distinct {
19395            self.write_keyword("DISTINCT");
19396            self.write_space();
19397        }
19398        self.generate_expression(&f.this)?;
19399        if let Some(ref separator) = f.separator {
19400            self.write(", ");
19401            self.generate_expression(separator)?;
19402        }
19403        // For TSQL, ORDER BY goes in WITHIN GROUP clause after the closing paren
19404        if !is_tsql {
19405            if let Some(ref order_by) = f.order_by {
19406                self.write_space();
19407                self.write_keyword("ORDER BY");
19408                self.write_space();
19409                for (i, ord) in order_by.iter().enumerate() {
19410                    if i > 0 {
19411                        self.write(", ");
19412                    }
19413                    self.generate_ordered(ord)?;
19414                }
19415            }
19416        }
19417        if let Some(ref limit) = f.limit {
19418            self.write_space();
19419            self.write_keyword("LIMIT");
19420            self.write_space();
19421            self.generate_expression(limit)?;
19422        }
19423        self.write(")");
19424        // TSQL uses WITHIN GROUP (ORDER BY ...) after the function call
19425        if is_tsql {
19426            if let Some(ref order_by) = f.order_by {
19427                self.write_space();
19428                self.write_keyword("WITHIN GROUP");
19429                self.write(" (");
19430                self.write_keyword("ORDER BY");
19431                self.write_space();
19432                for (i, ord) in order_by.iter().enumerate() {
19433                    if i > 0 {
19434                        self.write(", ");
19435                    }
19436                    self.generate_ordered(ord)?;
19437                }
19438                self.write(")");
19439            }
19440        }
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_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
19454        use crate::dialects::DialectType;
19455        self.write_keyword("LISTAGG");
19456        self.write("(");
19457        if f.distinct {
19458            self.write_keyword("DISTINCT");
19459            self.write_space();
19460        }
19461        self.generate_expression(&f.this)?;
19462        if let Some(ref sep) = f.separator {
19463            self.write(", ");
19464            self.generate_expression(sep)?;
19465        } else if matches!(
19466            self.config.dialect,
19467            Some(DialectType::Trino) | Some(DialectType::Presto)
19468        ) {
19469            // Trino/Presto require explicit separator; default to ','
19470            self.write(", ','");
19471        }
19472        if let Some(ref overflow) = f.on_overflow {
19473            self.write_space();
19474            self.write_keyword("ON OVERFLOW");
19475            self.write_space();
19476            match overflow {
19477                ListAggOverflow::Error => self.write_keyword("ERROR"),
19478                ListAggOverflow::Truncate { filler, with_count } => {
19479                    self.write_keyword("TRUNCATE");
19480                    if let Some(ref fill) = filler {
19481                        self.write_space();
19482                        self.generate_expression(fill)?;
19483                    }
19484                    if *with_count {
19485                        self.write_space();
19486                        self.write_keyword("WITH COUNT");
19487                    } else {
19488                        self.write_space();
19489                        self.write_keyword("WITHOUT COUNT");
19490                    }
19491                }
19492            }
19493        }
19494        self.write(")");
19495        if let Some(ref order_by) = f.order_by {
19496            self.write_space();
19497            self.write_keyword("WITHIN GROUP");
19498            self.write(" (");
19499            self.write_keyword("ORDER BY");
19500            self.write_space();
19501            for (i, ord) in order_by.iter().enumerate() {
19502                if i > 0 {
19503                    self.write(", ");
19504                }
19505                self.generate_ordered(ord)?;
19506            }
19507            self.write(")");
19508        }
19509        if let Some(ref filter) = f.filter {
19510            self.write_space();
19511            self.write_keyword("FILTER");
19512            self.write("(");
19513            self.write_keyword("WHERE");
19514            self.write_space();
19515            self.generate_expression(filter)?;
19516            self.write(")");
19517        }
19518        Ok(())
19519    }
19520
19521    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
19522        self.write_keyword("SUM_IF");
19523        self.write("(");
19524        self.generate_expression(&f.this)?;
19525        self.write(", ");
19526        self.generate_expression(&f.condition)?;
19527        self.write(")");
19528        if let Some(ref filter) = f.filter {
19529            self.write_space();
19530            self.write_keyword("FILTER");
19531            self.write("(");
19532            self.write_keyword("WHERE");
19533            self.write_space();
19534            self.generate_expression(filter)?;
19535            self.write(")");
19536        }
19537        Ok(())
19538    }
19539
19540    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
19541        self.write_keyword("APPROX_PERCENTILE");
19542        self.write("(");
19543        self.generate_expression(&f.this)?;
19544        self.write(", ");
19545        self.generate_expression(&f.percentile)?;
19546        if let Some(ref acc) = f.accuracy {
19547            self.write(", ");
19548            self.generate_expression(acc)?;
19549        }
19550        self.write(")");
19551        if let Some(ref filter) = f.filter {
19552            self.write_space();
19553            self.write_keyword("FILTER");
19554            self.write("(");
19555            self.write_keyword("WHERE");
19556            self.write_space();
19557            self.generate_expression(filter)?;
19558            self.write(")");
19559        }
19560        Ok(())
19561    }
19562
19563    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
19564        self.write_keyword(name);
19565        self.write("(");
19566        self.generate_expression(&f.percentile)?;
19567        self.write(")");
19568        if let Some(ref order_by) = f.order_by {
19569            self.write_space();
19570            self.write_keyword("WITHIN GROUP");
19571            self.write(" (");
19572            self.write_keyword("ORDER BY");
19573            self.write_space();
19574            self.generate_expression(&f.this)?;
19575            for ord in order_by.iter() {
19576                if ord.desc {
19577                    self.write_space();
19578                    self.write_keyword("DESC");
19579                }
19580            }
19581            self.write(")");
19582        }
19583        if let Some(ref filter) = f.filter {
19584            self.write_space();
19585            self.write_keyword("FILTER");
19586            self.write("(");
19587            self.write_keyword("WHERE");
19588            self.write_space();
19589            self.generate_expression(filter)?;
19590            self.write(")");
19591        }
19592        Ok(())
19593    }
19594
19595    // Window function generators
19596
19597    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
19598        self.write_keyword("NTILE");
19599        self.write("(");
19600        if let Some(num_buckets) = &f.num_buckets {
19601            self.generate_expression(num_buckets)?;
19602        }
19603        if let Some(order_by) = &f.order_by {
19604            self.write_keyword(" ORDER BY ");
19605            for (i, ob) in order_by.iter().enumerate() {
19606                if i > 0 {
19607                    self.write(", ");
19608                }
19609                self.generate_ordered(ob)?;
19610            }
19611        }
19612        self.write(")");
19613        Ok(())
19614    }
19615
19616    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
19617        self.write_keyword(name);
19618        self.write("(");
19619        self.generate_expression(&f.this)?;
19620        if let Some(ref offset) = f.offset {
19621            self.write(", ");
19622            self.generate_expression(offset)?;
19623            if let Some(ref default) = f.default {
19624                self.write(", ");
19625                self.generate_expression(default)?;
19626            }
19627        }
19628        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
19629        if self.config.ignore_nulls_in_func {
19630            match f.ignore_nulls {
19631                Some(true) => {
19632                    self.write_space();
19633                    self.write_keyword("IGNORE NULLS");
19634                }
19635                Some(false) => {
19636                    self.write_space();
19637                    self.write_keyword("RESPECT NULLS");
19638                }
19639                None => {}
19640            }
19641        }
19642        self.write(")");
19643        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
19644        if !self.config.ignore_nulls_in_func {
19645            match f.ignore_nulls {
19646                Some(true) => {
19647                    self.write_space();
19648                    self.write_keyword("IGNORE NULLS");
19649                }
19650                Some(false) => {
19651                    self.write_space();
19652                    self.write_keyword("RESPECT NULLS");
19653                }
19654                None => {}
19655            }
19656        }
19657        Ok(())
19658    }
19659
19660    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
19661        self.write_keyword(name);
19662        self.write("(");
19663        self.generate_expression(&f.this)?;
19664        // ORDER BY inside parens (e.g., DuckDB: LAST_VALUE(x ORDER BY x))
19665        if !f.order_by.is_empty() {
19666            self.write_space();
19667            self.write_keyword("ORDER BY");
19668            self.write_space();
19669            for (i, ordered) in f.order_by.iter().enumerate() {
19670                if i > 0 {
19671                    self.write(", ");
19672                }
19673                self.generate_ordered(ordered)?;
19674            }
19675        }
19676        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
19677        if self.config.ignore_nulls_in_func {
19678            match f.ignore_nulls {
19679                Some(true) => {
19680                    self.write_space();
19681                    self.write_keyword("IGNORE NULLS");
19682                }
19683                Some(false) => {
19684                    self.write_space();
19685                    self.write_keyword("RESPECT NULLS");
19686                }
19687                None => {}
19688            }
19689        }
19690        self.write(")");
19691        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
19692        if !self.config.ignore_nulls_in_func {
19693            match f.ignore_nulls {
19694                Some(true) => {
19695                    self.write_space();
19696                    self.write_keyword("IGNORE NULLS");
19697                }
19698                Some(false) => {
19699                    self.write_space();
19700                    self.write_keyword("RESPECT NULLS");
19701                }
19702                None => {}
19703            }
19704        }
19705        Ok(())
19706    }
19707
19708    /// Generate FIRST_VALUE/LAST_VALUE with Hive/Spark2-style boolean argument for IGNORE NULLS.
19709    /// In Hive/Spark2, `FIRST_VALUE(col) IGNORE NULLS` is written as `FIRST_VALUE(col, TRUE)`.
19710    fn generate_value_func_with_ignore_nulls_bool(
19711        &mut self,
19712        name: &str,
19713        f: &ValueFunc,
19714    ) -> Result<()> {
19715        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
19716            self.write_keyword(name);
19717            self.write("(");
19718            self.generate_expression(&f.this)?;
19719            self.write(", ");
19720            self.write_keyword("TRUE");
19721            self.write(")");
19722            return Ok(());
19723        }
19724        self.generate_value_func(name, f)
19725    }
19726
19727    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
19728        self.write_keyword("NTH_VALUE");
19729        self.write("(");
19730        self.generate_expression(&f.this)?;
19731        self.write(", ");
19732        self.generate_expression(&f.offset)?;
19733        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
19734        if self.config.ignore_nulls_in_func {
19735            match f.ignore_nulls {
19736                Some(true) => {
19737                    self.write_space();
19738                    self.write_keyword("IGNORE NULLS");
19739                }
19740                Some(false) => {
19741                    self.write_space();
19742                    self.write_keyword("RESPECT NULLS");
19743                }
19744                None => {}
19745            }
19746        }
19747        self.write(")");
19748        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
19749        if matches!(
19750            self.config.dialect,
19751            Some(crate::dialects::DialectType::Snowflake)
19752        ) {
19753            match f.from_first {
19754                Some(true) => {
19755                    self.write_space();
19756                    self.write_keyword("FROM FIRST");
19757                }
19758                Some(false) => {
19759                    self.write_space();
19760                    self.write_keyword("FROM LAST");
19761                }
19762                None => {}
19763            }
19764        }
19765        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
19766        if !self.config.ignore_nulls_in_func {
19767            match f.ignore_nulls {
19768                Some(true) => {
19769                    self.write_space();
19770                    self.write_keyword("IGNORE NULLS");
19771                }
19772                Some(false) => {
19773                    self.write_space();
19774                    self.write_keyword("RESPECT NULLS");
19775                }
19776                None => {}
19777            }
19778        }
19779        Ok(())
19780    }
19781
19782    // Additional string function generators
19783
19784    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
19785        // Standard syntax: POSITION(substr IN str)
19786        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
19787        if matches!(
19788            self.config.dialect,
19789            Some(crate::dialects::DialectType::ClickHouse)
19790        ) {
19791            self.write_keyword("POSITION");
19792            self.write("(");
19793            self.generate_expression(&f.string)?;
19794            self.write(", ");
19795            self.generate_expression(&f.substring)?;
19796            if let Some(ref start) = f.start {
19797                self.write(", ");
19798                self.generate_expression(start)?;
19799            }
19800            self.write(")");
19801            return Ok(());
19802        }
19803
19804        self.write_keyword("POSITION");
19805        self.write("(");
19806        self.generate_expression(&f.substring)?;
19807        self.write_space();
19808        self.write_keyword("IN");
19809        self.write_space();
19810        self.generate_expression(&f.string)?;
19811        if let Some(ref start) = f.start {
19812            self.write(", ");
19813            self.generate_expression(start)?;
19814        }
19815        self.write(")");
19816        Ok(())
19817    }
19818
19819    // Additional math function generators
19820
19821    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
19822        // Teradata RANDOM(lower, upper)
19823        if f.lower.is_some() || f.upper.is_some() {
19824            self.write_keyword("RANDOM");
19825            self.write("(");
19826            if let Some(ref lower) = f.lower {
19827                self.generate_expression(lower)?;
19828            }
19829            if let Some(ref upper) = f.upper {
19830                self.write(", ");
19831                self.generate_expression(upper)?;
19832            }
19833            self.write(")");
19834            return Ok(());
19835        }
19836        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
19837        let func_name = match self.config.dialect {
19838            Some(crate::dialects::DialectType::Snowflake)
19839            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
19840            _ => "RAND",
19841        };
19842        self.write_keyword(func_name);
19843        self.write("(");
19844        // DuckDB doesn't support seeded RANDOM, so skip the seed
19845        if !matches!(
19846            self.config.dialect,
19847            Some(crate::dialects::DialectType::DuckDB)
19848        ) {
19849            if let Some(ref seed) = f.seed {
19850                self.generate_expression(seed)?;
19851            }
19852        }
19853        self.write(")");
19854        Ok(())
19855    }
19856
19857    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
19858        self.write_keyword("TRUNCATE");
19859        self.write("(");
19860        self.generate_expression(&f.this)?;
19861        if let Some(ref decimals) = f.decimals {
19862            self.write(", ");
19863            self.generate_expression(decimals)?;
19864        }
19865        self.write(")");
19866        Ok(())
19867    }
19868
19869    // Control flow generators
19870
19871    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
19872        self.write_keyword("DECODE");
19873        self.write("(");
19874        self.generate_expression(&f.this)?;
19875        for (search, result) in &f.search_results {
19876            self.write(", ");
19877            self.generate_expression(search)?;
19878            self.write(", ");
19879            self.generate_expression(result)?;
19880        }
19881        if let Some(ref default) = f.default {
19882            self.write(", ");
19883            self.generate_expression(default)?;
19884        }
19885        self.write(")");
19886        Ok(())
19887    }
19888
19889    // Date/time function generators
19890
19891    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
19892        self.write_keyword(name);
19893        self.write("(");
19894        self.generate_expression(&f.this)?;
19895        self.write(", ");
19896        self.generate_expression(&f.format)?;
19897        self.write(")");
19898        Ok(())
19899    }
19900
19901    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
19902        self.write_keyword("FROM_UNIXTIME");
19903        self.write("(");
19904        self.generate_expression(&f.this)?;
19905        if let Some(ref format) = f.format {
19906            self.write(", ");
19907            self.generate_expression(format)?;
19908        }
19909        self.write(")");
19910        Ok(())
19911    }
19912
19913    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
19914        self.write_keyword("UNIX_TIMESTAMP");
19915        self.write("(");
19916        if let Some(ref expr) = f.this {
19917            self.generate_expression(expr)?;
19918            if let Some(ref format) = f.format {
19919                self.write(", ");
19920                self.generate_expression(format)?;
19921            }
19922        } else if matches!(
19923            self.config.dialect,
19924            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
19925        ) {
19926            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
19927            self.write_keyword("CURRENT_TIMESTAMP");
19928            self.write("()");
19929        }
19930        self.write(")");
19931        Ok(())
19932    }
19933
19934    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
19935        self.write_keyword("MAKE_DATE");
19936        self.write("(");
19937        self.generate_expression(&f.year)?;
19938        self.write(", ");
19939        self.generate_expression(&f.month)?;
19940        self.write(", ");
19941        self.generate_expression(&f.day)?;
19942        self.write(")");
19943        Ok(())
19944    }
19945
19946    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
19947        self.write_keyword("MAKE_TIMESTAMP");
19948        self.write("(");
19949        self.generate_expression(&f.year)?;
19950        self.write(", ");
19951        self.generate_expression(&f.month)?;
19952        self.write(", ");
19953        self.generate_expression(&f.day)?;
19954        self.write(", ");
19955        self.generate_expression(&f.hour)?;
19956        self.write(", ");
19957        self.generate_expression(&f.minute)?;
19958        self.write(", ");
19959        self.generate_expression(&f.second)?;
19960        if let Some(ref tz) = f.timezone {
19961            self.write(", ");
19962            self.generate_expression(tz)?;
19963        }
19964        self.write(")");
19965        Ok(())
19966    }
19967
19968    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
19969    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
19970        match expr {
19971            Expression::Struct(s) => {
19972                if s.fields.iter().all(|(name, _)| name.is_some()) {
19973                    Some(
19974                        s.fields
19975                            .iter()
19976                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
19977                            .collect(),
19978                    )
19979                } else {
19980                    None
19981                }
19982            }
19983            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
19984                // Check if all args are Alias (named fields)
19985                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
19986                    Some(
19987                        f.args
19988                            .iter()
19989                            .filter_map(|a| {
19990                                if let Expression::Alias(alias) = a {
19991                                    Some(alias.alias.name.clone())
19992                                } else {
19993                                    None
19994                                }
19995                            })
19996                            .collect(),
19997                    )
19998                } else {
19999                    None
20000                }
20001            }
20002            _ => None,
20003        }
20004    }
20005
20006    /// Check if a struct expression has any unnamed fields
20007    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
20008        match expr {
20009            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
20010            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20011                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
20012            }
20013            _ => false,
20014        }
20015    }
20016
20017    /// Get the field count of a struct expression
20018    fn struct_field_count(expr: &Expression) -> usize {
20019        match expr {
20020            Expression::Struct(s) => s.fields.len(),
20021            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => f.args.len(),
20022            _ => 0,
20023        }
20024    }
20025
20026    /// Apply field names to an unnamed struct expression, producing a new expression with names
20027    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
20028        match expr {
20029            Expression::Struct(s) => {
20030                let mut new_fields = Vec::with_capacity(s.fields.len());
20031                for (i, (name, value)) in s.fields.iter().enumerate() {
20032                    if name.is_none() && i < field_names.len() {
20033                        new_fields.push((Some(field_names[i].clone()), value.clone()));
20034                    } else {
20035                        new_fields.push((name.clone(), value.clone()));
20036                    }
20037                }
20038                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
20039            }
20040            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20041                let mut new_args = Vec::with_capacity(f.args.len());
20042                for (i, arg) in f.args.iter().enumerate() {
20043                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
20044                        // Wrap the value in an Alias with the inherited name
20045                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
20046                            this: arg.clone(),
20047                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
20048                            column_aliases: Vec::new(),
20049                            pre_alias_comments: Vec::new(),
20050                            trailing_comments: Vec::new(),
20051                            inferred_type: None,
20052                        })));
20053                    } else {
20054                        new_args.push(arg.clone());
20055                    }
20056                }
20057                Expression::Function(Box::new(crate::expressions::Function {
20058                    name: f.name.clone(),
20059                    args: new_args,
20060                    distinct: f.distinct,
20061                    trailing_comments: f.trailing_comments.clone(),
20062                    use_bracket_syntax: f.use_bracket_syntax,
20063                    no_parens: f.no_parens,
20064                    quoted: f.quoted,
20065                    span: None,
20066                    inferred_type: None,
20067                }))
20068            }
20069            _ => expr.clone(),
20070        }
20071    }
20072
20073    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
20074    /// This implements BigQuery's implicit field name inheritance for struct arrays.
20075    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
20076    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
20077        let first = match expressions.first() {
20078            Some(e) => e,
20079            None => return expressions.to_vec(),
20080        };
20081
20082        let field_names = match Self::extract_struct_field_names(first) {
20083            Some(names) if !names.is_empty() => names,
20084            _ => return expressions.to_vec(),
20085        };
20086
20087        let mut result = Vec::with_capacity(expressions.len());
20088        for (idx, expr) in expressions.iter().enumerate() {
20089            if idx == 0 {
20090                result.push(expr.clone());
20091                continue;
20092            }
20093            // Check if this is a struct with unnamed fields that needs name propagation
20094            if Self::struct_field_count(expr) == field_names.len()
20095                && Self::struct_has_unnamed_fields(expr)
20096            {
20097                result.push(Self::apply_struct_field_names(expr, &field_names));
20098            } else {
20099                result.push(expr.clone());
20100            }
20101        }
20102        result
20103    }
20104
20105    // Array function generators
20106
20107    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
20108        // Apply struct name inheritance for target dialects that need it
20109        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
20110        let needs_inheritance = matches!(
20111            self.config.dialect,
20112            Some(DialectType::DuckDB)
20113                | Some(DialectType::Spark)
20114                | Some(DialectType::Databricks)
20115                | Some(DialectType::Hive)
20116                | Some(DialectType::Snowflake)
20117                | Some(DialectType::Presto)
20118                | Some(DialectType::Trino)
20119        );
20120        let propagated: Vec<Expression>;
20121        let expressions = if needs_inheritance && f.expressions.len() > 1 {
20122            propagated = Self::inherit_struct_field_names(&f.expressions);
20123            &propagated
20124        } else {
20125            &f.expressions
20126        };
20127
20128        // Check if elements should be split onto multiple lines (pretty + too wide)
20129        let should_split = if self.config.pretty && !expressions.is_empty() {
20130            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
20131            for expr in expressions {
20132                let mut temp_gen = Generator::with_arc_config(self.config.clone());
20133                Arc::make_mut(&mut temp_gen.config).pretty = false;
20134                temp_gen.generate_expression(expr)?;
20135                expr_strings.push(temp_gen.output);
20136            }
20137            self.too_wide(&expr_strings)
20138        } else {
20139            false
20140        };
20141
20142        if f.bracket_notation {
20143            // For Spark/Databricks, use ARRAY(...) with parens
20144            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
20145            // For others (DuckDB, Snowflake), use bare [...]
20146            let (open, close) = match self.config.dialect {
20147                None
20148                | Some(DialectType::Generic)
20149                | Some(DialectType::Spark)
20150                | Some(DialectType::Databricks)
20151                | Some(DialectType::Hive) => {
20152                    self.write_keyword("ARRAY");
20153                    ("(", ")")
20154                }
20155                Some(DialectType::Presto)
20156                | Some(DialectType::Trino)
20157                | Some(DialectType::PostgreSQL)
20158                | Some(DialectType::Redshift)
20159                | Some(DialectType::Materialize)
20160                | Some(DialectType::RisingWave)
20161                | Some(DialectType::CockroachDB) => {
20162                    self.write_keyword("ARRAY");
20163                    ("[", "]")
20164                }
20165                _ => ("[", "]"),
20166            };
20167            self.write(open);
20168            if should_split {
20169                self.write_newline();
20170                self.indent_level += 1;
20171                for (i, expr) in expressions.iter().enumerate() {
20172                    self.write_indent();
20173                    self.generate_expression(expr)?;
20174                    if i + 1 < expressions.len() {
20175                        self.write(",");
20176                    }
20177                    self.write_newline();
20178                }
20179                self.indent_level -= 1;
20180                self.write_indent();
20181            } else {
20182                for (i, expr) in expressions.iter().enumerate() {
20183                    if i > 0 {
20184                        self.write(", ");
20185                    }
20186                    self.generate_expression(expr)?;
20187                }
20188            }
20189            self.write(close);
20190        } else {
20191            // Use LIST keyword if that was the original syntax (DuckDB)
20192            if f.use_list_keyword {
20193                self.write_keyword("LIST");
20194            } else {
20195                self.write_keyword("ARRAY");
20196            }
20197            // For Spark/Hive, always use ARRAY(...) with parens
20198            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
20199            let has_subquery = expressions
20200                .iter()
20201                .any(|e| matches!(e, Expression::Select(_)));
20202            let (open, close) = if matches!(
20203                self.config.dialect,
20204                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
20205            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
20206                && has_subquery)
20207            {
20208                ("(", ")")
20209            } else {
20210                ("[", "]")
20211            };
20212            self.write(open);
20213            if should_split {
20214                self.write_newline();
20215                self.indent_level += 1;
20216                for (i, expr) in expressions.iter().enumerate() {
20217                    self.write_indent();
20218                    self.generate_expression(expr)?;
20219                    if i + 1 < expressions.len() {
20220                        self.write(",");
20221                    }
20222                    self.write_newline();
20223                }
20224                self.indent_level -= 1;
20225                self.write_indent();
20226            } else {
20227                for (i, expr) in expressions.iter().enumerate() {
20228                    if i > 0 {
20229                        self.write(", ");
20230                    }
20231                    self.generate_expression(expr)?;
20232                }
20233            }
20234            self.write(close);
20235        }
20236        Ok(())
20237    }
20238
20239    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
20240        self.write_keyword("ARRAY_SORT");
20241        self.write("(");
20242        self.generate_expression(&f.this)?;
20243        if let Some(ref comp) = f.comparator {
20244            self.write(", ");
20245            self.generate_expression(comp)?;
20246        }
20247        self.write(")");
20248        Ok(())
20249    }
20250
20251    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
20252        self.write_keyword(name);
20253        self.write("(");
20254        self.generate_expression(&f.this)?;
20255        self.write(", ");
20256        self.generate_expression(&f.separator)?;
20257        if let Some(ref null_rep) = f.null_replacement {
20258            self.write(", ");
20259            self.generate_expression(null_rep)?;
20260        }
20261        self.write(")");
20262        Ok(())
20263    }
20264
20265    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
20266        self.write_keyword("UNNEST");
20267        self.write("(");
20268        self.generate_expression(&f.this)?;
20269        for extra in &f.expressions {
20270            self.write(", ");
20271            self.generate_expression(extra)?;
20272        }
20273        self.write(")");
20274        if f.with_ordinality {
20275            self.write_space();
20276            if self.config.unnest_with_ordinality {
20277                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
20278                self.write_keyword("WITH ORDINALITY");
20279            } else if f.offset_alias.is_some() {
20280                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
20281                // Alias (if any) comes BEFORE WITH OFFSET
20282                if let Some(ref alias) = f.alias {
20283                    self.write_keyword("AS");
20284                    self.write_space();
20285                    self.generate_identifier(alias)?;
20286                    self.write_space();
20287                }
20288                self.write_keyword("WITH OFFSET");
20289                if let Some(ref offset_alias) = f.offset_alias {
20290                    self.write_space();
20291                    self.write_keyword("AS");
20292                    self.write_space();
20293                    self.generate_identifier(offset_alias)?;
20294                }
20295            } else {
20296                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
20297                self.write_keyword("WITH OFFSET");
20298                if f.alias.is_none() {
20299                    self.write(" AS offset");
20300                }
20301            }
20302        }
20303        if let Some(ref alias) = f.alias {
20304            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
20305            let should_add_alias = if !f.with_ordinality {
20306                true
20307            } else if self.config.unnest_with_ordinality {
20308                // Presto/Trino: alias comes after WITH ORDINALITY
20309                true
20310            } else if f.offset_alias.is_some() {
20311                // BigQuery expansion: alias already handled above
20312                false
20313            } else {
20314                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
20315                true
20316            };
20317            if should_add_alias {
20318                self.write_space();
20319                self.write_keyword("AS");
20320                self.write_space();
20321                self.generate_identifier(alias)?;
20322            }
20323        }
20324        Ok(())
20325    }
20326
20327    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
20328        self.write_keyword("FILTER");
20329        self.write("(");
20330        self.generate_expression(&f.this)?;
20331        self.write(", ");
20332        self.generate_expression(&f.filter)?;
20333        self.write(")");
20334        Ok(())
20335    }
20336
20337    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
20338        self.write_keyword("TRANSFORM");
20339        self.write("(");
20340        self.generate_expression(&f.this)?;
20341        self.write(", ");
20342        self.generate_expression(&f.transform)?;
20343        self.write(")");
20344        Ok(())
20345    }
20346
20347    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
20348        self.write_keyword(name);
20349        self.write("(");
20350        self.generate_expression(&f.start)?;
20351        self.write(", ");
20352        self.generate_expression(&f.stop)?;
20353        if let Some(ref step) = f.step {
20354            self.write(", ");
20355            self.generate_expression(step)?;
20356        }
20357        self.write(")");
20358        Ok(())
20359    }
20360
20361    // Struct function generators
20362
20363    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
20364        self.write_keyword("STRUCT");
20365        self.write("(");
20366        for (i, (name, expr)) in f.fields.iter().enumerate() {
20367            if i > 0 {
20368                self.write(", ");
20369            }
20370            if let Some(ref id) = name {
20371                self.generate_identifier(id)?;
20372                self.write(" ");
20373                self.write_keyword("AS");
20374                self.write(" ");
20375            }
20376            self.generate_expression(expr)?;
20377        }
20378        self.write(")");
20379        Ok(())
20380    }
20381
20382    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
20383    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
20384        // Extract named/unnamed fields from function args
20385        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
20386        let mut names: Vec<Option<String>> = Vec::new();
20387        let mut values: Vec<&Expression> = Vec::new();
20388        let mut all_named = true;
20389
20390        for arg in &func.args {
20391            match arg {
20392                Expression::Alias(a) => {
20393                    names.push(Some(a.alias.name.clone()));
20394                    values.push(&a.this);
20395                }
20396                _ => {
20397                    names.push(None);
20398                    values.push(arg);
20399                    all_named = false;
20400                }
20401            }
20402        }
20403
20404        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
20405            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
20406            self.write("{");
20407            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20408                if i > 0 {
20409                    self.write(", ");
20410                }
20411                if let Some(n) = name {
20412                    self.write("'");
20413                    self.write(n);
20414                    self.write("'");
20415                } else {
20416                    self.write("'_");
20417                    self.write(&i.to_string());
20418                    self.write("'");
20419                }
20420                self.write(": ");
20421                self.generate_expression(value)?;
20422            }
20423            self.write("}");
20424            return Ok(());
20425        }
20426
20427        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
20428            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
20429            self.write_keyword("OBJECT_CONSTRUCT");
20430            self.write("(");
20431            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20432                if i > 0 {
20433                    self.write(", ");
20434                }
20435                if let Some(n) = name {
20436                    self.write("'");
20437                    self.write(n);
20438                    self.write("'");
20439                } else {
20440                    self.write("'_");
20441                    self.write(&i.to_string());
20442                    self.write("'");
20443                }
20444                self.write(", ");
20445                self.generate_expression(value)?;
20446            }
20447            self.write(")");
20448            return Ok(());
20449        }
20450
20451        if matches!(
20452            self.config.dialect,
20453            Some(DialectType::Presto) | Some(DialectType::Trino)
20454        ) {
20455            if all_named && !names.is_empty() {
20456                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
20457                // Need to infer types from values
20458                self.write_keyword("CAST");
20459                self.write("(");
20460                self.write_keyword("ROW");
20461                self.write("(");
20462                for (i, value) in values.iter().enumerate() {
20463                    if i > 0 {
20464                        self.write(", ");
20465                    }
20466                    self.generate_expression(value)?;
20467                }
20468                self.write(")");
20469                self.write(" ");
20470                self.write_keyword("AS");
20471                self.write(" ");
20472                self.write_keyword("ROW");
20473                self.write("(");
20474                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20475                    if i > 0 {
20476                        self.write(", ");
20477                    }
20478                    if let Some(n) = name {
20479                        self.write(n);
20480                    }
20481                    self.write(" ");
20482                    let type_str = Self::infer_sql_type_for_presto(value);
20483                    self.write_keyword(&type_str);
20484                }
20485                self.write(")");
20486                self.write(")");
20487            } else {
20488                // Unnamed: ROW(values...)
20489                self.write_keyword("ROW");
20490                self.write("(");
20491                for (i, value) in values.iter().enumerate() {
20492                    if i > 0 {
20493                        self.write(", ");
20494                    }
20495                    self.generate_expression(value)?;
20496                }
20497                self.write(")");
20498            }
20499            return Ok(());
20500        }
20501
20502        // Default: ROW(values...) for other dialects
20503        self.write_keyword("ROW");
20504        self.write("(");
20505        for (i, value) in values.iter().enumerate() {
20506            if i > 0 {
20507                self.write(", ");
20508            }
20509            self.generate_expression(value)?;
20510        }
20511        self.write(")");
20512        Ok(())
20513    }
20514
20515    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
20516    fn infer_sql_type_for_presto(expr: &Expression) -> String {
20517        match expr {
20518            Expression::Literal(lit)
20519                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
20520            {
20521                "VARCHAR".to_string()
20522            }
20523            Expression::Literal(lit)
20524                if matches!(lit.as_ref(), crate::expressions::Literal::Number(_)) =>
20525            {
20526                let crate::expressions::Literal::Number(n) = lit.as_ref() else {
20527                    unreachable!()
20528                };
20529                if n.contains('.') {
20530                    "DOUBLE".to_string()
20531                } else {
20532                    "INTEGER".to_string()
20533                }
20534            }
20535            Expression::Boolean(_) => "BOOLEAN".to_string(),
20536            Expression::Literal(lit)
20537                if matches!(lit.as_ref(), crate::expressions::Literal::Date(_)) =>
20538            {
20539                "DATE".to_string()
20540            }
20541            Expression::Literal(lit)
20542                if matches!(lit.as_ref(), crate::expressions::Literal::Timestamp(_)) =>
20543            {
20544                "TIMESTAMP".to_string()
20545            }
20546            Expression::Literal(lit)
20547                if matches!(lit.as_ref(), crate::expressions::Literal::Datetime(_)) =>
20548            {
20549                "TIMESTAMP".to_string()
20550            }
20551            Expression::Array(_) | Expression::ArrayFunc(_) => {
20552                // Try to infer element type from first element
20553                "ARRAY(VARCHAR)".to_string()
20554            }
20555            // For nested structs - generate a nested ROW type by inspecting fields
20556            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
20557            Expression::Function(f) => {
20558                if f.name.eq_ignore_ascii_case("STRUCT") {
20559                    "ROW".to_string()
20560                } else if f.name.eq_ignore_ascii_case("CURRENT_DATE") {
20561                    "DATE".to_string()
20562                } else if f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
20563                    || f.name.eq_ignore_ascii_case("NOW")
20564                {
20565                    "TIMESTAMP".to_string()
20566                } else {
20567                    "VARCHAR".to_string()
20568                }
20569            }
20570            _ => "VARCHAR".to_string(),
20571        }
20572    }
20573
20574    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
20575        // DuckDB uses STRUCT_EXTRACT function syntax
20576        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
20577            self.write_keyword("STRUCT_EXTRACT");
20578            self.write("(");
20579            self.generate_expression(&f.this)?;
20580            self.write(", ");
20581            // Output field name as string literal
20582            self.write("'");
20583            self.write(&f.field.name);
20584            self.write("'");
20585            self.write(")");
20586            return Ok(());
20587        }
20588        self.generate_expression(&f.this)?;
20589        self.write(".");
20590        self.generate_identifier(&f.field)
20591    }
20592
20593    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
20594        self.write_keyword("NAMED_STRUCT");
20595        self.write("(");
20596        for (i, (name, value)) in f.pairs.iter().enumerate() {
20597            if i > 0 {
20598                self.write(", ");
20599            }
20600            self.generate_expression(name)?;
20601            self.write(", ");
20602            self.generate_expression(value)?;
20603        }
20604        self.write(")");
20605        Ok(())
20606    }
20607
20608    // Map function generators
20609
20610    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
20611        if f.curly_brace_syntax {
20612            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
20613            if f.with_map_keyword {
20614                self.write_keyword("MAP");
20615                self.write(" ");
20616            }
20617            self.write("{");
20618            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
20619                if i > 0 {
20620                    self.write(", ");
20621                }
20622                self.generate_expression(key)?;
20623                self.write(": ");
20624                self.generate_expression(val)?;
20625            }
20626            self.write("}");
20627        } else {
20628            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
20629            self.write_keyword("MAP");
20630            self.write("(");
20631            self.write_keyword("ARRAY");
20632            self.write("[");
20633            for (i, key) in f.keys.iter().enumerate() {
20634                if i > 0 {
20635                    self.write(", ");
20636                }
20637                self.generate_expression(key)?;
20638            }
20639            self.write("], ");
20640            self.write_keyword("ARRAY");
20641            self.write("[");
20642            for (i, val) in f.values.iter().enumerate() {
20643                if i > 0 {
20644                    self.write(", ");
20645                }
20646                self.generate_expression(val)?;
20647            }
20648            self.write("])");
20649        }
20650        Ok(())
20651    }
20652
20653    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
20654        self.write_keyword(name);
20655        self.write("(");
20656        self.generate_expression(&f.this)?;
20657        self.write(", ");
20658        self.generate_expression(&f.transform)?;
20659        self.write(")");
20660        Ok(())
20661    }
20662
20663    // JSON function generators
20664
20665    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
20666        use crate::dialects::DialectType;
20667
20668        // Check if we should use arrow syntax (-> or ->>)
20669        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
20670
20671        if use_arrow {
20672            // Output arrow syntax: expr -> path or expr ->> path
20673            self.generate_expression(&f.this)?;
20674            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
20675                self.write(" ->> ");
20676            } else {
20677                self.write(" -> ");
20678            }
20679            self.generate_expression(&f.path)?;
20680            return Ok(());
20681        }
20682
20683        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
20684        if f.hash_arrow_syntax
20685            && matches!(
20686                self.config.dialect,
20687                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
20688            )
20689        {
20690            self.generate_expression(&f.this)?;
20691            self.write(" #>> ");
20692            self.generate_expression(&f.path)?;
20693            return Ok(());
20694        }
20695
20696        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
20697        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
20698        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
20699            match name {
20700                "JSON_EXTRACT_SCALAR"
20701                | "JSON_EXTRACT_PATH_TEXT"
20702                | "JSON_EXTRACT"
20703                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
20704                _ => name,
20705            }
20706        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
20707            match name {
20708                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
20709                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
20710                _ => name,
20711            }
20712        } else {
20713            name
20714        };
20715
20716        self.write_keyword(func_name);
20717        self.write("(");
20718        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
20719        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
20720            if let Expression::Cast(ref cast) = f.this {
20721                if matches!(cast.to, crate::expressions::DataType::Json) {
20722                    self.generate_expression(&cast.this)?;
20723                } else {
20724                    self.generate_expression(&f.this)?;
20725                }
20726            } else {
20727                self.generate_expression(&f.this)?;
20728            }
20729        } else {
20730            self.generate_expression(&f.this)?;
20731        }
20732        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
20733        // decompose JSON path into separate string arguments
20734        if matches!(
20735            self.config.dialect,
20736            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
20737        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
20738        {
20739            if let Expression::Literal(ref lit) = f.path {
20740                if let Literal::String(ref s) = lit.as_ref() {
20741                    let parts = Self::decompose_json_path(s);
20742                    for part in &parts {
20743                        self.write(", '");
20744                        self.write(part);
20745                        self.write("'");
20746                    }
20747                }
20748            } else {
20749                self.write(", ");
20750                self.generate_expression(&f.path)?;
20751            }
20752        } else {
20753            self.write(", ");
20754            self.generate_expression(&f.path)?;
20755        }
20756
20757        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
20758        // These go BEFORE the closing parenthesis
20759        if let Some(ref wrapper) = f.wrapper_option {
20760            self.write_space();
20761            self.write_keyword(wrapper);
20762        }
20763        if let Some(ref quotes) = f.quotes_option {
20764            self.write_space();
20765            self.write_keyword(quotes);
20766            if f.on_scalar_string {
20767                self.write_space();
20768                self.write_keyword("ON SCALAR STRING");
20769            }
20770        }
20771        if let Some(ref on_err) = f.on_error {
20772            self.write_space();
20773            self.write_keyword(on_err);
20774        }
20775        if let Some(ref ret_type) = f.returning {
20776            self.write_space();
20777            self.write_keyword("RETURNING");
20778            self.write_space();
20779            self.generate_data_type(ret_type)?;
20780        }
20781
20782        self.write(")");
20783        Ok(())
20784    }
20785
20786    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
20787    fn dialect_supports_json_arrow(&self) -> bool {
20788        use crate::dialects::DialectType;
20789        match self.config.dialect {
20790            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
20791            Some(DialectType::PostgreSQL) => true,
20792            Some(DialectType::MySQL) => true,
20793            Some(DialectType::DuckDB) => true,
20794            Some(DialectType::CockroachDB) => true,
20795            Some(DialectType::StarRocks) => true,
20796            Some(DialectType::SQLite) => true,
20797            // Other dialects use function syntax
20798            _ => false,
20799        }
20800    }
20801
20802    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
20803        use crate::dialects::DialectType;
20804
20805        // PostgreSQL uses #> operator for JSONB path extraction
20806        if matches!(
20807            self.config.dialect,
20808            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
20809        ) && name == "JSON_EXTRACT_PATH"
20810        {
20811            self.generate_expression(&f.this)?;
20812            self.write(" #> ");
20813            if f.paths.len() == 1 {
20814                self.generate_expression(&f.paths[0])?;
20815            } else {
20816                // Multiple paths: ARRAY[path1, path2, ...]
20817                self.write_keyword("ARRAY");
20818                self.write("[");
20819                for (i, path) in f.paths.iter().enumerate() {
20820                    if i > 0 {
20821                        self.write(", ");
20822                    }
20823                    self.generate_expression(path)?;
20824                }
20825                self.write("]");
20826            }
20827            return Ok(());
20828        }
20829
20830        self.write_keyword(name);
20831        self.write("(");
20832        self.generate_expression(&f.this)?;
20833        for path in &f.paths {
20834            self.write(", ");
20835            self.generate_expression(path)?;
20836        }
20837        self.write(")");
20838        Ok(())
20839    }
20840
20841    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
20842        use crate::dialects::DialectType;
20843
20844        self.write_keyword("JSON_OBJECT");
20845        self.write("(");
20846        if f.star {
20847            self.write("*");
20848        } else {
20849            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
20850            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
20851            // Also respect the json_key_value_pair_sep config
20852            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
20853                || matches!(
20854                    self.config.dialect,
20855                    Some(DialectType::BigQuery)
20856                        | Some(DialectType::MySQL)
20857                        | Some(DialectType::SQLite)
20858                );
20859
20860            for (i, (key, value)) in f.pairs.iter().enumerate() {
20861                if i > 0 {
20862                    self.write(", ");
20863                }
20864                self.generate_expression(key)?;
20865                if use_comma_syntax {
20866                    self.write(", ");
20867                } else {
20868                    self.write(": ");
20869                }
20870                self.generate_expression(value)?;
20871            }
20872        }
20873        if let Some(null_handling) = f.null_handling {
20874            self.write_space();
20875            match null_handling {
20876                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
20877                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
20878            }
20879        }
20880        if f.with_unique_keys {
20881            self.write_space();
20882            self.write_keyword("WITH UNIQUE KEYS");
20883        }
20884        if let Some(ref ret_type) = f.returning_type {
20885            self.write_space();
20886            self.write_keyword("RETURNING");
20887            self.write_space();
20888            self.generate_data_type(ret_type)?;
20889            if f.format_json {
20890                self.write_space();
20891                self.write_keyword("FORMAT JSON");
20892            }
20893            if let Some(ref enc) = f.encoding {
20894                self.write_space();
20895                self.write_keyword("ENCODING");
20896                self.write_space();
20897                self.write(enc);
20898            }
20899        }
20900        self.write(")");
20901        Ok(())
20902    }
20903
20904    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
20905        self.write_keyword(name);
20906        self.write("(");
20907        self.generate_expression(&f.this)?;
20908        for (path, value) in &f.path_values {
20909            self.write(", ");
20910            self.generate_expression(path)?;
20911            self.write(", ");
20912            self.generate_expression(value)?;
20913        }
20914        self.write(")");
20915        Ok(())
20916    }
20917
20918    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
20919        self.write_keyword("JSON_ARRAYAGG");
20920        self.write("(");
20921        self.generate_expression(&f.this)?;
20922        if let Some(ref order_by) = f.order_by {
20923            self.write_space();
20924            self.write_keyword("ORDER BY");
20925            self.write_space();
20926            for (i, ord) in order_by.iter().enumerate() {
20927                if i > 0 {
20928                    self.write(", ");
20929                }
20930                self.generate_ordered(ord)?;
20931            }
20932        }
20933        if let Some(null_handling) = f.null_handling {
20934            self.write_space();
20935            match null_handling {
20936                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
20937                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
20938            }
20939        }
20940        self.write(")");
20941        if let Some(ref filter) = f.filter {
20942            self.write_space();
20943            self.write_keyword("FILTER");
20944            self.write("(");
20945            self.write_keyword("WHERE");
20946            self.write_space();
20947            self.generate_expression(filter)?;
20948            self.write(")");
20949        }
20950        Ok(())
20951    }
20952
20953    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
20954        self.write_keyword("JSON_OBJECTAGG");
20955        self.write("(");
20956        self.generate_expression(&f.key)?;
20957        self.write(": ");
20958        self.generate_expression(&f.value)?;
20959        if let Some(null_handling) = f.null_handling {
20960            self.write_space();
20961            match null_handling {
20962                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
20963                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
20964            }
20965        }
20966        self.write(")");
20967        if let Some(ref filter) = f.filter {
20968            self.write_space();
20969            self.write_keyword("FILTER");
20970            self.write("(");
20971            self.write_keyword("WHERE");
20972            self.write_space();
20973            self.generate_expression(filter)?;
20974            self.write(")");
20975        }
20976        Ok(())
20977    }
20978
20979    // Type casting/conversion generators
20980
20981    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
20982        use crate::dialects::DialectType;
20983
20984        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
20985        if self.config.dialect == Some(DialectType::Redshift) {
20986            self.write_keyword("CAST");
20987            self.write("(");
20988            self.generate_expression(&f.this)?;
20989            self.write_space();
20990            self.write_keyword("AS");
20991            self.write_space();
20992            self.generate_data_type(&f.to)?;
20993            self.write(")");
20994            return Ok(());
20995        }
20996
20997        self.write_keyword("CONVERT");
20998        self.write("(");
20999        self.generate_data_type(&f.to)?;
21000        self.write(", ");
21001        self.generate_expression(&f.this)?;
21002        if let Some(ref style) = f.style {
21003            self.write(", ");
21004            self.generate_expression(style)?;
21005        }
21006        self.write(")");
21007        Ok(())
21008    }
21009
21010    // Additional expression generators
21011
21012    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
21013        if f.colon {
21014            // DuckDB syntax: LAMBDA x : expr
21015            self.write_keyword("LAMBDA");
21016            self.write_space();
21017            for (i, param) in f.parameters.iter().enumerate() {
21018                if i > 0 {
21019                    self.write(", ");
21020                }
21021                self.generate_identifier(param)?;
21022            }
21023            self.write(" : ");
21024        } else {
21025            // Standard syntax: x -> expr or (x, y) -> expr
21026            if f.parameters.len() == 1 {
21027                self.generate_identifier(&f.parameters[0])?;
21028            } else {
21029                self.write("(");
21030                for (i, param) in f.parameters.iter().enumerate() {
21031                    if i > 0 {
21032                        self.write(", ");
21033                    }
21034                    self.generate_identifier(param)?;
21035                }
21036                self.write(")");
21037            }
21038            self.write(" -> ");
21039        }
21040        self.generate_expression(&f.body)
21041    }
21042
21043    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
21044        self.generate_identifier(&f.name)?;
21045        match f.separator {
21046            NamedArgSeparator::DArrow => self.write(" => "),
21047            NamedArgSeparator::ColonEq => self.write(" := "),
21048            NamedArgSeparator::Eq => self.write(" = "),
21049        }
21050        self.generate_expression(&f.value)
21051    }
21052
21053    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
21054        self.write_keyword(&f.prefix);
21055        self.write(" ");
21056        self.generate_expression(&f.this)
21057    }
21058
21059    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
21060        match f.style {
21061            ParameterStyle::Question => self.write("?"),
21062            ParameterStyle::Dollar => {
21063                self.write("$");
21064                if let Some(idx) = f.index {
21065                    self.write(&idx.to_string());
21066                } else if let Some(ref name) = f.name {
21067                    // Session variable like $x or $query_id
21068                    self.write(name);
21069                }
21070            }
21071            ParameterStyle::DollarBrace => {
21072                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
21073                self.write("${");
21074                if let Some(ref name) = f.name {
21075                    self.write(name);
21076                }
21077                if let Some(ref expr) = f.expression {
21078                    self.write(":");
21079                    self.write(expr);
21080                }
21081                self.write("}");
21082            }
21083            ParameterStyle::Colon => {
21084                self.write(":");
21085                if let Some(idx) = f.index {
21086                    self.write(&idx.to_string());
21087                } else if let Some(ref name) = f.name {
21088                    self.write(name);
21089                }
21090            }
21091            ParameterStyle::At => {
21092                self.write("@");
21093                if let Some(ref name) = f.name {
21094                    if f.string_quoted {
21095                        self.write("'");
21096                        self.write(name);
21097                        self.write("'");
21098                    } else if f.quoted {
21099                        self.write("\"");
21100                        self.write(name);
21101                        self.write("\"");
21102                    } else {
21103                        self.write(name);
21104                    }
21105                }
21106            }
21107            ParameterStyle::DoubleAt => {
21108                self.write("@@");
21109                if let Some(ref name) = f.name {
21110                    self.write(name);
21111                }
21112            }
21113            ParameterStyle::DoubleDollar => {
21114                self.write("$$");
21115                if let Some(ref name) = f.name {
21116                    self.write(name);
21117                }
21118            }
21119            ParameterStyle::Percent => {
21120                if let Some(ref name) = f.name {
21121                    // %(name)s format
21122                    self.write("%(");
21123                    self.write(name);
21124                    self.write(")s");
21125                } else {
21126                    // %s format
21127                    self.write("%s");
21128                }
21129            }
21130            ParameterStyle::Brace => {
21131                // Spark/Databricks widget template variable: {name}
21132                // ClickHouse query parameter may include kind: {name: Type}
21133                self.write("{");
21134                if let Some(ref name) = f.name {
21135                    self.write(name);
21136                }
21137                if let Some(ref expr) = f.expression {
21138                    self.write(": ");
21139                    self.write(expr);
21140                }
21141                self.write("}");
21142            }
21143        }
21144        Ok(())
21145    }
21146
21147    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
21148        self.write("?");
21149        if let Some(idx) = f.index {
21150            self.write(&idx.to_string());
21151        }
21152        Ok(())
21153    }
21154
21155    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
21156        if f.is_block {
21157            self.write("/*");
21158            self.write(&f.text);
21159            self.write("*/");
21160        } else {
21161            self.write("--");
21162            self.write(&f.text);
21163        }
21164        Ok(())
21165    }
21166
21167    // Additional predicate generators
21168
21169    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
21170        self.generate_expression(&f.this)?;
21171        if f.not {
21172            self.write_space();
21173            self.write_keyword("NOT");
21174        }
21175        self.write_space();
21176        self.write_keyword("SIMILAR TO");
21177        self.write_space();
21178        self.generate_expression(&f.pattern)?;
21179        if let Some(ref escape) = f.escape {
21180            self.write_space();
21181            self.write_keyword("ESCAPE");
21182            self.write_space();
21183            self.generate_expression(escape)?;
21184        }
21185        Ok(())
21186    }
21187
21188    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
21189        self.generate_expression(&f.this)?;
21190        self.write_space();
21191        // Output comparison operator if present
21192        if let Some(op) = &f.op {
21193            match op {
21194                QuantifiedOp::Eq => self.write("="),
21195                QuantifiedOp::Neq => self.write("<>"),
21196                QuantifiedOp::Lt => self.write("<"),
21197                QuantifiedOp::Lte => self.write("<="),
21198                QuantifiedOp::Gt => self.write(">"),
21199                QuantifiedOp::Gte => self.write(">="),
21200            }
21201            self.write_space();
21202        }
21203        self.write_keyword(name);
21204
21205        // If the child is a Subquery, it provides its own parens — output with space
21206        if matches!(&f.subquery, Expression::Subquery(_)) {
21207            self.write_space();
21208            self.generate_expression(&f.subquery)?;
21209        } else {
21210            self.write("(");
21211
21212            let is_statement = matches!(
21213                &f.subquery,
21214                Expression::Select(_)
21215                    | Expression::Union(_)
21216                    | Expression::Intersect(_)
21217                    | Expression::Except(_)
21218            );
21219
21220            if self.config.pretty && is_statement {
21221                self.write_newline();
21222                self.indent_level += 1;
21223                self.write_indent();
21224            }
21225            self.generate_expression(&f.subquery)?;
21226            if self.config.pretty && is_statement {
21227                self.write_newline();
21228                self.indent_level -= 1;
21229                self.write_indent();
21230            }
21231            self.write(")");
21232        }
21233        Ok(())
21234    }
21235
21236    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
21237        // Check if this is a simple binary form (this OVERLAPS expression)
21238        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
21239            self.generate_expression(this)?;
21240            self.write_space();
21241            self.write_keyword("OVERLAPS");
21242            self.write_space();
21243            self.generate_expression(expr)?;
21244        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
21245            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
21246        {
21247            // Full ANSI form: (a, b) OVERLAPS (c, d)
21248            self.write("(");
21249            self.generate_expression(ls)?;
21250            self.write(", ");
21251            self.generate_expression(le)?;
21252            self.write(")");
21253            self.write_space();
21254            self.write_keyword("OVERLAPS");
21255            self.write_space();
21256            self.write("(");
21257            self.generate_expression(rs)?;
21258            self.write(", ");
21259            self.generate_expression(re)?;
21260            self.write(")");
21261        }
21262        Ok(())
21263    }
21264
21265    // Type conversion generators
21266
21267    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
21268        use crate::dialects::DialectType;
21269
21270        // SingleStore uses !:> syntax for try cast
21271        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
21272            self.generate_expression(&cast.this)?;
21273            self.write(" !:> ");
21274            self.generate_data_type(&cast.to)?;
21275            return Ok(());
21276        }
21277
21278        // Teradata uses TRYCAST (no underscore)
21279        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
21280            self.write_keyword("TRYCAST");
21281            self.write("(");
21282            self.generate_expression(&cast.this)?;
21283            self.write_space();
21284            self.write_keyword("AS");
21285            self.write_space();
21286            self.generate_data_type(&cast.to)?;
21287            self.write(")");
21288            return Ok(());
21289        }
21290
21291        // Dialects without TRY_CAST: generate as regular CAST
21292        let keyword = if matches!(
21293            self.config.dialect,
21294            Some(DialectType::Hive)
21295                | Some(DialectType::MySQL)
21296                | Some(DialectType::SQLite)
21297                | Some(DialectType::Oracle)
21298                | Some(DialectType::ClickHouse)
21299                | Some(DialectType::Redshift)
21300                | Some(DialectType::PostgreSQL)
21301                | Some(DialectType::StarRocks)
21302                | Some(DialectType::Doris)
21303        ) {
21304            "CAST"
21305        } else {
21306            "TRY_CAST"
21307        };
21308
21309        self.write_keyword(keyword);
21310        self.write("(");
21311        self.generate_expression(&cast.this)?;
21312        self.write_space();
21313        self.write_keyword("AS");
21314        self.write_space();
21315        self.generate_data_type(&cast.to)?;
21316
21317        // Output FORMAT clause if present
21318        if let Some(format) = &cast.format {
21319            self.write_space();
21320            self.write_keyword("FORMAT");
21321            self.write_space();
21322            self.generate_expression(format)?;
21323        }
21324
21325        self.write(")");
21326        Ok(())
21327    }
21328
21329    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
21330        self.write_keyword("SAFE_CAST");
21331        self.write("(");
21332        self.generate_expression(&cast.this)?;
21333        self.write_space();
21334        self.write_keyword("AS");
21335        self.write_space();
21336        self.generate_data_type(&cast.to)?;
21337
21338        // Output FORMAT clause if present
21339        if let Some(format) = &cast.format {
21340            self.write_space();
21341            self.write_keyword("FORMAT");
21342            self.write_space();
21343            self.generate_expression(format)?;
21344        }
21345
21346        self.write(")");
21347        Ok(())
21348    }
21349
21350    // Array/struct/map access generators
21351
21352    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
21353        // Wrap the base expression in parentheses when it uses arrow syntax (->)
21354        // which has lower precedence than bracket subscript ([]).
21355        // E.g., (t.v -> '$.a')[s.x] instead of t.v -> '$.a'[s.x]
21356        let needs_parens = matches!(&s.this, Expression::JsonExtract(ref f) if f.arrow_syntax);
21357        if needs_parens {
21358            self.write("(");
21359        }
21360        self.generate_expression(&s.this)?;
21361        if needs_parens {
21362            self.write(")");
21363        }
21364        self.write("[");
21365        self.generate_expression(&s.index)?;
21366        self.write("]");
21367        Ok(())
21368    }
21369
21370    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
21371        self.generate_expression(&d.this)?;
21372        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
21373        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
21374        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
21375            && matches!(
21376                &d.this,
21377                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
21378            );
21379        if use_colon {
21380            self.write(":");
21381        } else {
21382            self.write(".");
21383        }
21384        self.generate_identifier(&d.field)
21385    }
21386
21387    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
21388        self.generate_expression(&m.this)?;
21389        self.write(".");
21390        // Method names after a dot should not be quoted based on reserved keywords
21391        // Only quote if explicitly marked as quoted in the AST
21392        if m.method.quoted {
21393            let q = self.config.identifier_quote;
21394            self.write(&format!("{}{}{}", q, m.method.name, q));
21395        } else {
21396            self.write(&m.method.name);
21397        }
21398        self.write("(");
21399        for (i, arg) in m.args.iter().enumerate() {
21400            if i > 0 {
21401                self.write(", ");
21402            }
21403            self.generate_expression(arg)?;
21404        }
21405        self.write(")");
21406        Ok(())
21407    }
21408
21409    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
21410        // Check if we need to wrap the inner expression in parentheses
21411        // JSON arrow expressions have lower precedence than array subscript
21412        let needs_parens = matches!(
21413            &s.this,
21414            Expression::JsonExtract(f) if f.arrow_syntax
21415        ) || matches!(
21416            &s.this,
21417            Expression::JsonExtractScalar(f) if f.arrow_syntax
21418        );
21419
21420        if needs_parens {
21421            self.write("(");
21422        }
21423        self.generate_expression(&s.this)?;
21424        if needs_parens {
21425            self.write(")");
21426        }
21427        self.write("[");
21428        if let Some(start) = &s.start {
21429            self.generate_expression(start)?;
21430        }
21431        self.write(":");
21432        if let Some(end) = &s.end {
21433            self.generate_expression(end)?;
21434        }
21435        self.write("]");
21436        Ok(())
21437    }
21438
21439    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
21440        // Generate left expression, but skip trailing comments if they're already in left_comments
21441        // to avoid duplication (comments are captured as both expr.trailing_comments
21442        // and BinaryOp.left_comments during parsing)
21443        match &op.left {
21444            Expression::Column(col) => {
21445                // Generate column with trailing comments but skip them if they're
21446                // already captured in BinaryOp.left_comments to avoid duplication
21447                if let Some(table) = &col.table {
21448                    self.generate_identifier(table)?;
21449                    self.write(".");
21450                }
21451                self.generate_identifier(&col.name)?;
21452                // Oracle-style join marker (+)
21453                if col.join_mark && self.config.supports_column_join_marks {
21454                    self.write(" (+)");
21455                }
21456                // Output column trailing comments if they're not already in left_comments
21457                if op.left_comments.is_empty() {
21458                    for comment in &col.trailing_comments {
21459                        self.write_space();
21460                        self.write_formatted_comment(comment);
21461                    }
21462                }
21463            }
21464            Expression::Add(inner_op)
21465            | Expression::Sub(inner_op)
21466            | Expression::Mul(inner_op)
21467            | Expression::Div(inner_op)
21468            | Expression::Concat(inner_op) => {
21469                // Generate binary op without its trailing comments
21470                self.generate_binary_op_no_trailing(inner_op, match &op.left {
21471                    Expression::Add(_) => "+",
21472                    Expression::Sub(_) => "-",
21473                    Expression::Mul(_) => "*",
21474                    Expression::Div(_) => "/",
21475                    Expression::Concat(_) => "||",
21476                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
21477                })?;
21478            }
21479            _ => {
21480                self.generate_expression(&op.left)?;
21481            }
21482        }
21483        // Output comments after left operand
21484        for comment in &op.left_comments {
21485            self.write_space();
21486            self.write_formatted_comment(comment);
21487        }
21488        if self.config.pretty
21489            && matches!(self.config.dialect, Some(DialectType::Snowflake))
21490            && (operator == "AND" || operator == "OR")
21491        {
21492            self.write_newline();
21493            self.write_indent();
21494            self.write_keyword(operator);
21495        } else {
21496            self.write_space();
21497            if operator.chars().all(|c| c.is_alphabetic()) {
21498                self.write_keyword(operator);
21499            } else {
21500                self.write(operator);
21501            }
21502        }
21503        // Output comments after operator (before right operand)
21504        for comment in &op.operator_comments {
21505            self.write_space();
21506            self.write_formatted_comment(comment);
21507        }
21508        self.write_space();
21509        self.generate_expression(&op.right)?;
21510        // Output trailing comments after right operand
21511        for comment in &op.trailing_comments {
21512            self.write_space();
21513            self.write_formatted_comment(comment);
21514        }
21515        Ok(())
21516    }
21517
21518    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
21519        let keyword = connector.keyword();
21520        let Some(terms) = self.flatten_connector_terms(op, connector) else {
21521            return self.generate_binary_op(op, keyword);
21522        };
21523
21524        self.generate_expression(terms[0])?;
21525        for term in terms.iter().skip(1) {
21526            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
21527                self.write_newline();
21528                self.write_indent();
21529                self.write_keyword(keyword);
21530            } else {
21531                self.write_space();
21532                self.write_keyword(keyword);
21533            }
21534            self.write_space();
21535            self.generate_expression(term)?;
21536        }
21537
21538        Ok(())
21539    }
21540
21541    fn flatten_connector_terms<'a>(
21542        &self,
21543        root: &'a BinaryOp,
21544        connector: ConnectorOperator,
21545    ) -> Option<Vec<&'a Expression>> {
21546        if !root.left_comments.is_empty()
21547            || !root.operator_comments.is_empty()
21548            || !root.trailing_comments.is_empty()
21549        {
21550            return None;
21551        }
21552
21553        let mut terms = Vec::new();
21554        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
21555
21556        while let Some(expr) = stack.pop() {
21557            match (connector, expr) {
21558                (ConnectorOperator::And, Expression::And(inner))
21559                    if inner.left_comments.is_empty()
21560                        && inner.operator_comments.is_empty()
21561                        && inner.trailing_comments.is_empty() =>
21562                {
21563                    stack.push(&inner.right);
21564                    stack.push(&inner.left);
21565                }
21566                (ConnectorOperator::Or, Expression::Or(inner))
21567                    if inner.left_comments.is_empty()
21568                        && inner.operator_comments.is_empty()
21569                        && inner.trailing_comments.is_empty() =>
21570                {
21571                    stack.push(&inner.right);
21572                    stack.push(&inner.left);
21573                }
21574                _ => terms.push(expr),
21575            }
21576        }
21577
21578        if terms.len() > 1 {
21579            Some(terms)
21580        } else {
21581            None
21582        }
21583    }
21584
21585    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
21586    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
21587        self.generate_expression(&op.left)?;
21588        self.write_space();
21589        // Drill backtick-quotes ILIKE
21590        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
21591            self.write("`ILIKE`");
21592        } else {
21593            self.write_keyword(operator);
21594        }
21595        if let Some(quantifier) = &op.quantifier {
21596            self.write_space();
21597            self.write_keyword(quantifier);
21598            // Match Python sqlglot behavior:
21599            // ANY + Paren (single value): no space → ILIKE ANY('%a%')
21600            // ANY + Tuple (multiple values): space → LIKE ANY ('a', 'b')
21601            // ALL + anything: always space → LIKE ALL ('%a%'), LIKE ALL ('a', 'b')
21602            let is_any =
21603                quantifier.eq_ignore_ascii_case("ANY") || quantifier.eq_ignore_ascii_case("SOME");
21604            if !(is_any && matches!(&op.right, Expression::Paren(_))) {
21605                self.write_space();
21606            }
21607        } else {
21608            self.write_space();
21609        }
21610        self.generate_expression(&op.right)?;
21611        if let Some(escape) = &op.escape {
21612            self.write_space();
21613            self.write_keyword("ESCAPE");
21614            self.write_space();
21615            self.generate_expression(escape)?;
21616        }
21617        Ok(())
21618    }
21619
21620    /// Generate null-safe equality
21621    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
21622    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
21623        use crate::dialects::DialectType;
21624        self.generate_expression(&op.left)?;
21625        self.write_space();
21626        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
21627            self.write("<=>");
21628        } else {
21629            self.write_keyword("IS NOT DISTINCT FROM");
21630        }
21631        self.write_space();
21632        self.generate_expression(&op.right)?;
21633        Ok(())
21634    }
21635
21636    /// Generate IS DISTINCT FROM (null-safe inequality)
21637    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
21638        self.generate_expression(&op.left)?;
21639        self.write_space();
21640        self.write_keyword("IS DISTINCT FROM");
21641        self.write_space();
21642        self.generate_expression(&op.right)?;
21643        Ok(())
21644    }
21645
21646    /// Generate binary op without trailing comments (used when nested inside another binary op)
21647    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
21648        // Generate left expression, but skip trailing comments
21649        match &op.left {
21650            Expression::Column(col) => {
21651                if let Some(table) = &col.table {
21652                    self.generate_identifier(table)?;
21653                    self.write(".");
21654                }
21655                self.generate_identifier(&col.name)?;
21656                // Oracle-style join marker (+)
21657                if col.join_mark && self.config.supports_column_join_marks {
21658                    self.write(" (+)");
21659                }
21660            }
21661            Expression::Add(inner_op)
21662            | Expression::Sub(inner_op)
21663            | Expression::Mul(inner_op)
21664            | Expression::Div(inner_op)
21665            | Expression::Concat(inner_op) => {
21666                self.generate_binary_op_no_trailing(inner_op, match &op.left {
21667                    Expression::Add(_) => "+",
21668                    Expression::Sub(_) => "-",
21669                    Expression::Mul(_) => "*",
21670                    Expression::Div(_) => "/",
21671                    Expression::Concat(_) => "||",
21672                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
21673                })?;
21674            }
21675            _ => {
21676                self.generate_expression(&op.left)?;
21677            }
21678        }
21679        // Output left_comments
21680        for comment in &op.left_comments {
21681            self.write_space();
21682            self.write_formatted_comment(comment);
21683        }
21684        self.write_space();
21685        if operator.chars().all(|c| c.is_alphabetic()) {
21686            self.write_keyword(operator);
21687        } else {
21688            self.write(operator);
21689        }
21690        // Output operator_comments
21691        for comment in &op.operator_comments {
21692            self.write_space();
21693            self.write_formatted_comment(comment);
21694        }
21695        self.write_space();
21696        // Generate right expression, but skip trailing comments if it's a Column
21697        // (the parent's left_comments will output them)
21698        match &op.right {
21699            Expression::Column(col) => {
21700                if let Some(table) = &col.table {
21701                    self.generate_identifier(table)?;
21702                    self.write(".");
21703                }
21704                self.generate_identifier(&col.name)?;
21705                // Oracle-style join marker (+)
21706                if col.join_mark && self.config.supports_column_join_marks {
21707                    self.write(" (+)");
21708                }
21709            }
21710            _ => {
21711                self.generate_expression(&op.right)?;
21712            }
21713        }
21714        // Skip trailing_comments - parent will handle them via its left_comments
21715        Ok(())
21716    }
21717
21718    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
21719        if operator.chars().all(|c| c.is_alphabetic()) {
21720            self.write_keyword(operator);
21721            self.write_space();
21722        } else {
21723            self.write(operator);
21724            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
21725            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
21726                self.write_space();
21727            }
21728        }
21729        self.generate_expression(&op.this)
21730    }
21731
21732    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
21733        // Generic mode supports two styles for negated IN:
21734        // - Prefix: NOT a IN (...)
21735        // - Infix:  a NOT IN (...)
21736        let is_generic =
21737            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
21738        let use_prefix_not =
21739            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
21740        if use_prefix_not {
21741            self.write_keyword("NOT");
21742            self.write_space();
21743        }
21744        self.generate_expression(&in_expr.this)?;
21745        if in_expr.global {
21746            self.write_space();
21747            self.write_keyword("GLOBAL");
21748        }
21749        if in_expr.not && !use_prefix_not {
21750            self.write_space();
21751            self.write_keyword("NOT");
21752        }
21753        self.write_space();
21754        self.write_keyword("IN");
21755
21756        // BigQuery: IN UNNEST(expr)
21757        if let Some(unnest_expr) = &in_expr.unnest {
21758            self.write_space();
21759            self.write_keyword("UNNEST");
21760            self.write("(");
21761            self.generate_expression(unnest_expr)?;
21762            self.write(")");
21763            return Ok(());
21764        }
21765
21766        if let Some(query) = &in_expr.query {
21767            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
21768            // vs a subquery (col IN (SELECT ...))
21769            let is_bare = in_expr.expressions.is_empty()
21770                && !matches!(
21771                    query,
21772                    Expression::Select(_)
21773                        | Expression::Union(_)
21774                        | Expression::Intersect(_)
21775                        | Expression::Except(_)
21776                        | Expression::Subquery(_)
21777                );
21778            if is_bare {
21779                // Bare identifier: no parentheses
21780                self.write_space();
21781                self.generate_expression(query)?;
21782            } else {
21783                // Subquery: with parentheses
21784                self.write(" (");
21785                let is_statement = matches!(
21786                    query,
21787                    Expression::Select(_)
21788                        | Expression::Union(_)
21789                        | Expression::Intersect(_)
21790                        | Expression::Except(_)
21791                        | Expression::Subquery(_)
21792                );
21793                if self.config.pretty && is_statement {
21794                    self.write_newline();
21795                    self.indent_level += 1;
21796                    self.write_indent();
21797                }
21798                self.generate_expression(query)?;
21799                if self.config.pretty && is_statement {
21800                    self.write_newline();
21801                    self.indent_level -= 1;
21802                    self.write_indent();
21803                }
21804                self.write(")");
21805            }
21806        } else {
21807            // DuckDB: IN without parentheses for single expression that is NOT a literal
21808            // (array/list membership like 'red' IN tbl.flags)
21809            // ClickHouse: IN without parentheses for single non-array expressions
21810            let is_duckdb = matches!(
21811                self.config.dialect,
21812                Some(crate::dialects::DialectType::DuckDB)
21813            );
21814            let is_clickhouse = matches!(
21815                self.config.dialect,
21816                Some(crate::dialects::DialectType::ClickHouse)
21817            );
21818            let single_expr = in_expr.expressions.len() == 1;
21819            if is_clickhouse && single_expr {
21820                if let Expression::Array(arr) = &in_expr.expressions[0] {
21821                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
21822                    self.write(" (");
21823                    for (i, expr) in arr.expressions.iter().enumerate() {
21824                        if i > 0 {
21825                            self.write(", ");
21826                        }
21827                        self.generate_expression(expr)?;
21828                    }
21829                    self.write(")");
21830                } else {
21831                    self.write_space();
21832                    self.generate_expression(&in_expr.expressions[0])?;
21833                }
21834            } else {
21835                let is_bare_ref = single_expr
21836                    && matches!(
21837                        &in_expr.expressions[0],
21838                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
21839                    );
21840                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
21841                    // Bare field reference (no parens in source): IN identifier
21842                    // Also DuckDB: IN without parentheses for array/list membership
21843                    self.write_space();
21844                    self.generate_expression(&in_expr.expressions[0])?;
21845                } else {
21846                    // Standard IN (list)
21847                    self.write(" (");
21848                    for (i, expr) in in_expr.expressions.iter().enumerate() {
21849                        if i > 0 {
21850                            self.write(", ");
21851                        }
21852                        self.generate_expression(expr)?;
21853                    }
21854                    self.write(")");
21855                }
21856            }
21857        }
21858
21859        Ok(())
21860    }
21861
21862    fn generate_between(&mut self, between: &Between) -> Result<()> {
21863        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
21864        let use_prefix_not = between.not
21865            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
21866        if use_prefix_not {
21867            self.write_keyword("NOT");
21868            self.write_space();
21869        }
21870        self.generate_expression(&between.this)?;
21871        if between.not && !use_prefix_not {
21872            self.write_space();
21873            self.write_keyword("NOT");
21874        }
21875        self.write_space();
21876        self.write_keyword("BETWEEN");
21877        // Emit SYMMETRIC/ASYMMETRIC if present
21878        if let Some(sym) = between.symmetric {
21879            if sym {
21880                self.write(" SYMMETRIC");
21881            } else {
21882                self.write(" ASYMMETRIC");
21883            }
21884        }
21885        self.write_space();
21886        self.generate_expression(&between.low)?;
21887        self.write_space();
21888        self.write_keyword("AND");
21889        self.write_space();
21890        self.generate_expression(&between.high)
21891    }
21892
21893    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
21894        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
21895        let use_prefix_not = is_null.not
21896            && (self.config.dialect.is_none()
21897                || self.config.dialect == Some(DialectType::Generic)
21898                || is_null.postfix_form);
21899        if use_prefix_not {
21900            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
21901            self.write_keyword("NOT");
21902            self.write_space();
21903            self.generate_expression(&is_null.this)?;
21904            self.write_space();
21905            self.write_keyword("IS");
21906            self.write_space();
21907            self.write_keyword("NULL");
21908        } else {
21909            self.generate_expression(&is_null.this)?;
21910            self.write_space();
21911            self.write_keyword("IS");
21912            if is_null.not {
21913                self.write_space();
21914                self.write_keyword("NOT");
21915            }
21916            self.write_space();
21917            self.write_keyword("NULL");
21918        }
21919        Ok(())
21920    }
21921
21922    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
21923        self.generate_expression(&is_true.this)?;
21924        self.write_space();
21925        self.write_keyword("IS");
21926        if is_true.not {
21927            self.write_space();
21928            self.write_keyword("NOT");
21929        }
21930        self.write_space();
21931        self.write_keyword("TRUE");
21932        Ok(())
21933    }
21934
21935    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
21936        self.generate_expression(&is_false.this)?;
21937        self.write_space();
21938        self.write_keyword("IS");
21939        if is_false.not {
21940            self.write_space();
21941            self.write_keyword("NOT");
21942        }
21943        self.write_space();
21944        self.write_keyword("FALSE");
21945        Ok(())
21946    }
21947
21948    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
21949        self.generate_expression(&is_json.this)?;
21950        self.write_space();
21951        self.write_keyword("IS");
21952        if is_json.negated {
21953            self.write_space();
21954            self.write_keyword("NOT");
21955        }
21956        self.write_space();
21957        self.write_keyword("JSON");
21958
21959        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
21960        if let Some(ref json_type) = is_json.json_type {
21961            self.write_space();
21962            self.write_keyword(json_type);
21963        }
21964
21965        // Output key uniqueness constraint if specified
21966        match &is_json.unique_keys {
21967            Some(JsonUniqueKeys::With) => {
21968                self.write_space();
21969                self.write_keyword("WITH UNIQUE KEYS");
21970            }
21971            Some(JsonUniqueKeys::Without) => {
21972                self.write_space();
21973                self.write_keyword("WITHOUT UNIQUE KEYS");
21974            }
21975            Some(JsonUniqueKeys::Shorthand) => {
21976                self.write_space();
21977                self.write_keyword("UNIQUE KEYS");
21978            }
21979            None => {}
21980        }
21981
21982        Ok(())
21983    }
21984
21985    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
21986        self.generate_expression(&is_expr.left)?;
21987        self.write_space();
21988        self.write_keyword("IS");
21989        self.write_space();
21990        self.generate_expression(&is_expr.right)
21991    }
21992
21993    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
21994        if exists.not {
21995            self.write_keyword("NOT");
21996            self.write_space();
21997        }
21998        self.write_keyword("EXISTS");
21999        self.write("(");
22000        let is_statement = matches!(
22001            &exists.this,
22002            Expression::Select(_)
22003                | Expression::Union(_)
22004                | Expression::Intersect(_)
22005                | Expression::Except(_)
22006        );
22007        if self.config.pretty && is_statement {
22008            self.write_newline();
22009            self.indent_level += 1;
22010            self.write_indent();
22011            self.generate_expression(&exists.this)?;
22012            self.write_newline();
22013            self.indent_level -= 1;
22014            self.write_indent();
22015            self.write(")");
22016        } else {
22017            self.generate_expression(&exists.this)?;
22018            self.write(")");
22019        }
22020        Ok(())
22021    }
22022
22023    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
22024        self.generate_expression(&op.left)?;
22025        self.write_space();
22026        self.write_keyword("MEMBER OF");
22027        self.write("(");
22028        self.generate_expression(&op.right)?;
22029        self.write(")");
22030        Ok(())
22031    }
22032
22033    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
22034        if subquery.lateral {
22035            self.write_keyword("LATERAL");
22036            self.write_space();
22037        }
22038
22039        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
22040        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
22041        // to carry the LIMIT modifier without adding more parens
22042        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
22043            matches!(
22044                &p.this,
22045                Expression::Select(_)
22046                    | Expression::Union(_)
22047                    | Expression::Intersect(_)
22048                    | Expression::Except(_)
22049                    | Expression::Subquery(_)
22050            )
22051        } else {
22052            false
22053        };
22054
22055        // Check if inner expression is a statement for pretty formatting
22056        let is_statement = matches!(
22057            &subquery.this,
22058            Expression::Select(_)
22059                | Expression::Union(_)
22060                | Expression::Intersect(_)
22061                | Expression::Except(_)
22062                | Expression::Merge(_)
22063        );
22064
22065        if !skip_outer_parens {
22066            self.write("(");
22067            if self.config.pretty && is_statement {
22068                self.write_newline();
22069                self.indent_level += 1;
22070                self.write_indent();
22071            }
22072        }
22073        self.generate_expression(&subquery.this)?;
22074
22075        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
22076        if subquery.modifiers_inside {
22077            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
22078            if let Some(order_by) = &subquery.order_by {
22079                self.write_space();
22080                self.write_keyword("ORDER BY");
22081                self.write_space();
22082                for (i, ord) in order_by.expressions.iter().enumerate() {
22083                    if i > 0 {
22084                        self.write(", ");
22085                    }
22086                    self.generate_ordered(ord)?;
22087                }
22088            }
22089
22090            if let Some(limit) = &subquery.limit {
22091                self.write_space();
22092                self.write_keyword("LIMIT");
22093                self.write_space();
22094                self.generate_expression(&limit.this)?;
22095                if limit.percent {
22096                    self.write_space();
22097                    self.write_keyword("PERCENT");
22098                }
22099            }
22100
22101            if let Some(offset) = &subquery.offset {
22102                self.write_space();
22103                self.write_keyword("OFFSET");
22104                self.write_space();
22105                self.generate_expression(&offset.this)?;
22106            }
22107        }
22108
22109        if !skip_outer_parens {
22110            if self.config.pretty && is_statement {
22111                self.write_newline();
22112                self.indent_level -= 1;
22113                self.write_indent();
22114            }
22115            self.write(")");
22116        }
22117
22118        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
22119        if !subquery.modifiers_inside {
22120            if let Some(order_by) = &subquery.order_by {
22121                self.write_space();
22122                self.write_keyword("ORDER BY");
22123                self.write_space();
22124                for (i, ord) in order_by.expressions.iter().enumerate() {
22125                    if i > 0 {
22126                        self.write(", ");
22127                    }
22128                    self.generate_ordered(ord)?;
22129                }
22130            }
22131
22132            if let Some(limit) = &subquery.limit {
22133                self.write_space();
22134                self.write_keyword("LIMIT");
22135                self.write_space();
22136                self.generate_expression(&limit.this)?;
22137                if limit.percent {
22138                    self.write_space();
22139                    self.write_keyword("PERCENT");
22140                }
22141            }
22142
22143            if let Some(offset) = &subquery.offset {
22144                self.write_space();
22145                self.write_keyword("OFFSET");
22146                self.write_space();
22147                self.generate_expression(&offset.this)?;
22148            }
22149
22150            // Generate DISTRIBUTE BY (Hive/Spark)
22151            if let Some(distribute_by) = &subquery.distribute_by {
22152                self.write_space();
22153                self.write_keyword("DISTRIBUTE BY");
22154                self.write_space();
22155                for (i, expr) in distribute_by.expressions.iter().enumerate() {
22156                    if i > 0 {
22157                        self.write(", ");
22158                    }
22159                    self.generate_expression(expr)?;
22160                }
22161            }
22162
22163            // Generate SORT BY (Hive/Spark)
22164            if let Some(sort_by) = &subquery.sort_by {
22165                self.write_space();
22166                self.write_keyword("SORT BY");
22167                self.write_space();
22168                for (i, ord) in sort_by.expressions.iter().enumerate() {
22169                    if i > 0 {
22170                        self.write(", ");
22171                    }
22172                    self.generate_ordered(ord)?;
22173                }
22174            }
22175
22176            // Generate CLUSTER BY (Hive/Spark)
22177            if let Some(cluster_by) = &subquery.cluster_by {
22178                self.write_space();
22179                self.write_keyword("CLUSTER BY");
22180                self.write_space();
22181                for (i, ord) in cluster_by.expressions.iter().enumerate() {
22182                    if i > 0 {
22183                        self.write(", ");
22184                    }
22185                    self.generate_ordered(ord)?;
22186                }
22187            }
22188        }
22189
22190        if let Some(alias) = &subquery.alias {
22191            self.write_space();
22192            // Oracle doesn't use AS for subquery aliases
22193            let skip_as = matches!(
22194                self.config.dialect,
22195                Some(crate::dialects::DialectType::Oracle)
22196            );
22197            if !skip_as {
22198                self.write_keyword("AS");
22199                self.write_space();
22200            }
22201            self.generate_identifier(alias)?;
22202            if !subquery.column_aliases.is_empty() {
22203                self.write("(");
22204                for (i, col) in subquery.column_aliases.iter().enumerate() {
22205                    if i > 0 {
22206                        self.write(", ");
22207                    }
22208                    self.generate_identifier(col)?;
22209                }
22210                self.write(")");
22211            }
22212        }
22213        // Output trailing comments
22214        for comment in &subquery.trailing_comments {
22215            self.write(" ");
22216            self.write_formatted_comment(comment);
22217        }
22218        Ok(())
22219    }
22220
22221    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
22222        // Generate WITH clause if present
22223        if let Some(ref with) = pivot.with {
22224            self.generate_with(with)?;
22225            self.write_space();
22226        }
22227
22228        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
22229
22230        // Check for Redshift UNPIVOT in FROM clause:
22231        // UNPIVOT expr [AS val AT attr]
22232        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
22233        let is_redshift_unpivot = pivot.unpivot
22234            && pivot.expressions.is_empty()
22235            && pivot.fields.is_empty()
22236            && pivot.using.is_empty()
22237            && pivot.into.is_none()
22238            && !matches!(&pivot.this, Expression::Null(_));
22239
22240        if is_redshift_unpivot {
22241            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
22242            self.write_keyword("UNPIVOT");
22243            self.write_space();
22244            self.generate_expression(&pivot.this)?;
22245            // Alias - for Redshift it can be "val AT attr" format
22246            if let Some(alias) = &pivot.alias {
22247                self.write_space();
22248                self.write_keyword("AS");
22249                self.write_space();
22250                // The alias might contain " AT " for the attr part
22251                self.write(&alias.name);
22252            }
22253            return Ok(());
22254        }
22255
22256        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
22257        let is_simplified = !pivot.using.is_empty()
22258            || pivot.into.is_some()
22259            || (pivot.fields.is_empty()
22260                && !pivot.expressions.is_empty()
22261                && !matches!(&pivot.this, Expression::Null(_)));
22262
22263        if is_simplified {
22264            // DuckDB simplified syntax:
22265            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
22266            //   UNPIVOT table ON cols INTO NAME col VALUE col
22267            self.write_keyword(direction);
22268            self.write_space();
22269            self.generate_expression(&pivot.this)?;
22270
22271            if !pivot.expressions.is_empty() {
22272                self.write_space();
22273                self.write_keyword("ON");
22274                self.write_space();
22275                for (i, expr) in pivot.expressions.iter().enumerate() {
22276                    if i > 0 {
22277                        self.write(", ");
22278                    }
22279                    self.generate_expression(expr)?;
22280                }
22281            }
22282
22283            // INTO (for UNPIVOT)
22284            if let Some(into) = &pivot.into {
22285                self.write_space();
22286                self.write_keyword("INTO");
22287                self.write_space();
22288                self.generate_expression(into)?;
22289            }
22290
22291            // USING (for PIVOT)
22292            if !pivot.using.is_empty() {
22293                self.write_space();
22294                self.write_keyword("USING");
22295                self.write_space();
22296                for (i, expr) in pivot.using.iter().enumerate() {
22297                    if i > 0 {
22298                        self.write(", ");
22299                    }
22300                    self.generate_expression(expr)?;
22301                }
22302            }
22303
22304            // GROUP BY
22305            if let Some(group) = &pivot.group {
22306                self.write_space();
22307                self.generate_expression(group)?;
22308            }
22309        } else {
22310            // Standard syntax:
22311            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
22312            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
22313            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
22314            if !matches!(&pivot.this, Expression::Null(_)) {
22315                self.generate_expression(&pivot.this)?;
22316                self.write_space();
22317            }
22318            self.write_keyword(direction);
22319            self.write("(");
22320
22321            // Aggregation expressions
22322            for (i, expr) in pivot.expressions.iter().enumerate() {
22323                if i > 0 {
22324                    self.write(", ");
22325                }
22326                self.generate_expression(expr)?;
22327            }
22328
22329            // FOR...IN fields
22330            if !pivot.fields.is_empty() {
22331                if !pivot.expressions.is_empty() {
22332                    self.write_space();
22333                }
22334                self.write_keyword("FOR");
22335                self.write_space();
22336                for (i, field) in pivot.fields.iter().enumerate() {
22337                    if i > 0 {
22338                        self.write_space();
22339                    }
22340                    // field is an In expression: column IN (values)
22341                    self.generate_expression(field)?;
22342                }
22343            }
22344
22345            // DEFAULT ON NULL
22346            if let Some(default_val) = &pivot.default_on_null {
22347                self.write_space();
22348                self.write_keyword("DEFAULT ON NULL");
22349                self.write(" (");
22350                self.generate_expression(default_val)?;
22351                self.write(")");
22352            }
22353
22354            // GROUP BY inside PIVOT parens
22355            if let Some(group) = &pivot.group {
22356                self.write_space();
22357                self.generate_expression(group)?;
22358            }
22359
22360            self.write(")");
22361        }
22362
22363        // Alias
22364        if let Some(alias) = &pivot.alias {
22365            self.write_space();
22366            self.write_keyword("AS");
22367            self.write_space();
22368            self.generate_identifier(alias)?;
22369        }
22370
22371        Ok(())
22372    }
22373
22374    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
22375        self.generate_expression(&unpivot.this)?;
22376        self.write_space();
22377        self.write_keyword("UNPIVOT");
22378        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
22379        if let Some(include) = unpivot.include_nulls {
22380            self.write_space();
22381            if include {
22382                self.write_keyword("INCLUDE NULLS");
22383            } else {
22384                self.write_keyword("EXCLUDE NULLS");
22385            }
22386            self.write_space();
22387        }
22388        self.write("(");
22389        if unpivot.value_column_parenthesized {
22390            self.write("(");
22391        }
22392        self.generate_identifier(&unpivot.value_column)?;
22393        // Output additional value columns if present
22394        for extra_col in &unpivot.extra_value_columns {
22395            self.write(", ");
22396            self.generate_identifier(extra_col)?;
22397        }
22398        if unpivot.value_column_parenthesized {
22399            self.write(")");
22400        }
22401        self.write_space();
22402        self.write_keyword("FOR");
22403        self.write_space();
22404        self.generate_identifier(&unpivot.name_column)?;
22405        self.write_space();
22406        self.write_keyword("IN");
22407        self.write(" (");
22408        for (i, col) in unpivot.columns.iter().enumerate() {
22409            if i > 0 {
22410                self.write(", ");
22411            }
22412            self.generate_expression(col)?;
22413        }
22414        self.write("))");
22415        if let Some(alias) = &unpivot.alias {
22416            self.write_space();
22417            self.write_keyword("AS");
22418            self.write_space();
22419            self.generate_identifier(alias)?;
22420        }
22421        Ok(())
22422    }
22423
22424    fn generate_values(&mut self, values: &Values) -> Result<()> {
22425        self.write_keyword("VALUES");
22426        for (i, row) in values.expressions.iter().enumerate() {
22427            if i > 0 {
22428                self.write(",");
22429            }
22430            self.write(" (");
22431            for (j, expr) in row.expressions.iter().enumerate() {
22432                if j > 0 {
22433                    self.write(", ");
22434                }
22435                self.generate_expression(expr)?;
22436            }
22437            self.write(")");
22438        }
22439        if let Some(alias) = &values.alias {
22440            self.write_space();
22441            self.write_keyword("AS");
22442            self.write_space();
22443            self.generate_identifier(alias)?;
22444            if !values.column_aliases.is_empty() {
22445                self.write("(");
22446                for (i, col) in values.column_aliases.iter().enumerate() {
22447                    if i > 0 {
22448                        self.write(", ");
22449                    }
22450                    self.generate_identifier(col)?;
22451                }
22452                self.write(")");
22453            }
22454        }
22455        Ok(())
22456    }
22457
22458    fn generate_array(&mut self, arr: &Array) -> Result<()> {
22459        // Apply struct name inheritance for target dialects that need it
22460        let needs_inheritance = matches!(
22461            self.config.dialect,
22462            Some(DialectType::DuckDB)
22463                | Some(DialectType::Spark)
22464                | Some(DialectType::Databricks)
22465                | Some(DialectType::Hive)
22466                | Some(DialectType::Snowflake)
22467                | Some(DialectType::Presto)
22468                | Some(DialectType::Trino)
22469        );
22470        let propagated: Vec<Expression>;
22471        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
22472            propagated = Self::inherit_struct_field_names(&arr.expressions);
22473            &propagated
22474        } else {
22475            &arr.expressions
22476        };
22477
22478        // Generic mode: ARRAY(1, 2, 3) with parentheses
22479        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
22480        let use_parens =
22481            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
22482        if !self.config.array_bracket_only {
22483            self.write_keyword("ARRAY");
22484        }
22485        if use_parens {
22486            self.write("(");
22487        } else {
22488            self.write("[");
22489        }
22490        for (i, expr) in expressions.iter().enumerate() {
22491            if i > 0 {
22492                self.write(", ");
22493            }
22494            self.generate_expression(expr)?;
22495        }
22496        if use_parens {
22497            self.write(")");
22498        } else {
22499            self.write("]");
22500        }
22501        Ok(())
22502    }
22503
22504    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
22505        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
22506        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
22507        if tuple.expressions.len() == 2 {
22508            if let Expression::TableAlias(_) = &tuple.expressions[1] {
22509                // First element is the function/expression, second is the TableAlias
22510                self.generate_expression(&tuple.expressions[0])?;
22511                self.write_space();
22512                self.write_keyword("AS");
22513                self.write_space();
22514                self.generate_expression(&tuple.expressions[1])?;
22515                return Ok(());
22516            }
22517        }
22518
22519        // In pretty mode, format long tuples with each element on a new line
22520        // Only expand if total width exceeds threshold
22521        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
22522            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
22523            for expr in &tuple.expressions {
22524                expr_strings.push(self.generate_to_string(expr)?);
22525            }
22526            self.too_wide(&expr_strings)
22527        } else {
22528            false
22529        };
22530
22531        if expand_tuple {
22532            self.write("(");
22533            self.write_newline();
22534            self.indent_level += 1;
22535            for (i, expr) in tuple.expressions.iter().enumerate() {
22536                if i > 0 {
22537                    self.write(",");
22538                    self.write_newline();
22539                }
22540                self.write_indent();
22541                self.generate_expression(expr)?;
22542            }
22543            self.indent_level -= 1;
22544            self.write_newline();
22545            self.write_indent();
22546            self.write(")");
22547        } else {
22548            self.write("(");
22549            for (i, expr) in tuple.expressions.iter().enumerate() {
22550                if i > 0 {
22551                    self.write(", ");
22552                }
22553                self.generate_expression(expr)?;
22554            }
22555            self.write(")");
22556        }
22557        Ok(())
22558    }
22559
22560    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
22561        self.generate_expression(&pipe.this)?;
22562        self.write(" |> ");
22563        self.generate_expression(&pipe.expression)?;
22564        Ok(())
22565    }
22566
22567    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
22568        self.generate_expression(&ordered.this)?;
22569        if ordered.desc {
22570            self.write_space();
22571            self.write_keyword("DESC");
22572        } else if ordered.explicit_asc {
22573            self.write_space();
22574            self.write_keyword("ASC");
22575        }
22576        if let Some(nulls_first) = ordered.nulls_first {
22577            // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
22578            // for the dialect. Different dialects have different NULL ordering defaults:
22579            //
22580            // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
22581            //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
22582            //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
22583            //
22584            // nulls_are_small (Spark, Hive, BigQuery, most others):
22585            //   - ASC: NULLS FIRST is default
22586            //   - DESC: NULLS LAST is default
22587            //
22588            // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
22589            //   - NULLS LAST is always the default regardless of sort direction
22590            let is_asc = !ordered.desc;
22591            let is_nulls_are_large = matches!(
22592                self.config.dialect,
22593                Some(DialectType::Oracle)
22594                    | Some(DialectType::PostgreSQL)
22595                    | Some(DialectType::Redshift)
22596                    | Some(DialectType::Snowflake)
22597            );
22598            let is_nulls_are_last = matches!(
22599                self.config.dialect,
22600                Some(DialectType::Dremio)
22601                    | Some(DialectType::DuckDB)
22602                    | Some(DialectType::Presto)
22603                    | Some(DialectType::Trino)
22604                    | Some(DialectType::Athena)
22605                    | Some(DialectType::ClickHouse)
22606                    | Some(DialectType::Drill)
22607                    | Some(DialectType::Exasol)
22608            );
22609
22610            // Check if the NULLS ordering matches the default for this dialect
22611            let is_default_nulls = if is_nulls_are_large {
22612                // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
22613                (is_asc && !nulls_first) || (!is_asc && nulls_first)
22614            } else if is_nulls_are_last {
22615                // For nulls_are_last: NULLS LAST is always default
22616                !nulls_first
22617            } else {
22618                false
22619            };
22620
22621            if !is_default_nulls {
22622                self.write_space();
22623                self.write_keyword("NULLS");
22624                self.write_space();
22625                self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
22626            }
22627        }
22628        // WITH FILL clause (ClickHouse)
22629        if let Some(ref with_fill) = ordered.with_fill {
22630            self.write_space();
22631            self.generate_with_fill(with_fill)?;
22632        }
22633        Ok(())
22634    }
22635
22636    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
22637    fn write_clickhouse_type(&mut self, type_str: &str) {
22638        if self.clickhouse_nullable_depth < 0 {
22639            // Map key context: don't wrap in Nullable
22640            self.write(type_str);
22641        } else {
22642            self.write(&format!("Nullable({})", type_str));
22643        }
22644    }
22645
22646    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
22647        use crate::dialects::DialectType;
22648
22649        match dt {
22650            DataType::Boolean => {
22651                // Dialect-specific boolean type mappings
22652                match self.config.dialect {
22653                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
22654                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
22655                    Some(DialectType::Oracle) => {
22656                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
22657                        self.write_keyword("NUMBER(1)")
22658                    }
22659                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
22660                    _ => self.write_keyword("BOOLEAN"),
22661                }
22662            }
22663            DataType::TinyInt { length } => {
22664                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
22665                // Dremio maps TINYINT to INT
22666                // ClickHouse maps TINYINT to Int8
22667                match self.config.dialect {
22668                    Some(DialectType::PostgreSQL)
22669                    | Some(DialectType::Redshift)
22670                    | Some(DialectType::Oracle)
22671                    | Some(DialectType::Exasol) => {
22672                        self.write_keyword("SMALLINT");
22673                    }
22674                    Some(DialectType::Teradata) => {
22675                        // Teradata uses BYTEINT for smallest integer
22676                        self.write_keyword("BYTEINT");
22677                    }
22678                    Some(DialectType::Dremio) => {
22679                        // Dremio maps TINYINT to INT
22680                        self.write_keyword("INT");
22681                    }
22682                    Some(DialectType::ClickHouse) => {
22683                        self.write_clickhouse_type("Int8");
22684                    }
22685                    _ => {
22686                        self.write_keyword("TINYINT");
22687                    }
22688                }
22689                if let Some(n) = length {
22690                    if !matches!(
22691                        self.config.dialect,
22692                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
22693                    ) {
22694                        self.write(&format!("({})", n));
22695                    }
22696                }
22697            }
22698            DataType::SmallInt { length } => {
22699                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
22700                match self.config.dialect {
22701                    Some(DialectType::Dremio) => {
22702                        self.write_keyword("INT");
22703                    }
22704                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
22705                        self.write_keyword("INTEGER");
22706                    }
22707                    Some(DialectType::BigQuery) => {
22708                        self.write_keyword("INT64");
22709                    }
22710                    Some(DialectType::ClickHouse) => {
22711                        self.write_clickhouse_type("Int16");
22712                    }
22713                    _ => {
22714                        self.write_keyword("SMALLINT");
22715                        if let Some(n) = length {
22716                            self.write(&format!("({})", n));
22717                        }
22718                    }
22719                }
22720            }
22721            DataType::Int {
22722                length,
22723                integer_spelling: _,
22724            } => {
22725                // BigQuery uses INT64 for INT
22726                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
22727                    self.write_keyword("INT64");
22728                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22729                    self.write_clickhouse_type("Int32");
22730                } else {
22731                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
22732                    let use_integer = match self.config.dialect {
22733                        Some(DialectType::TSQL)
22734                        | Some(DialectType::Fabric)
22735                        | Some(DialectType::Presto)
22736                        | Some(DialectType::Trino)
22737                        | Some(DialectType::SQLite)
22738                        | Some(DialectType::Redshift) => true,
22739                        _ => false,
22740                    };
22741                    if use_integer {
22742                        self.write_keyword("INTEGER");
22743                    } else {
22744                        self.write_keyword("INT");
22745                    }
22746                    if let Some(n) = length {
22747                        self.write(&format!("({})", n));
22748                    }
22749                }
22750            }
22751            DataType::BigInt { length } => {
22752                // Dialect-specific bigint type mappings
22753                match self.config.dialect {
22754                    Some(DialectType::Oracle) => {
22755                        // Oracle doesn't have BIGINT, uses INT
22756                        self.write_keyword("INT");
22757                    }
22758                    Some(DialectType::ClickHouse) => {
22759                        self.write_clickhouse_type("Int64");
22760                    }
22761                    _ => {
22762                        self.write_keyword("BIGINT");
22763                        if let Some(n) = length {
22764                            self.write(&format!("({})", n));
22765                        }
22766                    }
22767                }
22768            }
22769            DataType::Float {
22770                precision,
22771                scale,
22772                real_spelling,
22773            } => {
22774                // Dialect-specific float type mappings
22775                // If real_spelling is true, preserve REAL; otherwise use dialect default
22776                // Spark/Hive don't support REAL, always use FLOAT
22777                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22778                    self.write_clickhouse_type("Float32");
22779                } else if *real_spelling
22780                    && !matches!(
22781                        self.config.dialect,
22782                        Some(DialectType::Spark)
22783                            | Some(DialectType::Databricks)
22784                            | Some(DialectType::Hive)
22785                            | Some(DialectType::Snowflake)
22786                            | Some(DialectType::MySQL)
22787                            | Some(DialectType::BigQuery)
22788                    )
22789                {
22790                    self.write_keyword("REAL")
22791                } else {
22792                    match self.config.dialect {
22793                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
22794                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
22795                        _ => self.write_keyword("FLOAT"),
22796                    }
22797                }
22798                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
22799                // Spark/Hive don't support FLOAT(precision)
22800                if !matches!(
22801                    self.config.dialect,
22802                    Some(DialectType::Spark)
22803                        | Some(DialectType::Databricks)
22804                        | Some(DialectType::Hive)
22805                        | Some(DialectType::Presto)
22806                        | Some(DialectType::Trino)
22807                ) {
22808                    if let Some(p) = precision {
22809                        self.write(&format!("({}", p));
22810                        if let Some(s) = scale {
22811                            self.write(&format!(", {})", s));
22812                        } else {
22813                            self.write(")");
22814                        }
22815                    }
22816                }
22817            }
22818            DataType::Double { precision, scale } => {
22819                // Dialect-specific double type mappings
22820                match self.config.dialect {
22821                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22822                        self.write_keyword("FLOAT")
22823                    } // SQL Server/Fabric FLOAT is double
22824                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
22825                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
22826                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
22827                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
22828                    Some(DialectType::PostgreSQL)
22829                    | Some(DialectType::Redshift)
22830                    | Some(DialectType::Teradata)
22831                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
22832                    _ => self.write_keyword("DOUBLE"),
22833                }
22834                // MySQL supports DOUBLE(precision, scale)
22835                if let Some(p) = precision {
22836                    self.write(&format!("({}", p));
22837                    if let Some(s) = scale {
22838                        self.write(&format!(", {})", s));
22839                    } else {
22840                        self.write(")");
22841                    }
22842                }
22843            }
22844            DataType::Decimal { precision, scale } => {
22845                // Dialect-specific decimal type mappings
22846                match self.config.dialect {
22847                    Some(DialectType::ClickHouse) => {
22848                        self.write("Decimal");
22849                        if let Some(p) = precision {
22850                            self.write(&format!("({}", p));
22851                            if let Some(s) = scale {
22852                                self.write(&format!(", {}", s));
22853                            }
22854                            self.write(")");
22855                        }
22856                    }
22857                    Some(DialectType::Oracle) => {
22858                        // Oracle uses NUMBER instead of DECIMAL
22859                        self.write_keyword("NUMBER");
22860                        if let Some(p) = precision {
22861                            self.write(&format!("({}", p));
22862                            if let Some(s) = scale {
22863                                self.write(&format!(", {}", s));
22864                            }
22865                            self.write(")");
22866                        }
22867                    }
22868                    Some(DialectType::BigQuery) => {
22869                        // BigQuery uses NUMERIC instead of DECIMAL
22870                        self.write_keyword("NUMERIC");
22871                        if let Some(p) = precision {
22872                            self.write(&format!("({}", p));
22873                            if let Some(s) = scale {
22874                                self.write(&format!(", {}", s));
22875                            }
22876                            self.write(")");
22877                        }
22878                    }
22879                    _ => {
22880                        self.write_keyword("DECIMAL");
22881                        if let Some(p) = precision {
22882                            self.write(&format!("({}", p));
22883                            if let Some(s) = scale {
22884                                self.write(&format!(", {}", s));
22885                            }
22886                            self.write(")");
22887                        }
22888                    }
22889                }
22890            }
22891            DataType::Char { length } => {
22892                // Dialect-specific char type mappings
22893                match self.config.dialect {
22894                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
22895                        // DuckDB/SQLite maps CHAR to TEXT
22896                        self.write_keyword("TEXT");
22897                    }
22898                    Some(DialectType::Hive)
22899                    | Some(DialectType::Spark)
22900                    | Some(DialectType::Databricks) => {
22901                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
22902                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
22903                        if length.is_some()
22904                            && !matches!(self.config.dialect, Some(DialectType::Hive))
22905                        {
22906                            self.write_keyword("CHAR");
22907                            if let Some(n) = length {
22908                                self.write(&format!("({})", n));
22909                            }
22910                        } else {
22911                            self.write_keyword("STRING");
22912                        }
22913                    }
22914                    Some(DialectType::Dremio) => {
22915                        // Dremio maps CHAR to VARCHAR
22916                        self.write_keyword("VARCHAR");
22917                        if let Some(n) = length {
22918                            self.write(&format!("({})", n));
22919                        }
22920                    }
22921                    _ => {
22922                        self.write_keyword("CHAR");
22923                        if let Some(n) = length {
22924                            self.write(&format!("({})", n));
22925                        }
22926                    }
22927                }
22928            }
22929            DataType::VarChar {
22930                length,
22931                parenthesized_length,
22932            } => {
22933                // Dialect-specific varchar type mappings
22934                match self.config.dialect {
22935                    Some(DialectType::Oracle) => {
22936                        self.write_keyword("VARCHAR2");
22937                        if let Some(n) = length {
22938                            self.write(&format!("({})", n));
22939                        }
22940                    }
22941                    Some(DialectType::DuckDB) => {
22942                        // DuckDB maps VARCHAR to TEXT, preserving length
22943                        self.write_keyword("TEXT");
22944                        if let Some(n) = length {
22945                            self.write(&format!("({})", n));
22946                        }
22947                    }
22948                    Some(DialectType::SQLite) => {
22949                        // SQLite maps VARCHAR to TEXT, preserving length
22950                        self.write_keyword("TEXT");
22951                        if let Some(n) = length {
22952                            self.write(&format!("({})", n));
22953                        }
22954                    }
22955                    Some(DialectType::MySQL) if length.is_none() => {
22956                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
22957                        self.write_keyword("TEXT");
22958                    }
22959                    Some(DialectType::Hive)
22960                    | Some(DialectType::Spark)
22961                    | Some(DialectType::Databricks)
22962                        if length.is_none() =>
22963                    {
22964                        // Hive/Spark/Databricks: VARCHAR without length → STRING
22965                        self.write_keyword("STRING");
22966                    }
22967                    _ => {
22968                        self.write_keyword("VARCHAR");
22969                        if let Some(n) = length {
22970                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
22971                            if *parenthesized_length {
22972                                self.write(&format!("(({}))", n));
22973                            } else {
22974                                self.write(&format!("({})", n));
22975                            }
22976                        }
22977                    }
22978                }
22979            }
22980            DataType::Text => {
22981                // Dialect-specific text type mappings
22982                match self.config.dialect {
22983                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
22984                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22985                        self.write_keyword("VARCHAR(MAX)")
22986                    }
22987                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
22988                    Some(DialectType::Snowflake)
22989                    | Some(DialectType::Dremio)
22990                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
22991                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
22992                    Some(DialectType::Presto)
22993                    | Some(DialectType::Trino)
22994                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
22995                    Some(DialectType::Spark)
22996                    | Some(DialectType::Databricks)
22997                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
22998                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
22999                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
23000                        self.write_keyword("STRING")
23001                    }
23002                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
23003                    _ => self.write_keyword("TEXT"),
23004                }
23005            }
23006            DataType::TextWithLength { length } => {
23007                // TEXT(n) - dialect-specific type with length
23008                match self.config.dialect {
23009                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
23010                    Some(DialectType::Hive)
23011                    | Some(DialectType::Spark)
23012                    | Some(DialectType::Databricks) => {
23013                        self.write(&format!("VARCHAR({})", length));
23014                    }
23015                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
23016                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
23017                    Some(DialectType::Snowflake)
23018                    | Some(DialectType::Presto)
23019                    | Some(DialectType::Trino)
23020                    | Some(DialectType::Athena)
23021                    | Some(DialectType::Drill)
23022                    | Some(DialectType::Dremio) => {
23023                        self.write(&format!("VARCHAR({})", length));
23024                    }
23025                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23026                        self.write(&format!("VARCHAR({})", length))
23027                    }
23028                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
23029                        self.write(&format!("STRING({})", length))
23030                    }
23031                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
23032                    _ => self.write(&format!("TEXT({})", length)),
23033                }
23034            }
23035            DataType::String { length } => {
23036                // STRING type with optional length (BigQuery STRING(n))
23037                match self.config.dialect {
23038                    Some(DialectType::ClickHouse) => {
23039                        // ClickHouse uses String with specific casing
23040                        self.write("String");
23041                        if let Some(n) = length {
23042                            self.write(&format!("({})", n));
23043                        }
23044                    }
23045                    Some(DialectType::BigQuery)
23046                    | Some(DialectType::Hive)
23047                    | Some(DialectType::Spark)
23048                    | Some(DialectType::Databricks)
23049                    | Some(DialectType::StarRocks)
23050                    | Some(DialectType::Doris) => {
23051                        self.write_keyword("STRING");
23052                        if let Some(n) = length {
23053                            self.write(&format!("({})", n));
23054                        }
23055                    }
23056                    Some(DialectType::PostgreSQL) => {
23057                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
23058                        if let Some(n) = length {
23059                            self.write_keyword("VARCHAR");
23060                            self.write(&format!("({})", n));
23061                        } else {
23062                            self.write_keyword("TEXT");
23063                        }
23064                    }
23065                    Some(DialectType::Redshift) => {
23066                        // Redshift: STRING -> VARCHAR(MAX)
23067                        if let Some(n) = length {
23068                            self.write_keyword("VARCHAR");
23069                            self.write(&format!("({})", n));
23070                        } else {
23071                            self.write_keyword("VARCHAR(MAX)");
23072                        }
23073                    }
23074                    Some(DialectType::MySQL) => {
23075                        // MySQL doesn't have STRING - use VARCHAR or TEXT
23076                        if let Some(n) = length {
23077                            self.write_keyword("VARCHAR");
23078                            self.write(&format!("({})", n));
23079                        } else {
23080                            self.write_keyword("TEXT");
23081                        }
23082                    }
23083                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23084                        // TSQL: STRING -> VARCHAR(MAX)
23085                        if let Some(n) = length {
23086                            self.write_keyword("VARCHAR");
23087                            self.write(&format!("({})", n));
23088                        } else {
23089                            self.write_keyword("VARCHAR(MAX)");
23090                        }
23091                    }
23092                    Some(DialectType::Oracle) => {
23093                        // Oracle: STRING -> CLOB
23094                        self.write_keyword("CLOB");
23095                    }
23096                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
23097                        // DuckDB/Materialize uses TEXT for string types
23098                        self.write_keyword("TEXT");
23099                        if let Some(n) = length {
23100                            self.write(&format!("({})", n));
23101                        }
23102                    }
23103                    Some(DialectType::Presto)
23104                    | Some(DialectType::Trino)
23105                    | Some(DialectType::Drill)
23106                    | Some(DialectType::Dremio) => {
23107                        // Presto/Trino/Drill use VARCHAR for string types
23108                        self.write_keyword("VARCHAR");
23109                        if let Some(n) = length {
23110                            self.write(&format!("({})", n));
23111                        }
23112                    }
23113                    Some(DialectType::Snowflake) => {
23114                        // Snowflake: STRING stays as STRING (identity/DDL)
23115                        // CAST context STRING -> VARCHAR is handled in generate_cast
23116                        self.write_keyword("STRING");
23117                        if let Some(n) = length {
23118                            self.write(&format!("({})", n));
23119                        }
23120                    }
23121                    _ => {
23122                        // Default: output STRING with optional length
23123                        self.write_keyword("STRING");
23124                        if let Some(n) = length {
23125                            self.write(&format!("({})", n));
23126                        }
23127                    }
23128                }
23129            }
23130            DataType::Binary { length } => {
23131                // Dialect-specific binary type mappings
23132                match self.config.dialect {
23133                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
23134                        self.write_keyword("BYTEA");
23135                        if let Some(n) = length {
23136                            self.write(&format!("({})", n));
23137                        }
23138                    }
23139                    Some(DialectType::Redshift) => {
23140                        self.write_keyword("VARBYTE");
23141                        if let Some(n) = length {
23142                            self.write(&format!("({})", n));
23143                        }
23144                    }
23145                    Some(DialectType::DuckDB)
23146                    | Some(DialectType::SQLite)
23147                    | Some(DialectType::Oracle) => {
23148                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
23149                        self.write_keyword("BLOB");
23150                        if let Some(n) = length {
23151                            self.write(&format!("({})", n));
23152                        }
23153                    }
23154                    Some(DialectType::Presto)
23155                    | Some(DialectType::Trino)
23156                    | Some(DialectType::Athena)
23157                    | Some(DialectType::Drill)
23158                    | Some(DialectType::Dremio) => {
23159                        // These dialects map BINARY to VARBINARY
23160                        self.write_keyword("VARBINARY");
23161                        if let Some(n) = length {
23162                            self.write(&format!("({})", n));
23163                        }
23164                    }
23165                    Some(DialectType::ClickHouse) => {
23166                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
23167                        if self.clickhouse_nullable_depth < 0 {
23168                            self.write("BINARY");
23169                        } else {
23170                            self.write("Nullable(BINARY");
23171                        }
23172                        if let Some(n) = length {
23173                            self.write(&format!("({})", n));
23174                        }
23175                        if self.clickhouse_nullable_depth >= 0 {
23176                            self.write(")");
23177                        }
23178                    }
23179                    _ => {
23180                        self.write_keyword("BINARY");
23181                        if let Some(n) = length {
23182                            self.write(&format!("({})", n));
23183                        }
23184                    }
23185                }
23186            }
23187            DataType::VarBinary { length } => {
23188                // Dialect-specific varbinary type mappings
23189                match self.config.dialect {
23190                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
23191                        self.write_keyword("BYTEA");
23192                        if let Some(n) = length {
23193                            self.write(&format!("({})", n));
23194                        }
23195                    }
23196                    Some(DialectType::Redshift) => {
23197                        self.write_keyword("VARBYTE");
23198                        if let Some(n) = length {
23199                            self.write(&format!("({})", n));
23200                        }
23201                    }
23202                    Some(DialectType::DuckDB)
23203                    | Some(DialectType::SQLite)
23204                    | Some(DialectType::Oracle) => {
23205                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
23206                        self.write_keyword("BLOB");
23207                        if let Some(n) = length {
23208                            self.write(&format!("({})", n));
23209                        }
23210                    }
23211                    Some(DialectType::Exasol) => {
23212                        // Exasol maps VARBINARY to VARCHAR
23213                        self.write_keyword("VARCHAR");
23214                    }
23215                    Some(DialectType::Spark)
23216                    | Some(DialectType::Hive)
23217                    | Some(DialectType::Databricks) => {
23218                        // Spark/Hive use BINARY instead of VARBINARY
23219                        self.write_keyword("BINARY");
23220                        if let Some(n) = length {
23221                            self.write(&format!("({})", n));
23222                        }
23223                    }
23224                    Some(DialectType::ClickHouse) => {
23225                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
23226                        self.write_clickhouse_type("String");
23227                    }
23228                    _ => {
23229                        self.write_keyword("VARBINARY");
23230                        if let Some(n) = length {
23231                            self.write(&format!("({})", n));
23232                        }
23233                    }
23234                }
23235            }
23236            DataType::Blob => {
23237                // Dialect-specific blob type mappings
23238                match self.config.dialect {
23239                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
23240                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
23241                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23242                        self.write_keyword("VARBINARY")
23243                    }
23244                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
23245                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
23246                    Some(DialectType::Presto)
23247                    | Some(DialectType::Trino)
23248                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
23249                    Some(DialectType::DuckDB) => {
23250                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
23251                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
23252                        self.write_keyword("VARBINARY");
23253                    }
23254                    Some(DialectType::Spark)
23255                    | Some(DialectType::Databricks)
23256                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
23257                    Some(DialectType::ClickHouse) => {
23258                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
23259                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
23260                        // This matches Python sqlglot behavior.
23261                        self.write("Nullable(String)");
23262                    }
23263                    _ => self.write_keyword("BLOB"),
23264                }
23265            }
23266            DataType::Bit { length } => {
23267                // Dialect-specific bit type mappings
23268                match self.config.dialect {
23269                    Some(DialectType::Dremio)
23270                    | Some(DialectType::Spark)
23271                    | Some(DialectType::Databricks)
23272                    | Some(DialectType::Hive)
23273                    | Some(DialectType::Snowflake)
23274                    | Some(DialectType::BigQuery)
23275                    | Some(DialectType::Presto)
23276                    | Some(DialectType::Trino)
23277                    | Some(DialectType::ClickHouse)
23278                    | Some(DialectType::Redshift) => {
23279                        // These dialects don't support BIT type, use BOOLEAN
23280                        self.write_keyword("BOOLEAN");
23281                    }
23282                    _ => {
23283                        self.write_keyword("BIT");
23284                        if let Some(n) = length {
23285                            self.write(&format!("({})", n));
23286                        }
23287                    }
23288                }
23289            }
23290            DataType::VarBit { length } => {
23291                self.write_keyword("VARBIT");
23292                if let Some(n) = length {
23293                    self.write(&format!("({})", n));
23294                }
23295            }
23296            DataType::Date => self.write_keyword("DATE"),
23297            DataType::Time {
23298                precision,
23299                timezone,
23300            } => {
23301                if *timezone {
23302                    // Dialect-specific TIME WITH TIME ZONE output
23303                    match self.config.dialect {
23304                        Some(DialectType::DuckDB) => {
23305                            // DuckDB: TIMETZ (drops precision)
23306                            self.write_keyword("TIMETZ");
23307                        }
23308                        Some(DialectType::PostgreSQL) => {
23309                            // PostgreSQL: TIMETZ or TIMETZ(p)
23310                            self.write_keyword("TIMETZ");
23311                            if let Some(p) = precision {
23312                                self.write(&format!("({})", p));
23313                            }
23314                        }
23315                        _ => {
23316                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
23317                            self.write_keyword("TIME");
23318                            if let Some(p) = precision {
23319                                self.write(&format!("({})", p));
23320                            }
23321                            self.write_keyword(" WITH TIME ZONE");
23322                        }
23323                    }
23324                } else {
23325                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
23326                    if matches!(
23327                        self.config.dialect,
23328                        Some(DialectType::Spark)
23329                            | Some(DialectType::Databricks)
23330                            | Some(DialectType::Hive)
23331                    ) {
23332                        self.write_keyword("TIMESTAMP");
23333                    } else {
23334                        self.write_keyword("TIME");
23335                        if let Some(p) = precision {
23336                            self.write(&format!("({})", p));
23337                        }
23338                    }
23339                }
23340            }
23341            DataType::Timestamp {
23342                precision,
23343                timezone,
23344            } => {
23345                // Dialect-specific timestamp type mappings
23346                match self.config.dialect {
23347                    Some(DialectType::ClickHouse) => {
23348                        self.write("DateTime");
23349                        if let Some(p) = precision {
23350                            self.write(&format!("({})", p));
23351                        }
23352                    }
23353                    Some(DialectType::TSQL) => {
23354                        if *timezone {
23355                            self.write_keyword("DATETIMEOFFSET");
23356                        } else {
23357                            self.write_keyword("DATETIME2");
23358                        }
23359                        if let Some(p) = precision {
23360                            self.write(&format!("({})", p));
23361                        }
23362                    }
23363                    Some(DialectType::MySQL) => {
23364                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
23365                        self.write_keyword("TIMESTAMP");
23366                        if let Some(p) = precision {
23367                            self.write(&format!("({})", p));
23368                        }
23369                    }
23370                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
23371                        // Doris/StarRocks: TIMESTAMP -> DATETIME
23372                        self.write_keyword("DATETIME");
23373                        if let Some(p) = precision {
23374                            self.write(&format!("({})", p));
23375                        }
23376                    }
23377                    Some(DialectType::BigQuery) => {
23378                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
23379                        if *timezone {
23380                            self.write_keyword("TIMESTAMP");
23381                        } else {
23382                            self.write_keyword("DATETIME");
23383                        }
23384                    }
23385                    Some(DialectType::DuckDB) => {
23386                        // DuckDB: TIMESTAMPTZ shorthand
23387                        if *timezone {
23388                            self.write_keyword("TIMESTAMPTZ");
23389                        } else {
23390                            self.write_keyword("TIMESTAMP");
23391                            if let Some(p) = precision {
23392                                self.write(&format!("({})", p));
23393                            }
23394                        }
23395                    }
23396                    _ => {
23397                        if *timezone && !self.config.tz_to_with_time_zone {
23398                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
23399                            self.write_keyword("TIMESTAMPTZ");
23400                            if let Some(p) = precision {
23401                                self.write(&format!("({})", p));
23402                            }
23403                        } else {
23404                            self.write_keyword("TIMESTAMP");
23405                            if let Some(p) = precision {
23406                                self.write(&format!("({})", p));
23407                            }
23408                            if *timezone {
23409                                self.write_space();
23410                                self.write_keyword("WITH TIME ZONE");
23411                            }
23412                        }
23413                    }
23414                }
23415            }
23416            DataType::Interval { unit, to } => {
23417                self.write_keyword("INTERVAL");
23418                if let Some(u) = unit {
23419                    self.write_space();
23420                    self.write_keyword(u);
23421                }
23422                // Handle range intervals like DAY TO HOUR
23423                if let Some(t) = to {
23424                    self.write_space();
23425                    self.write_keyword("TO");
23426                    self.write_space();
23427                    self.write_keyword(t);
23428                }
23429            }
23430            DataType::Json => {
23431                // Dialect-specific JSON type mappings
23432                match self.config.dialect {
23433                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
23434                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
23435                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
23436                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
23437                    _ => self.write_keyword("JSON"),
23438                }
23439            }
23440            DataType::JsonB => {
23441                // JSONB is PostgreSQL specific, but Doris also supports it
23442                match self.config.dialect {
23443                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
23444                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
23445                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
23446                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
23447                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
23448                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
23449                }
23450            }
23451            DataType::Uuid => {
23452                // Dialect-specific UUID type mappings
23453                match self.config.dialect {
23454                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
23455                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
23456                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
23457                    Some(DialectType::BigQuery)
23458                    | Some(DialectType::Spark)
23459                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
23460                    _ => self.write_keyword("UUID"),
23461                }
23462            }
23463            DataType::Array {
23464                element_type,
23465                dimension,
23466            } => {
23467                // Dialect-specific array syntax
23468                match self.config.dialect {
23469                    Some(DialectType::PostgreSQL)
23470                    | Some(DialectType::Redshift)
23471                    | Some(DialectType::DuckDB) => {
23472                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
23473                        self.generate_data_type(element_type)?;
23474                        if let Some(dim) = dimension {
23475                            self.write(&format!("[{}]", dim));
23476                        } else {
23477                            self.write("[]");
23478                        }
23479                    }
23480                    Some(DialectType::BigQuery) => {
23481                        self.write_keyword("ARRAY<");
23482                        self.generate_data_type(element_type)?;
23483                        self.write(">");
23484                    }
23485                    Some(DialectType::Snowflake)
23486                    | Some(DialectType::Presto)
23487                    | Some(DialectType::Trino)
23488                    | Some(DialectType::ClickHouse) => {
23489                        // These dialects use Array(TYPE) parentheses syntax
23490                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23491                            self.write("Array(");
23492                        } else {
23493                            self.write_keyword("ARRAY(");
23494                        }
23495                        self.generate_data_type(element_type)?;
23496                        self.write(")");
23497                    }
23498                    Some(DialectType::TSQL)
23499                    | Some(DialectType::MySQL)
23500                    | Some(DialectType::Oracle) => {
23501                        // These dialects don't have native array types
23502                        // Fall back to JSON or use native workarounds
23503                        match self.config.dialect {
23504                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
23505                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
23506                            _ => self.write_keyword("JSON"),
23507                        }
23508                    }
23509                    _ => {
23510                        // Default: use angle bracket syntax (ARRAY<T>)
23511                        self.write_keyword("ARRAY<");
23512                        self.generate_data_type(element_type)?;
23513                        self.write(">");
23514                    }
23515                }
23516            }
23517            DataType::List { element_type } => {
23518                // Materialize: element_type LIST (postfix syntax)
23519                self.generate_data_type(element_type)?;
23520                self.write_keyword(" LIST");
23521            }
23522            DataType::Map {
23523                key_type,
23524                value_type,
23525            } => {
23526                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
23527                match self.config.dialect {
23528                    Some(DialectType::Materialize) => {
23529                        // Materialize: MAP[key_type => value_type]
23530                        self.write_keyword("MAP[");
23531                        self.generate_data_type(key_type)?;
23532                        self.write(" => ");
23533                        self.generate_data_type(value_type)?;
23534                        self.write("]");
23535                    }
23536                    Some(DialectType::Snowflake)
23537                    | Some(DialectType::RisingWave)
23538                    | Some(DialectType::DuckDB)
23539                    | Some(DialectType::Presto)
23540                    | Some(DialectType::Trino)
23541                    | Some(DialectType::Athena) => {
23542                        self.write_keyword("MAP(");
23543                        self.generate_data_type(key_type)?;
23544                        self.write(", ");
23545                        self.generate_data_type(value_type)?;
23546                        self.write(")");
23547                    }
23548                    Some(DialectType::ClickHouse) => {
23549                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
23550                        // Key types must NOT be wrapped in Nullable
23551                        self.write("Map(");
23552                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
23553                        self.generate_data_type(key_type)?;
23554                        self.clickhouse_nullable_depth = 0;
23555                        self.write(", ");
23556                        self.generate_data_type(value_type)?;
23557                        self.write(")");
23558                    }
23559                    _ => {
23560                        self.write_keyword("MAP<");
23561                        self.generate_data_type(key_type)?;
23562                        self.write(", ");
23563                        self.generate_data_type(value_type)?;
23564                        self.write(">");
23565                    }
23566                }
23567            }
23568            DataType::Vector {
23569                element_type,
23570                dimension,
23571            } => {
23572                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
23573                    // SingleStore format: VECTOR(dimension, type_alias)
23574                    self.write_keyword("VECTOR(");
23575                    if let Some(dim) = dimension {
23576                        self.write(&dim.to_string());
23577                    }
23578                    // Map type back to SingleStore alias
23579                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
23580                        DataType::TinyInt { .. } => Some("I8"),
23581                        DataType::SmallInt { .. } => Some("I16"),
23582                        DataType::Int { .. } => Some("I32"),
23583                        DataType::BigInt { .. } => Some("I64"),
23584                        DataType::Float { .. } => Some("F32"),
23585                        DataType::Double { .. } => Some("F64"),
23586                        _ => None,
23587                    });
23588                    if let Some(alias) = type_alias {
23589                        if dimension.is_some() {
23590                            self.write(", ");
23591                        }
23592                        self.write(alias);
23593                    }
23594                    self.write(")");
23595                } else {
23596                    // Snowflake format: VECTOR(type, dimension)
23597                    self.write_keyword("VECTOR(");
23598                    if let Some(ref et) = element_type {
23599                        self.generate_data_type(et)?;
23600                        if dimension.is_some() {
23601                            self.write(", ");
23602                        }
23603                    }
23604                    if let Some(dim) = dimension {
23605                        self.write(&dim.to_string());
23606                    }
23607                    self.write(")");
23608                }
23609            }
23610            DataType::Object { fields, modifier } => {
23611                self.write_keyword("OBJECT(");
23612                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
23613                    if i > 0 {
23614                        self.write(", ");
23615                    }
23616                    self.write(name);
23617                    self.write(" ");
23618                    self.generate_data_type(dt)?;
23619                    if *not_null {
23620                        self.write_keyword(" NOT NULL");
23621                    }
23622                }
23623                self.write(")");
23624                if let Some(mod_str) = modifier {
23625                    self.write(" ");
23626                    self.write_keyword(mod_str);
23627                }
23628            }
23629            DataType::Struct { fields, nested } => {
23630                // Dialect-specific struct type mappings
23631                match self.config.dialect {
23632                    Some(DialectType::Snowflake) => {
23633                        // Snowflake maps STRUCT to OBJECT
23634                        self.write_keyword("OBJECT(");
23635                        for (i, field) in fields.iter().enumerate() {
23636                            if i > 0 {
23637                                self.write(", ");
23638                            }
23639                            if !field.name.is_empty() {
23640                                self.write(&field.name);
23641                                self.write(" ");
23642                            }
23643                            self.generate_data_type(&field.data_type)?;
23644                        }
23645                        self.write(")");
23646                    }
23647                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
23648                        // Presto/Trino use ROW(name TYPE, ...) syntax
23649                        self.write_keyword("ROW(");
23650                        for (i, field) in fields.iter().enumerate() {
23651                            if i > 0 {
23652                                self.write(", ");
23653                            }
23654                            if !field.name.is_empty() {
23655                                self.write(&field.name);
23656                                self.write(" ");
23657                            }
23658                            self.generate_data_type(&field.data_type)?;
23659                        }
23660                        self.write(")");
23661                    }
23662                    Some(DialectType::DuckDB) => {
23663                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
23664                        self.write_keyword("STRUCT(");
23665                        for (i, field) in fields.iter().enumerate() {
23666                            if i > 0 {
23667                                self.write(", ");
23668                            }
23669                            if !field.name.is_empty() {
23670                                self.write(&field.name);
23671                                self.write(" ");
23672                            }
23673                            self.generate_data_type(&field.data_type)?;
23674                        }
23675                        self.write(")");
23676                    }
23677                    Some(DialectType::ClickHouse) => {
23678                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
23679                        self.write("Tuple(");
23680                        for (i, field) in fields.iter().enumerate() {
23681                            if i > 0 {
23682                                self.write(", ");
23683                            }
23684                            if !field.name.is_empty() {
23685                                self.write(&field.name);
23686                                self.write(" ");
23687                            }
23688                            self.generate_data_type(&field.data_type)?;
23689                        }
23690                        self.write(")");
23691                    }
23692                    Some(DialectType::SingleStore) => {
23693                        // SingleStore uses RECORD(name TYPE, ...) for struct types
23694                        self.write_keyword("RECORD(");
23695                        for (i, field) in fields.iter().enumerate() {
23696                            if i > 0 {
23697                                self.write(", ");
23698                            }
23699                            if !field.name.is_empty() {
23700                                self.write(&field.name);
23701                                self.write(" ");
23702                            }
23703                            self.generate_data_type(&field.data_type)?;
23704                        }
23705                        self.write(")");
23706                    }
23707                    _ => {
23708                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
23709                        let force_angle_brackets = matches!(
23710                            self.config.dialect,
23711                            Some(DialectType::Hive)
23712                                | Some(DialectType::Spark)
23713                                | Some(DialectType::Databricks)
23714                        );
23715                        if *nested && !force_angle_brackets {
23716                            self.write_keyword("STRUCT(");
23717                            for (i, field) in fields.iter().enumerate() {
23718                                if i > 0 {
23719                                    self.write(", ");
23720                                }
23721                                if !field.name.is_empty() {
23722                                    self.write(&field.name);
23723                                    self.write(" ");
23724                                }
23725                                self.generate_data_type(&field.data_type)?;
23726                            }
23727                            self.write(")");
23728                        } else {
23729                            self.write_keyword("STRUCT<");
23730                            for (i, field) in fields.iter().enumerate() {
23731                                if i > 0 {
23732                                    self.write(", ");
23733                                }
23734                                if !field.name.is_empty() {
23735                                    // Named field: name TYPE (with configurable separator for Hive)
23736                                    self.write(&field.name);
23737                                    self.write(self.config.struct_field_sep);
23738                                }
23739                                // For anonymous fields, just output the type
23740                                self.generate_data_type(&field.data_type)?;
23741                                // Spark/Databricks: Output COMMENT clause if present
23742                                if let Some(comment) = &field.comment {
23743                                    self.write(" COMMENT '");
23744                                    self.write(comment);
23745                                    self.write("'");
23746                                }
23747                                // BigQuery: Output OPTIONS clause if present
23748                                if !field.options.is_empty() {
23749                                    self.write(" ");
23750                                    self.generate_options_clause(&field.options)?;
23751                                }
23752                            }
23753                            self.write(">");
23754                        }
23755                    }
23756                }
23757            }
23758            DataType::Enum {
23759                values,
23760                assignments,
23761            } => {
23762                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
23763                // ClickHouse: Enum('hello' = 1, 'world' = 2)
23764                if self.config.dialect == Some(DialectType::ClickHouse) {
23765                    self.write("Enum(");
23766                } else {
23767                    self.write_keyword("ENUM(");
23768                }
23769                for (i, val) in values.iter().enumerate() {
23770                    if i > 0 {
23771                        self.write(", ");
23772                    }
23773                    self.write("'");
23774                    self.write(val);
23775                    self.write("'");
23776                    if let Some(Some(assignment)) = assignments.get(i) {
23777                        self.write(" = ");
23778                        self.write(assignment);
23779                    }
23780                }
23781                self.write(")");
23782            }
23783            DataType::Set { values } => {
23784                // MySQL SET type: SET('a', 'b', 'c')
23785                self.write_keyword("SET(");
23786                for (i, val) in values.iter().enumerate() {
23787                    if i > 0 {
23788                        self.write(", ");
23789                    }
23790                    self.write("'");
23791                    self.write(val);
23792                    self.write("'");
23793                }
23794                self.write(")");
23795            }
23796            DataType::Union { fields } => {
23797                // DuckDB UNION type: UNION(num INT, str TEXT)
23798                self.write_keyword("UNION(");
23799                for (i, (name, dt)) in fields.iter().enumerate() {
23800                    if i > 0 {
23801                        self.write(", ");
23802                    }
23803                    if !name.is_empty() {
23804                        self.write(name);
23805                        self.write(" ");
23806                    }
23807                    self.generate_data_type(dt)?;
23808                }
23809                self.write(")");
23810            }
23811            DataType::Nullable { inner } => {
23812                // ClickHouse: Nullable(T), other dialects: just the inner type
23813                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23814                    self.write("Nullable(");
23815                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
23816                    let saved_depth = self.clickhouse_nullable_depth;
23817                    self.clickhouse_nullable_depth = -1;
23818                    self.generate_data_type(inner)?;
23819                    self.clickhouse_nullable_depth = saved_depth;
23820                    self.write(")");
23821                } else {
23822                    // Map ClickHouse-specific custom type names to standard types
23823                    match inner.as_ref() {
23824                        DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME") => {
23825                            self.generate_data_type(&DataType::Timestamp {
23826                                precision: None,
23827                                timezone: false,
23828                            })?;
23829                        }
23830                        _ => {
23831                            self.generate_data_type(inner)?;
23832                        }
23833                    }
23834                }
23835            }
23836            DataType::Custom { name } => {
23837                // Handle dialect-specific type transformations
23838                let name_upper = name.to_ascii_uppercase();
23839                match self.config.dialect {
23840                    Some(DialectType::ClickHouse) => {
23841                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
23842                            (name_upper[..idx].to_string(), &name[idx..])
23843                        } else {
23844                            (name_upper.clone(), "")
23845                        };
23846                        let mapped = match base_upper.as_str() {
23847                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
23848                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
23849                            "DATETIME64" => "DateTime64",
23850                            "DATE32" => "Date32",
23851                            "INT" => "Int32",
23852                            "MEDIUMINT" => "Int32",
23853                            "INT8" => "Int8",
23854                            "INT16" => "Int16",
23855                            "INT32" => "Int32",
23856                            "INT64" => "Int64",
23857                            "INT128" => "Int128",
23858                            "INT256" => "Int256",
23859                            "UINT8" => "UInt8",
23860                            "UINT16" => "UInt16",
23861                            "UINT32" => "UInt32",
23862                            "UINT64" => "UInt64",
23863                            "UINT128" => "UInt128",
23864                            "UINT256" => "UInt256",
23865                            "FLOAT32" => "Float32",
23866                            "FLOAT64" => "Float64",
23867                            "DECIMAL32" => "Decimal32",
23868                            "DECIMAL64" => "Decimal64",
23869                            "DECIMAL128" => "Decimal128",
23870                            "DECIMAL256" => "Decimal256",
23871                            "ENUM" => "Enum",
23872                            "ENUM8" => "Enum8",
23873                            "ENUM16" => "Enum16",
23874                            "FIXEDSTRING" => "FixedString",
23875                            "NESTED" => "Nested",
23876                            "LOWCARDINALITY" => "LowCardinality",
23877                            "NULLABLE" => "Nullable",
23878                            "IPV4" => "IPv4",
23879                            "IPV6" => "IPv6",
23880                            "POINT" => "Point",
23881                            "RING" => "Ring",
23882                            "LINESTRING" => "LineString",
23883                            "MULTILINESTRING" => "MultiLineString",
23884                            "POLYGON" => "Polygon",
23885                            "MULTIPOLYGON" => "MultiPolygon",
23886                            "AGGREGATEFUNCTION" => "AggregateFunction",
23887                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
23888                            "DYNAMIC" => "Dynamic",
23889                            _ => "",
23890                        };
23891                        if mapped.is_empty() {
23892                            self.write(name);
23893                        } else {
23894                            self.write(mapped);
23895                            self.write(suffix);
23896                        }
23897                    }
23898                    Some(DialectType::MySQL)
23899                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
23900                    {
23901                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
23902                        self.write_keyword("TIMESTAMP");
23903                    }
23904                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
23905                        self.write_keyword("SQL_VARIANT");
23906                    }
23907                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
23908                        self.write_keyword("DECIMAL(38, 5)");
23909                    }
23910                    Some(DialectType::Exasol) => {
23911                        // Exasol type mappings for custom types
23912                        match name_upper.as_str() {
23913                            // Binary types → VARCHAR
23914                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
23915                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
23916                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
23917                            // Integer types
23918                            "MEDIUMINT" => self.write_keyword("INT"),
23919                            // Decimal types → DECIMAL
23920                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
23921                                self.write_keyword("DECIMAL")
23922                            }
23923                            // Timestamp types
23924                            "DATETIME" => self.write_keyword("TIMESTAMP"),
23925                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
23926                            _ => self.write(name),
23927                        }
23928                    }
23929                    Some(DialectType::Dremio) => {
23930                        // Dremio type mappings for custom types
23931                        match name_upper.as_str() {
23932                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
23933                            "ARRAY" => self.write_keyword("LIST"),
23934                            "NCHAR" => self.write_keyword("VARCHAR"),
23935                            _ => self.write(name),
23936                        }
23937                    }
23938                    // Map dialect-specific custom types to standard SQL types for other dialects
23939                    _ => {
23940                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
23941                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
23942                            (name_upper[..idx].to_string(), Some(&name[idx..]))
23943                        } else {
23944                            (name_upper.clone(), None)
23945                        };
23946
23947                        match base_upper.as_str() {
23948                            "INT64"
23949                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
23950                            {
23951                                self.write_keyword("BIGINT");
23952                            }
23953                            "FLOAT64"
23954                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
23955                            {
23956                                self.write_keyword("DOUBLE");
23957                            }
23958                            "BOOL"
23959                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
23960                            {
23961                                self.write_keyword("BOOLEAN");
23962                            }
23963                            "BYTES"
23964                                if matches!(
23965                                    self.config.dialect,
23966                                    Some(DialectType::Spark)
23967                                        | Some(DialectType::Hive)
23968                                        | Some(DialectType::Databricks)
23969                                ) =>
23970                            {
23971                                self.write_keyword("BINARY");
23972                            }
23973                            "BYTES"
23974                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
23975                            {
23976                                self.write_keyword("VARBINARY");
23977                            }
23978                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
23979                            "DATETIME2" | "SMALLDATETIME"
23980                                if !matches!(
23981                                    self.config.dialect,
23982                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23983                                ) =>
23984                            {
23985                                // PostgreSQL preserves precision, others don't
23986                                if matches!(
23987                                    self.config.dialect,
23988                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
23989                                ) {
23990                                    self.write_keyword("TIMESTAMP");
23991                                    if let Some(args) = _args_str {
23992                                        self.write(args);
23993                                    }
23994                                } else {
23995                                    self.write_keyword("TIMESTAMP");
23996                                }
23997                            }
23998                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
23999                            "DATETIMEOFFSET"
24000                                if !matches!(
24001                                    self.config.dialect,
24002                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24003                                ) =>
24004                            {
24005                                if matches!(
24006                                    self.config.dialect,
24007                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
24008                                ) {
24009                                    self.write_keyword("TIMESTAMPTZ");
24010                                    if let Some(args) = _args_str {
24011                                        self.write(args);
24012                                    }
24013                                } else {
24014                                    self.write_keyword("TIMESTAMPTZ");
24015                                }
24016                            }
24017                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
24018                            "UNIQUEIDENTIFIER"
24019                                if !matches!(
24020                                    self.config.dialect,
24021                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24022                                ) =>
24023                            {
24024                                match self.config.dialect {
24025                                    Some(DialectType::Spark)
24026                                    | Some(DialectType::Databricks)
24027                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
24028                                    _ => self.write_keyword("UUID"),
24029                                }
24030                            }
24031                            // TSQL BIT -> BOOLEAN for most dialects
24032                            "BIT"
24033                                if !matches!(
24034                                    self.config.dialect,
24035                                    Some(DialectType::TSQL)
24036                                        | Some(DialectType::Fabric)
24037                                        | Some(DialectType::PostgreSQL)
24038                                        | Some(DialectType::MySQL)
24039                                        | Some(DialectType::DuckDB)
24040                                ) =>
24041                            {
24042                                self.write_keyword("BOOLEAN");
24043                            }
24044                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
24045                            "NVARCHAR"
24046                                if !matches!(
24047                                    self.config.dialect,
24048                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24049                                ) =>
24050                            {
24051                                match self.config.dialect {
24052                                    Some(DialectType::Oracle) => {
24053                                        // Oracle: NVARCHAR -> NVARCHAR2
24054                                        self.write_keyword("NVARCHAR2");
24055                                        if let Some(args) = _args_str {
24056                                            self.write(args);
24057                                        }
24058                                    }
24059                                    Some(DialectType::BigQuery) => {
24060                                        // BigQuery: NVARCHAR -> STRING
24061                                        self.write_keyword("STRING");
24062                                    }
24063                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
24064                                        self.write_keyword("TEXT");
24065                                        if let Some(args) = _args_str {
24066                                            self.write(args);
24067                                        }
24068                                    }
24069                                    Some(DialectType::Hive) => {
24070                                        // Hive: NVARCHAR -> STRING
24071                                        self.write_keyword("STRING");
24072                                    }
24073                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
24074                                        if _args_str.is_some() {
24075                                            self.write_keyword("VARCHAR");
24076                                            self.write(_args_str.unwrap());
24077                                        } else {
24078                                            self.write_keyword("STRING");
24079                                        }
24080                                    }
24081                                    _ => {
24082                                        self.write_keyword("VARCHAR");
24083                                        if let Some(args) = _args_str {
24084                                            self.write(args);
24085                                        }
24086                                    }
24087                                }
24088                            }
24089                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
24090                            "NCHAR"
24091                                if !matches!(
24092                                    self.config.dialect,
24093                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24094                                ) =>
24095                            {
24096                                match self.config.dialect {
24097                                    Some(DialectType::Oracle) => {
24098                                        // Oracle natively supports NCHAR
24099                                        self.write_keyword("NCHAR");
24100                                        if let Some(args) = _args_str {
24101                                            self.write(args);
24102                                        }
24103                                    }
24104                                    Some(DialectType::BigQuery) => {
24105                                        // BigQuery: NCHAR -> STRING
24106                                        self.write_keyword("STRING");
24107                                    }
24108                                    Some(DialectType::Hive) => {
24109                                        // Hive: NCHAR -> STRING
24110                                        self.write_keyword("STRING");
24111                                    }
24112                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
24113                                        self.write_keyword("TEXT");
24114                                        if let Some(args) = _args_str {
24115                                            self.write(args);
24116                                        }
24117                                    }
24118                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
24119                                        if _args_str.is_some() {
24120                                            self.write_keyword("CHAR");
24121                                            self.write(_args_str.unwrap());
24122                                        } else {
24123                                            self.write_keyword("STRING");
24124                                        }
24125                                    }
24126                                    _ => {
24127                                        self.write_keyword("CHAR");
24128                                        if let Some(args) = _args_str {
24129                                            self.write(args);
24130                                        }
24131                                    }
24132                                }
24133                            }
24134                            // MySQL text variant types -> map to appropriate target type
24135                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
24136                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
24137                                Some(DialectType::MySQL)
24138                                | Some(DialectType::SingleStore)
24139                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
24140                                Some(DialectType::Spark)
24141                                | Some(DialectType::Databricks)
24142                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
24143                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
24144                                Some(DialectType::Presto)
24145                                | Some(DialectType::Trino)
24146                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
24147                                Some(DialectType::Snowflake)
24148                                | Some(DialectType::Redshift)
24149                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
24150                                _ => self.write_keyword("TEXT"),
24151                            },
24152                            // MySQL blob variant types -> map to appropriate target type
24153                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
24154                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
24155                                Some(DialectType::MySQL)
24156                                | Some(DialectType::SingleStore)
24157                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
24158                                Some(DialectType::Spark)
24159                                | Some(DialectType::Databricks)
24160                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
24161                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
24162                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
24163                                Some(DialectType::Presto)
24164                                | Some(DialectType::Trino)
24165                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
24166                                Some(DialectType::Snowflake)
24167                                | Some(DialectType::Redshift)
24168                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
24169                                _ => self.write_keyword("BLOB"),
24170                            },
24171                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
24172                            "LONGVARCHAR" => match self.config.dialect {
24173                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
24174                                _ => self.write_keyword("VARCHAR"),
24175                            },
24176                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
24177                            "DATETIME" => {
24178                                match self.config.dialect {
24179                                    Some(DialectType::MySQL)
24180                                    | Some(DialectType::Doris)
24181                                    | Some(DialectType::StarRocks)
24182                                    | Some(DialectType::TSQL)
24183                                    | Some(DialectType::Fabric)
24184                                    | Some(DialectType::BigQuery)
24185                                    | Some(DialectType::SQLite)
24186                                    | Some(DialectType::Snowflake) => {
24187                                        self.write_keyword("DATETIME");
24188                                        if let Some(args) = _args_str {
24189                                            self.write(args);
24190                                        }
24191                                    }
24192                                    Some(_) => {
24193                                        // Only map to TIMESTAMP when we have a specific target dialect
24194                                        self.write_keyword("TIMESTAMP");
24195                                        if let Some(args) = _args_str {
24196                                            self.write(args);
24197                                        }
24198                                    }
24199                                    None => {
24200                                        // No dialect - preserve original
24201                                        self.write(name);
24202                                    }
24203                                }
24204                            }
24205                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
24206                            "VARCHAR2"
24207                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
24208                            {
24209                                match self.config.dialect {
24210                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
24211                                        self.write_keyword("TEXT");
24212                                    }
24213                                    Some(DialectType::Hive)
24214                                    | Some(DialectType::Spark)
24215                                    | Some(DialectType::Databricks)
24216                                    | Some(DialectType::BigQuery)
24217                                    | Some(DialectType::ClickHouse)
24218                                    | Some(DialectType::StarRocks)
24219                                    | Some(DialectType::Doris) => {
24220                                        self.write_keyword("STRING");
24221                                    }
24222                                    _ => {
24223                                        self.write_keyword("VARCHAR");
24224                                        if let Some(args) = _args_str {
24225                                            self.write(args);
24226                                        }
24227                                    }
24228                                }
24229                            }
24230                            "NVARCHAR2"
24231                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
24232                            {
24233                                match self.config.dialect {
24234                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
24235                                        self.write_keyword("TEXT");
24236                                    }
24237                                    Some(DialectType::Hive)
24238                                    | Some(DialectType::Spark)
24239                                    | Some(DialectType::Databricks)
24240                                    | Some(DialectType::BigQuery)
24241                                    | Some(DialectType::ClickHouse)
24242                                    | Some(DialectType::StarRocks)
24243                                    | Some(DialectType::Doris) => {
24244                                        self.write_keyword("STRING");
24245                                    }
24246                                    _ => {
24247                                        self.write_keyword("VARCHAR");
24248                                        if let Some(args) = _args_str {
24249                                            self.write(args);
24250                                        }
24251                                    }
24252                                }
24253                            }
24254                            _ => self.write(name),
24255                        }
24256                    }
24257                }
24258            }
24259            DataType::Geometry { subtype, srid } => {
24260                // Dialect-specific geometry type mappings
24261                match self.config.dialect {
24262                    Some(DialectType::MySQL) => {
24263                        // MySQL uses POINT SRID 4326 syntax for specific types
24264                        if let Some(sub) = subtype {
24265                            self.write_keyword(sub);
24266                            if let Some(s) = srid {
24267                                self.write(" SRID ");
24268                                self.write(&s.to_string());
24269                            }
24270                        } else {
24271                            self.write_keyword("GEOMETRY");
24272                        }
24273                    }
24274                    Some(DialectType::BigQuery) => {
24275                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
24276                        self.write_keyword("GEOGRAPHY");
24277                    }
24278                    Some(DialectType::Teradata) => {
24279                        // Teradata uses ST_GEOMETRY
24280                        self.write_keyword("ST_GEOMETRY");
24281                        if subtype.is_some() || srid.is_some() {
24282                            self.write("(");
24283                            if let Some(sub) = subtype {
24284                                self.write_keyword(sub);
24285                            }
24286                            if let Some(s) = srid {
24287                                if subtype.is_some() {
24288                                    self.write(", ");
24289                                }
24290                                self.write(&s.to_string());
24291                            }
24292                            self.write(")");
24293                        }
24294                    }
24295                    _ => {
24296                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
24297                        self.write_keyword("GEOMETRY");
24298                        if subtype.is_some() || srid.is_some() {
24299                            self.write("(");
24300                            if let Some(sub) = subtype {
24301                                self.write_keyword(sub);
24302                            }
24303                            if let Some(s) = srid {
24304                                if subtype.is_some() {
24305                                    self.write(", ");
24306                                }
24307                                self.write(&s.to_string());
24308                            }
24309                            self.write(")");
24310                        }
24311                    }
24312                }
24313            }
24314            DataType::Geography { subtype, srid } => {
24315                // Dialect-specific geography type mappings
24316                match self.config.dialect {
24317                    Some(DialectType::MySQL) => {
24318                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
24319                        if let Some(sub) = subtype {
24320                            self.write_keyword(sub);
24321                        } else {
24322                            self.write_keyword("GEOMETRY");
24323                        }
24324                        // Geography implies SRID 4326 (WGS84)
24325                        let effective_srid = srid.unwrap_or(4326);
24326                        self.write(" SRID ");
24327                        self.write(&effective_srid.to_string());
24328                    }
24329                    Some(DialectType::BigQuery) => {
24330                        // BigQuery uses simple GEOGRAPHY without parameters
24331                        self.write_keyword("GEOGRAPHY");
24332                    }
24333                    Some(DialectType::Snowflake) => {
24334                        // Snowflake uses GEOGRAPHY without parameters
24335                        self.write_keyword("GEOGRAPHY");
24336                    }
24337                    _ => {
24338                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
24339                        self.write_keyword("GEOGRAPHY");
24340                        if subtype.is_some() || srid.is_some() {
24341                            self.write("(");
24342                            if let Some(sub) = subtype {
24343                                self.write_keyword(sub);
24344                            }
24345                            if let Some(s) = srid {
24346                                if subtype.is_some() {
24347                                    self.write(", ");
24348                                }
24349                                self.write(&s.to_string());
24350                            }
24351                            self.write(")");
24352                        }
24353                    }
24354                }
24355            }
24356            DataType::CharacterSet { name } => {
24357                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
24358                self.write_keyword("CHAR CHARACTER SET ");
24359                self.write(name);
24360            }
24361            _ => self.write("UNKNOWN"),
24362        }
24363        Ok(())
24364    }
24365
24366    // === Helper methods ===
24367
24368    #[inline]
24369    fn write(&mut self, s: &str) {
24370        self.output.push_str(s);
24371    }
24372
24373    #[inline]
24374    fn write_space(&mut self) {
24375        self.output.push(' ');
24376    }
24377
24378    #[inline]
24379    fn write_keyword(&mut self, keyword: &str) {
24380        if self.config.uppercase_keywords {
24381            self.output.push_str(keyword);
24382        } else {
24383            for b in keyword.bytes() {
24384                self.output.push(b.to_ascii_lowercase() as char);
24385            }
24386        }
24387    }
24388
24389    /// Write a function name respecting the normalize_functions config setting
24390    fn write_func_name(&mut self, name: &str) {
24391        let normalized = self.normalize_func_name(name);
24392        self.output.push_str(normalized.as_ref());
24393    }
24394
24395    /// Convert strptime format string to Exasol format string
24396    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
24397    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
24398    fn convert_strptime_to_exasol_format(format: &str) -> String {
24399        let mut result = String::new();
24400        let chars: Vec<char> = format.chars().collect();
24401        let mut i = 0;
24402        while i < chars.len() {
24403            if chars[i] == '%' && i + 1 < chars.len() {
24404                let spec = chars[i + 1];
24405                let exasol_spec = match spec {
24406                    'Y' => "YYYY",
24407                    'y' => "YY",
24408                    'm' => "MM",
24409                    'd' => "DD",
24410                    'H' => "HH",
24411                    'M' => "MI",
24412                    'S' => "SS",
24413                    'a' => "DY",    // abbreviated weekday name
24414                    'A' => "DAY",   // full weekday name
24415                    'b' => "MON",   // abbreviated month name
24416                    'B' => "MONTH", // full month name
24417                    'I' => "H12",   // 12-hour format
24418                    'u' => "ID",    // ISO weekday (1-7)
24419                    'V' => "IW",    // ISO week number
24420                    'G' => "IYYY",  // ISO year
24421                    'W' => "UW",    // Week number (Monday as first day)
24422                    'U' => "UW",    // Week number (Sunday as first day)
24423                    'z' => "Z",     // timezone offset
24424                    _ => {
24425                        // Unknown specifier, keep as-is
24426                        result.push('%');
24427                        result.push(spec);
24428                        i += 2;
24429                        continue;
24430                    }
24431                };
24432                result.push_str(exasol_spec);
24433                i += 2;
24434            } else {
24435                result.push(chars[i]);
24436                i += 1;
24437            }
24438        }
24439        result
24440    }
24441
24442    /// Convert strptime format string to PostgreSQL/Redshift format string
24443    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
24444    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
24445    fn convert_strptime_to_postgres_format(format: &str) -> String {
24446        let mut result = String::new();
24447        let chars: Vec<char> = format.chars().collect();
24448        let mut i = 0;
24449        while i < chars.len() {
24450            if chars[i] == '%' && i + 1 < chars.len() {
24451                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
24452                if chars[i + 1] == '-' && i + 2 < chars.len() {
24453                    let spec = chars[i + 2];
24454                    let pg_spec = match spec {
24455                        'd' => "FMDD",
24456                        'm' => "FMMM",
24457                        'H' => "FMHH24",
24458                        'M' => "FMMI",
24459                        'S' => "FMSS",
24460                        _ => {
24461                            result.push('%');
24462                            result.push('-');
24463                            result.push(spec);
24464                            i += 3;
24465                            continue;
24466                        }
24467                    };
24468                    result.push_str(pg_spec);
24469                    i += 3;
24470                    continue;
24471                }
24472                let spec = chars[i + 1];
24473                let pg_spec = match spec {
24474                    'Y' => "YYYY",
24475                    'y' => "YY",
24476                    'm' => "MM",
24477                    'd' => "DD",
24478                    'H' => "HH24",
24479                    'I' => "HH12",
24480                    'M' => "MI",
24481                    'S' => "SS",
24482                    'f' => "US",      // microseconds
24483                    'u' => "D",       // day of week (1=Monday)
24484                    'j' => "DDD",     // day of year
24485                    'z' => "OF",      // UTC offset
24486                    'Z' => "TZ",      // timezone name
24487                    'A' => "TMDay",   // full weekday name
24488                    'a' => "TMDy",    // abbreviated weekday name
24489                    'b' => "TMMon",   // abbreviated month name
24490                    'B' => "TMMonth", // full month name
24491                    'U' => "WW",      // week number
24492                    _ => {
24493                        // Unknown specifier, keep as-is
24494                        result.push('%');
24495                        result.push(spec);
24496                        i += 2;
24497                        continue;
24498                    }
24499                };
24500                result.push_str(pg_spec);
24501                i += 2;
24502            } else {
24503                result.push(chars[i]);
24504                i += 1;
24505            }
24506        }
24507        result
24508    }
24509
24510    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
24511    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
24512        if self.config.limit_only_literals {
24513            if let Some(value) = Self::try_evaluate_constant(expr) {
24514                self.write(&value.to_string());
24515                return Ok(());
24516            }
24517        }
24518        self.generate_expression(expr)
24519    }
24520
24521    /// Format a comment with proper spacing.
24522    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
24523    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
24524    fn write_formatted_comment(&mut self, comment: &str) {
24525        // Normalize all comments to block comment format /* ... */
24526        // This matches Python sqlglot behavior which always outputs block comments
24527        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
24528            // Already block comment - extract inner content
24529            // Preserve internal whitespace, but ensure at least one space padding
24530            &comment[2..comment.len() - 2]
24531        } else if comment.starts_with("--") {
24532            // Line comment - extract content after --
24533            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
24534            &comment[2..]
24535        } else {
24536            // Raw content (no delimiters)
24537            comment
24538        };
24539        // Skip empty comments (e.g., bare "--" with no content)
24540        if content.trim().is_empty() {
24541            return;
24542        }
24543        // Escape nested block comment markers to prevent premature closure or unintended nesting.
24544        // This matches Python sqlglot's sanitize_comment behavior.
24545        let sanitized = content.replace("*/", "* /").replace("/*", "/ *");
24546        let content = &sanitized;
24547        // Ensure at least one space after /* and before */
24548        self.output.push_str("/*");
24549        if !content.starts_with(' ') {
24550            self.output.push(' ');
24551        }
24552        self.output.push_str(content);
24553        if !content.ends_with(' ') {
24554            self.output.push(' ');
24555        }
24556        self.output.push_str("*/");
24557    }
24558
24559    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
24560    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
24561    fn escape_block_for_single_quote(&self, block: &str) -> String {
24562        let escape_backslash = matches!(
24563            self.config.dialect,
24564            Some(crate::dialects::DialectType::Snowflake)
24565        );
24566        let mut escaped = String::with_capacity(block.len() + 4);
24567        for ch in block.chars() {
24568            if ch == '\'' {
24569                escaped.push('\\');
24570                escaped.push('\'');
24571            } else if escape_backslash && ch == '\\' {
24572                escaped.push('\\');
24573                escaped.push('\\');
24574            } else {
24575                escaped.push(ch);
24576            }
24577        }
24578        escaped
24579    }
24580
24581    fn write_newline(&mut self) {
24582        self.output.push('\n');
24583    }
24584
24585    fn write_indent(&mut self) {
24586        for _ in 0..self.indent_level {
24587            self.output.push_str(self.config.indent);
24588        }
24589    }
24590
24591    // === SQLGlot-style pretty printing helpers ===
24592
24593    /// Returns the separator string for pretty printing.
24594    /// Check if the total length of arguments exceeds max_text_width.
24595    /// Used for dynamic line breaking in expressions() formatting.
24596    fn too_wide(&self, args: &[String]) -> bool {
24597        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
24598    }
24599
24600    /// Generate an expression to a string using a temporary non-pretty generator.
24601    /// Useful for width calculations before deciding on formatting.
24602    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
24603        let config = GeneratorConfig {
24604            pretty: false,
24605            dialect: self.config.dialect,
24606            ..Default::default()
24607        };
24608        let mut gen = Generator::with_config(config);
24609        gen.generate_expression(expr)?;
24610        Ok(gen.output)
24611    }
24612
24613    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
24614    /// In pretty mode: newline + indented keyword + newline + indented condition
24615    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
24616        if self.config.pretty {
24617            self.write_newline();
24618            self.write_indent();
24619            self.write_keyword(keyword);
24620            self.write_newline();
24621            self.indent_level += 1;
24622            self.write_indent();
24623            self.generate_expression(condition)?;
24624            self.indent_level -= 1;
24625        } else {
24626            self.write_space();
24627            self.write_keyword(keyword);
24628            self.write_space();
24629            self.generate_expression(condition)?;
24630        }
24631        Ok(())
24632    }
24633
24634    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
24635    /// In pretty mode: each expression on new line with indentation
24636    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
24637        if exprs.is_empty() {
24638            return Ok(());
24639        }
24640
24641        if self.config.pretty {
24642            self.write_newline();
24643            self.write_indent();
24644            self.write_keyword(keyword);
24645            self.write_newline();
24646            self.indent_level += 1;
24647            for (i, expr) in exprs.iter().enumerate() {
24648                if i > 0 {
24649                    self.write(",");
24650                    self.write_newline();
24651                }
24652                self.write_indent();
24653                self.generate_expression(expr)?;
24654            }
24655            self.indent_level -= 1;
24656        } else {
24657            self.write_space();
24658            self.write_keyword(keyword);
24659            self.write_space();
24660            for (i, expr) in exprs.iter().enumerate() {
24661                if i > 0 {
24662                    self.write(", ");
24663                }
24664                self.generate_expression(expr)?;
24665            }
24666        }
24667        Ok(())
24668    }
24669
24670    /// Writes ORDER BY / SORT BY clause with Ordered expressions
24671    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
24672        if orderings.is_empty() {
24673            return Ok(());
24674        }
24675
24676        if self.config.pretty {
24677            self.write_newline();
24678            self.write_indent();
24679            self.write_keyword(keyword);
24680            self.write_newline();
24681            self.indent_level += 1;
24682            for (i, ordered) in orderings.iter().enumerate() {
24683                if i > 0 {
24684                    self.write(",");
24685                    self.write_newline();
24686                }
24687                self.write_indent();
24688                self.generate_ordered(ordered)?;
24689            }
24690            self.indent_level -= 1;
24691        } else {
24692            self.write_space();
24693            self.write_keyword(keyword);
24694            self.write_space();
24695            for (i, ordered) in orderings.iter().enumerate() {
24696                if i > 0 {
24697                    self.write(", ");
24698                }
24699                self.generate_ordered(ordered)?;
24700            }
24701        }
24702        Ok(())
24703    }
24704
24705    /// Writes WINDOW clause with named window definitions
24706    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
24707        if windows.is_empty() {
24708            return Ok(());
24709        }
24710
24711        if self.config.pretty {
24712            self.write_newline();
24713            self.write_indent();
24714            self.write_keyword("WINDOW");
24715            self.write_newline();
24716            self.indent_level += 1;
24717            for (i, named_window) in windows.iter().enumerate() {
24718                if i > 0 {
24719                    self.write(",");
24720                    self.write_newline();
24721                }
24722                self.write_indent();
24723                self.generate_identifier(&named_window.name)?;
24724                self.write_space();
24725                self.write_keyword("AS");
24726                self.write(" (");
24727                self.generate_over(&named_window.spec)?;
24728                self.write(")");
24729            }
24730            self.indent_level -= 1;
24731        } else {
24732            self.write_space();
24733            self.write_keyword("WINDOW");
24734            self.write_space();
24735            for (i, named_window) in windows.iter().enumerate() {
24736                if i > 0 {
24737                    self.write(", ");
24738                }
24739                self.generate_identifier(&named_window.name)?;
24740                self.write_space();
24741                self.write_keyword("AS");
24742                self.write(" (");
24743                self.generate_over(&named_window.spec)?;
24744                self.write(")");
24745            }
24746        }
24747        Ok(())
24748    }
24749
24750    // === BATCH-GENERATED STUB METHODS (481 variants) ===
24751    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
24752        // AI_AGG(this, expression)
24753        self.write_keyword("AI_AGG");
24754        self.write("(");
24755        self.generate_expression(&e.this)?;
24756        self.write(", ");
24757        self.generate_expression(&e.expression)?;
24758        self.write(")");
24759        Ok(())
24760    }
24761
24762    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
24763        // AI_CLASSIFY(input, [categories], [config])
24764        self.write_keyword("AI_CLASSIFY");
24765        self.write("(");
24766        self.generate_expression(&e.this)?;
24767        if let Some(categories) = &e.categories {
24768            self.write(", ");
24769            self.generate_expression(categories)?;
24770        }
24771        if let Some(config) = &e.config {
24772            self.write(", ");
24773            self.generate_expression(config)?;
24774        }
24775        self.write(")");
24776        Ok(())
24777    }
24778
24779    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
24780        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
24781        self.write_keyword("ADD");
24782        self.write_space();
24783        if e.exists {
24784            self.write_keyword("IF NOT EXISTS");
24785            self.write_space();
24786        }
24787        self.generate_expression(&e.this)?;
24788        if let Some(location) = &e.location {
24789            self.write_space();
24790            self.generate_expression(location)?;
24791        }
24792        Ok(())
24793    }
24794
24795    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
24796        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
24797        self.write_keyword("ALGORITHM");
24798        self.write("=");
24799        self.generate_expression(&e.this)?;
24800        Ok(())
24801    }
24802
24803    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
24804        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
24805        self.generate_expression(&e.this)?;
24806        self.write_space();
24807        self.write_keyword("AS");
24808        self.write(" (");
24809        for (i, expr) in e.expressions.iter().enumerate() {
24810            if i > 0 {
24811                self.write(", ");
24812            }
24813            self.generate_expression(expr)?;
24814        }
24815        self.write(")");
24816        Ok(())
24817    }
24818
24819    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
24820        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
24821        self.write_keyword("ALLOWED_VALUES");
24822        self.write_space();
24823        for (i, expr) in e.expressions.iter().enumerate() {
24824            if i > 0 {
24825                self.write(", ");
24826            }
24827            self.generate_expression(expr)?;
24828        }
24829        Ok(())
24830    }
24831
24832    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
24833        // Python: complex logic based on dtype, default, comment, visible, etc.
24834        self.write_keyword("ALTER COLUMN");
24835        self.write_space();
24836        self.generate_expression(&e.this)?;
24837
24838        if let Some(dtype) = &e.dtype {
24839            self.write_space();
24840            self.write_keyword("SET DATA TYPE");
24841            self.write_space();
24842            self.generate_expression(dtype)?;
24843            if let Some(collate) = &e.collate {
24844                self.write_space();
24845                self.write_keyword("COLLATE");
24846                self.write_space();
24847                self.generate_expression(collate)?;
24848            }
24849            if let Some(using) = &e.using {
24850                self.write_space();
24851                self.write_keyword("USING");
24852                self.write_space();
24853                self.generate_expression(using)?;
24854            }
24855        } else if let Some(default) = &e.default {
24856            self.write_space();
24857            self.write_keyword("SET DEFAULT");
24858            self.write_space();
24859            self.generate_expression(default)?;
24860        } else if let Some(comment) = &e.comment {
24861            self.write_space();
24862            self.write_keyword("COMMENT");
24863            self.write_space();
24864            self.generate_expression(comment)?;
24865        } else if let Some(drop) = &e.drop {
24866            self.write_space();
24867            self.write_keyword("DROP");
24868            self.write_space();
24869            self.generate_expression(drop)?;
24870        } else if let Some(visible) = &e.visible {
24871            self.write_space();
24872            self.generate_expression(visible)?;
24873        } else if let Some(rename_to) = &e.rename_to {
24874            self.write_space();
24875            self.write_keyword("RENAME TO");
24876            self.write_space();
24877            self.generate_expression(rename_to)?;
24878        } else if let Some(allow_null) = &e.allow_null {
24879            self.write_space();
24880            self.generate_expression(allow_null)?;
24881        }
24882        Ok(())
24883    }
24884
24885    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
24886        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
24887        self.write_keyword("ALTER SESSION");
24888        self.write_space();
24889        if e.unset.is_some() {
24890            self.write_keyword("UNSET");
24891        } else {
24892            self.write_keyword("SET");
24893        }
24894        self.write_space();
24895        for (i, expr) in e.expressions.iter().enumerate() {
24896            if i > 0 {
24897                self.write(", ");
24898            }
24899            self.generate_expression(expr)?;
24900        }
24901        Ok(())
24902    }
24903
24904    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
24905        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
24906        self.write_keyword("SET");
24907
24908        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
24909        if let Some(opt) = &e.option {
24910            self.write_space();
24911            self.generate_expression(opt)?;
24912        }
24913
24914        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
24915        // Check if expressions look like property assignments
24916        if !e.expressions.is_empty() {
24917            // Check if this looks like property assignments (for SET PROPERTIES)
24918            let is_properties = e
24919                .expressions
24920                .iter()
24921                .any(|expr| matches!(expr, Expression::Eq(_)));
24922            if is_properties && e.option.is_none() {
24923                self.write_space();
24924                self.write_keyword("PROPERTIES");
24925            }
24926            self.write_space();
24927            for (i, expr) in e.expressions.iter().enumerate() {
24928                if i > 0 {
24929                    self.write(", ");
24930                }
24931                self.generate_expression(expr)?;
24932            }
24933        }
24934
24935        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
24936        if let Some(file_format) = &e.file_format {
24937            self.write(" ");
24938            self.write_keyword("STAGE_FILE_FORMAT");
24939            self.write(" = (");
24940            self.generate_space_separated_properties(file_format)?;
24941            self.write(")");
24942        }
24943
24944        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
24945        if let Some(copy_options) = &e.copy_options {
24946            self.write(" ");
24947            self.write_keyword("STAGE_COPY_OPTIONS");
24948            self.write(" = (");
24949            self.generate_space_separated_properties(copy_options)?;
24950            self.write(")");
24951        }
24952
24953        // Generate TAG ...
24954        if let Some(tag) = &e.tag {
24955            self.write(" ");
24956            self.write_keyword("TAG");
24957            self.write(" ");
24958            self.generate_expression(tag)?;
24959        }
24960
24961        Ok(())
24962    }
24963
24964    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
24965    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
24966        match expr {
24967            Expression::Tuple(t) => {
24968                for (i, prop) in t.expressions.iter().enumerate() {
24969                    if i > 0 {
24970                        self.write(" ");
24971                    }
24972                    self.generate_expression(prop)?;
24973                }
24974            }
24975            _ => {
24976                self.generate_expression(expr)?;
24977            }
24978        }
24979        Ok(())
24980    }
24981
24982    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
24983        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
24984        self.write_keyword("ALTER");
24985        if e.compound.is_some() {
24986            self.write_space();
24987            self.write_keyword("COMPOUND");
24988        }
24989        self.write_space();
24990        self.write_keyword("SORTKEY");
24991        self.write_space();
24992        if let Some(this) = &e.this {
24993            self.generate_expression(this)?;
24994        } else if !e.expressions.is_empty() {
24995            self.write("(");
24996            for (i, expr) in e.expressions.iter().enumerate() {
24997                if i > 0 {
24998                    self.write(", ");
24999                }
25000                self.generate_expression(expr)?;
25001            }
25002            self.write(")");
25003        }
25004        Ok(())
25005    }
25006
25007    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
25008        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
25009        self.write_keyword("ANALYZE");
25010        if !e.options.is_empty() {
25011            self.write_space();
25012            for (i, opt) in e.options.iter().enumerate() {
25013                if i > 0 {
25014                    self.write_space();
25015                }
25016                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
25017                if let Expression::Identifier(id) = opt {
25018                    self.write_keyword(&id.name);
25019                } else {
25020                    self.generate_expression(opt)?;
25021                }
25022            }
25023        }
25024        if let Some(kind) = &e.kind {
25025            self.write_space();
25026            self.write_keyword(kind);
25027        }
25028        if let Some(this) = &e.this {
25029            self.write_space();
25030            self.generate_expression(this)?;
25031        }
25032        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
25033        if !e.columns.is_empty() {
25034            self.write("(");
25035            for (i, col) in e.columns.iter().enumerate() {
25036                if i > 0 {
25037                    self.write(", ");
25038                }
25039                self.write(col);
25040            }
25041            self.write(")");
25042        }
25043        if let Some(partition) = &e.partition {
25044            self.write_space();
25045            self.generate_expression(partition)?;
25046        }
25047        if let Some(mode) = &e.mode {
25048            self.write_space();
25049            self.generate_expression(mode)?;
25050        }
25051        if let Some(expression) = &e.expression {
25052            self.write_space();
25053            self.generate_expression(expression)?;
25054        }
25055        if !e.properties.is_empty() {
25056            self.write_space();
25057            self.write_keyword(self.config.with_properties_prefix);
25058            self.write(" (");
25059            for (i, prop) in e.properties.iter().enumerate() {
25060                if i > 0 {
25061                    self.write(", ");
25062                }
25063                self.generate_expression(prop)?;
25064            }
25065            self.write(")");
25066        }
25067        Ok(())
25068    }
25069
25070    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
25071        // Python: return f"DELETE{kind} STATISTICS"
25072        self.write_keyword("DELETE");
25073        if let Some(kind) = &e.kind {
25074            self.write_space();
25075            self.write_keyword(kind);
25076        }
25077        self.write_space();
25078        self.write_keyword("STATISTICS");
25079        Ok(())
25080    }
25081
25082    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
25083        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
25084        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
25085        if let Expression::Identifier(id) = e.this.as_ref() {
25086            self.write_keyword(&id.name);
25087        } else {
25088            self.generate_expression(&e.this)?;
25089        }
25090        self.write_space();
25091        self.write_keyword("HISTOGRAM ON");
25092        self.write_space();
25093        for (i, expr) in e.expressions.iter().enumerate() {
25094            if i > 0 {
25095                self.write(", ");
25096            }
25097            self.generate_expression(expr)?;
25098        }
25099        if let Some(expression) = &e.expression {
25100            self.write_space();
25101            self.generate_expression(expression)?;
25102        }
25103        if let Some(update_options) = &e.update_options {
25104            self.write_space();
25105            self.generate_expression(update_options)?;
25106            self.write_space();
25107            self.write_keyword("UPDATE");
25108        }
25109        Ok(())
25110    }
25111
25112    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
25113        // Python: return f"LIST CHAINED ROWS{inner_expression}"
25114        self.write_keyword("LIST CHAINED ROWS");
25115        if let Some(expression) = &e.expression {
25116            self.write_space();
25117            self.write_keyword("INTO");
25118            self.write_space();
25119            self.generate_expression(expression)?;
25120        }
25121        Ok(())
25122    }
25123
25124    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
25125        // Python: return f"SAMPLE {sample} {kind}"
25126        self.write_keyword("SAMPLE");
25127        self.write_space();
25128        if let Some(sample) = &e.sample {
25129            self.generate_expression(sample)?;
25130            self.write_space();
25131        }
25132        self.write_keyword(&e.kind);
25133        Ok(())
25134    }
25135
25136    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
25137        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
25138        self.write_keyword(&e.kind);
25139        if let Some(option) = &e.option {
25140            self.write_space();
25141            self.generate_expression(option)?;
25142        }
25143        self.write_space();
25144        self.write_keyword("STATISTICS");
25145        if let Some(this) = &e.this {
25146            self.write_space();
25147            self.generate_expression(this)?;
25148        }
25149        if !e.expressions.is_empty() {
25150            self.write_space();
25151            for (i, expr) in e.expressions.iter().enumerate() {
25152                if i > 0 {
25153                    self.write(", ");
25154                }
25155                self.generate_expression(expr)?;
25156            }
25157        }
25158        Ok(())
25159    }
25160
25161    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
25162        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
25163        self.write_keyword("VALIDATE");
25164        self.write_space();
25165        self.write_keyword(&e.kind);
25166        if let Some(this) = &e.this {
25167            self.write_space();
25168            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
25169            if let Expression::Identifier(id) = this.as_ref() {
25170                self.write_keyword(&id.name);
25171            } else {
25172                self.generate_expression(this)?;
25173            }
25174        }
25175        if let Some(expression) = &e.expression {
25176            self.write_space();
25177            self.write_keyword("INTO");
25178            self.write_space();
25179            self.generate_expression(expression)?;
25180        }
25181        Ok(())
25182    }
25183
25184    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
25185        // Python: return f"WITH {expressions}"
25186        self.write_keyword("WITH");
25187        self.write_space();
25188        for (i, expr) in e.expressions.iter().enumerate() {
25189            if i > 0 {
25190                self.write(", ");
25191            }
25192            self.generate_expression(expr)?;
25193        }
25194        Ok(())
25195    }
25196
25197    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
25198        // Anonymous represents a generic function call: FUNC_NAME(args...)
25199        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
25200        self.generate_expression(&e.this)?;
25201        self.write("(");
25202        for (i, arg) in e.expressions.iter().enumerate() {
25203            if i > 0 {
25204                self.write(", ");
25205            }
25206            self.generate_expression(arg)?;
25207        }
25208        self.write(")");
25209        Ok(())
25210    }
25211
25212    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
25213        // Same as Anonymous but for aggregate functions
25214        self.generate_expression(&e.this)?;
25215        self.write("(");
25216        for (i, arg) in e.expressions.iter().enumerate() {
25217            if i > 0 {
25218                self.write(", ");
25219            }
25220            self.generate_expression(arg)?;
25221        }
25222        self.write(")");
25223        Ok(())
25224    }
25225
25226    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
25227        // Python: return f"{this} APPLY({expr})"
25228        self.generate_expression(&e.this)?;
25229        self.write_space();
25230        self.write_keyword("APPLY");
25231        self.write("(");
25232        self.generate_expression(&e.expression)?;
25233        self.write(")");
25234        Ok(())
25235    }
25236
25237    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
25238        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
25239        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
25240        self.write("(");
25241        self.generate_expression(&e.this)?;
25242        if let Some(percentile) = &e.percentile {
25243            self.write(", ");
25244            self.generate_expression(percentile)?;
25245        }
25246        self.write(")");
25247        Ok(())
25248    }
25249
25250    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
25251        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
25252        self.write_keyword("APPROX_QUANTILE");
25253        self.write("(");
25254        self.generate_expression(&e.this)?;
25255        if let Some(quantile) = &e.quantile {
25256            self.write(", ");
25257            self.generate_expression(quantile)?;
25258        }
25259        if let Some(accuracy) = &e.accuracy {
25260            self.write(", ");
25261            self.generate_expression(accuracy)?;
25262        }
25263        if let Some(weight) = &e.weight {
25264            self.write(", ");
25265            self.generate_expression(weight)?;
25266        }
25267        self.write(")");
25268        Ok(())
25269    }
25270
25271    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
25272        // APPROX_QUANTILES(this, expression)
25273        self.write_keyword("APPROX_QUANTILES");
25274        self.write("(");
25275        self.generate_expression(&e.this)?;
25276        if let Some(expression) = &e.expression {
25277            self.write(", ");
25278            self.generate_expression(expression)?;
25279        }
25280        self.write(")");
25281        Ok(())
25282    }
25283
25284    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
25285        // APPROX_TOP_K(this[, expression][, counters])
25286        self.write_keyword("APPROX_TOP_K");
25287        self.write("(");
25288        self.generate_expression(&e.this)?;
25289        if let Some(expression) = &e.expression {
25290            self.write(", ");
25291            self.generate_expression(expression)?;
25292        }
25293        if let Some(counters) = &e.counters {
25294            self.write(", ");
25295            self.generate_expression(counters)?;
25296        }
25297        self.write(")");
25298        Ok(())
25299    }
25300
25301    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
25302        // APPROX_TOP_K_ACCUMULATE(this[, expression])
25303        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
25304        self.write("(");
25305        self.generate_expression(&e.this)?;
25306        if let Some(expression) = &e.expression {
25307            self.write(", ");
25308            self.generate_expression(expression)?;
25309        }
25310        self.write(")");
25311        Ok(())
25312    }
25313
25314    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
25315        // APPROX_TOP_K_COMBINE(this[, expression])
25316        self.write_keyword("APPROX_TOP_K_COMBINE");
25317        self.write("(");
25318        self.generate_expression(&e.this)?;
25319        if let Some(expression) = &e.expression {
25320            self.write(", ");
25321            self.generate_expression(expression)?;
25322        }
25323        self.write(")");
25324        Ok(())
25325    }
25326
25327    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
25328        // APPROX_TOP_K_ESTIMATE(this[, expression])
25329        self.write_keyword("APPROX_TOP_K_ESTIMATE");
25330        self.write("(");
25331        self.generate_expression(&e.this)?;
25332        if let Some(expression) = &e.expression {
25333            self.write(", ");
25334            self.generate_expression(expression)?;
25335        }
25336        self.write(")");
25337        Ok(())
25338    }
25339
25340    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
25341        // APPROX_TOP_SUM(this, expression[, count])
25342        self.write_keyword("APPROX_TOP_SUM");
25343        self.write("(");
25344        self.generate_expression(&e.this)?;
25345        self.write(", ");
25346        self.generate_expression(&e.expression)?;
25347        if let Some(count) = &e.count {
25348            self.write(", ");
25349            self.generate_expression(count)?;
25350        }
25351        self.write(")");
25352        Ok(())
25353    }
25354
25355    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
25356        // ARG_MAX(this, expression[, count])
25357        self.write_keyword("ARG_MAX");
25358        self.write("(");
25359        self.generate_expression(&e.this)?;
25360        self.write(", ");
25361        self.generate_expression(&e.expression)?;
25362        if let Some(count) = &e.count {
25363            self.write(", ");
25364            self.generate_expression(count)?;
25365        }
25366        self.write(")");
25367        Ok(())
25368    }
25369
25370    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
25371        // ARG_MIN(this, expression[, count])
25372        self.write_keyword("ARG_MIN");
25373        self.write("(");
25374        self.generate_expression(&e.this)?;
25375        self.write(", ");
25376        self.generate_expression(&e.expression)?;
25377        if let Some(count) = &e.count {
25378            self.write(", ");
25379            self.generate_expression(count)?;
25380        }
25381        self.write(")");
25382        Ok(())
25383    }
25384
25385    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
25386        // ARRAY_ALL(this, expression)
25387        self.write_keyword("ARRAY_ALL");
25388        self.write("(");
25389        self.generate_expression(&e.this)?;
25390        self.write(", ");
25391        self.generate_expression(&e.expression)?;
25392        self.write(")");
25393        Ok(())
25394    }
25395
25396    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
25397        // ARRAY_ANY(this, expression) - fallback implementation
25398        self.write_keyword("ARRAY_ANY");
25399        self.write("(");
25400        self.generate_expression(&e.this)?;
25401        self.write(", ");
25402        self.generate_expression(&e.expression)?;
25403        self.write(")");
25404        Ok(())
25405    }
25406
25407    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
25408        // ARRAY_CONSTRUCT_COMPACT(expressions...)
25409        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
25410        self.write("(");
25411        for (i, expr) in e.expressions.iter().enumerate() {
25412            if i > 0 {
25413                self.write(", ");
25414            }
25415            self.generate_expression(expr)?;
25416        }
25417        self.write(")");
25418        Ok(())
25419    }
25420
25421    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
25422        // ARRAY_SUM(this[, expression])
25423        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
25424            self.write("arraySum");
25425        } else {
25426            self.write_keyword("ARRAY_SUM");
25427        }
25428        self.write("(");
25429        self.generate_expression(&e.this)?;
25430        if let Some(expression) = &e.expression {
25431            self.write(", ");
25432            self.generate_expression(expression)?;
25433        }
25434        self.write(")");
25435        Ok(())
25436    }
25437
25438    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
25439        // Python: return f"{this} AT {index}"
25440        self.generate_expression(&e.this)?;
25441        self.write_space();
25442        self.write_keyword("AT");
25443        self.write_space();
25444        self.generate_expression(&e.expression)?;
25445        Ok(())
25446    }
25447
25448    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
25449        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
25450        self.write_keyword("ATTACH");
25451        if e.exists {
25452            self.write_space();
25453            self.write_keyword("IF NOT EXISTS");
25454        }
25455        self.write_space();
25456        self.generate_expression(&e.this)?;
25457        if !e.expressions.is_empty() {
25458            self.write(" (");
25459            for (i, expr) in e.expressions.iter().enumerate() {
25460                if i > 0 {
25461                    self.write(", ");
25462                }
25463                self.generate_expression(expr)?;
25464            }
25465            self.write(")");
25466        }
25467        Ok(())
25468    }
25469
25470    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
25471        // AttachOption: this [expression]
25472        // Python sqlglot: no equals sign, just space-separated
25473        self.generate_expression(&e.this)?;
25474        if let Some(expression) = &e.expression {
25475            self.write_space();
25476            self.generate_expression(expression)?;
25477        }
25478        Ok(())
25479    }
25480
25481    /// Generate the auto_increment keyword and options for a column definition.
25482    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
25483    /// GENERATED AS IDENTITY, etc.
25484    fn generate_auto_increment_keyword(
25485        &mut self,
25486        col: &crate::expressions::ColumnDef,
25487    ) -> Result<()> {
25488        use crate::dialects::DialectType;
25489        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
25490            self.write_keyword("IDENTITY");
25491            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25492                self.write("(");
25493                if let Some(ref start) = col.auto_increment_start {
25494                    self.generate_expression(start)?;
25495                } else {
25496                    self.write("0");
25497                }
25498                self.write(", ");
25499                if let Some(ref inc) = col.auto_increment_increment {
25500                    self.generate_expression(inc)?;
25501                } else {
25502                    self.write("1");
25503                }
25504                self.write(")");
25505            }
25506        } else if matches!(
25507            self.config.dialect,
25508            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
25509        ) {
25510            self.write_keyword("AUTOINCREMENT");
25511            if let Some(ref start) = col.auto_increment_start {
25512                self.write_space();
25513                self.write_keyword("START");
25514                self.write_space();
25515                self.generate_expression(start)?;
25516            }
25517            if let Some(ref inc) = col.auto_increment_increment {
25518                self.write_space();
25519                self.write_keyword("INCREMENT");
25520                self.write_space();
25521                self.generate_expression(inc)?;
25522            }
25523            if let Some(order) = col.auto_increment_order {
25524                self.write_space();
25525                if order {
25526                    self.write_keyword("ORDER");
25527                } else {
25528                    self.write_keyword("NOORDER");
25529                }
25530            }
25531        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
25532            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
25533            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25534                self.write(" (");
25535                let mut first = true;
25536                if let Some(ref start) = col.auto_increment_start {
25537                    self.write_keyword("START WITH");
25538                    self.write_space();
25539                    self.generate_expression(start)?;
25540                    first = false;
25541                }
25542                if let Some(ref inc) = col.auto_increment_increment {
25543                    if !first {
25544                        self.write_space();
25545                    }
25546                    self.write_keyword("INCREMENT BY");
25547                    self.write_space();
25548                    self.generate_expression(inc)?;
25549                }
25550                self.write(")");
25551            }
25552        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
25553            // IDENTITY(start, increment) -> GENERATED BY DEFAULT AS IDENTITY
25554            // Plain IDENTITY/AUTO_INCREMENT -> GENERATED ALWAYS AS IDENTITY
25555            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25556                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
25557            } else {
25558                self.write_keyword("GENERATED ALWAYS AS IDENTITY");
25559            }
25560            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25561                self.write(" (");
25562                let mut first = true;
25563                if let Some(ref start) = col.auto_increment_start {
25564                    self.write_keyword("START WITH");
25565                    self.write_space();
25566                    self.generate_expression(start)?;
25567                    first = false;
25568                }
25569                if let Some(ref inc) = col.auto_increment_increment {
25570                    if !first {
25571                        self.write_space();
25572                    }
25573                    self.write_keyword("INCREMENT BY");
25574                    self.write_space();
25575                    self.generate_expression(inc)?;
25576                }
25577                self.write(")");
25578            }
25579        } else if matches!(
25580            self.config.dialect,
25581            Some(DialectType::TSQL) | Some(DialectType::Fabric)
25582        ) {
25583            self.write_keyword("IDENTITY");
25584            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25585                self.write("(");
25586                if let Some(ref start) = col.auto_increment_start {
25587                    self.generate_expression(start)?;
25588                } else {
25589                    self.write("0");
25590                }
25591                self.write(", ");
25592                if let Some(ref inc) = col.auto_increment_increment {
25593                    self.generate_expression(inc)?;
25594                } else {
25595                    self.write("1");
25596                }
25597                self.write(")");
25598            }
25599        } else {
25600            self.write_keyword("AUTO_INCREMENT");
25601            if let Some(ref start) = col.auto_increment_start {
25602                self.write_space();
25603                self.write_keyword("START");
25604                self.write_space();
25605                self.generate_expression(start)?;
25606            }
25607            if let Some(ref inc) = col.auto_increment_increment {
25608                self.write_space();
25609                self.write_keyword("INCREMENT");
25610                self.write_space();
25611                self.generate_expression(inc)?;
25612            }
25613            if let Some(order) = col.auto_increment_order {
25614                self.write_space();
25615                if order {
25616                    self.write_keyword("ORDER");
25617                } else {
25618                    self.write_keyword("NOORDER");
25619                }
25620            }
25621        }
25622        Ok(())
25623    }
25624
25625    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
25626        // AUTO_INCREMENT=value
25627        self.write_keyword("AUTO_INCREMENT");
25628        self.write("=");
25629        self.generate_expression(&e.this)?;
25630        Ok(())
25631    }
25632
25633    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
25634        // AUTO_REFRESH=value
25635        self.write_keyword("AUTO_REFRESH");
25636        self.write("=");
25637        self.generate_expression(&e.this)?;
25638        Ok(())
25639    }
25640
25641    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
25642        // BACKUP YES|NO (Redshift syntax uses space, not equals)
25643        self.write_keyword("BACKUP");
25644        self.write_space();
25645        self.generate_expression(&e.this)?;
25646        Ok(())
25647    }
25648
25649    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
25650        // BASE64_DECODE_BINARY(this[, alphabet])
25651        self.write_keyword("BASE64_DECODE_BINARY");
25652        self.write("(");
25653        self.generate_expression(&e.this)?;
25654        if let Some(alphabet) = &e.alphabet {
25655            self.write(", ");
25656            self.generate_expression(alphabet)?;
25657        }
25658        self.write(")");
25659        Ok(())
25660    }
25661
25662    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
25663        // BASE64_DECODE_STRING(this[, alphabet])
25664        self.write_keyword("BASE64_DECODE_STRING");
25665        self.write("(");
25666        self.generate_expression(&e.this)?;
25667        if let Some(alphabet) = &e.alphabet {
25668            self.write(", ");
25669            self.generate_expression(alphabet)?;
25670        }
25671        self.write(")");
25672        Ok(())
25673    }
25674
25675    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
25676        // BASE64_ENCODE(this[, max_line_length][, alphabet])
25677        self.write_keyword("BASE64_ENCODE");
25678        self.write("(");
25679        self.generate_expression(&e.this)?;
25680        if let Some(max_line_length) = &e.max_line_length {
25681            self.write(", ");
25682            self.generate_expression(max_line_length)?;
25683        }
25684        if let Some(alphabet) = &e.alphabet {
25685            self.write(", ");
25686            self.generate_expression(alphabet)?;
25687        }
25688        self.write(")");
25689        Ok(())
25690    }
25691
25692    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
25693        // BLOCKCOMPRESSION=... (complex Teradata property)
25694        self.write_keyword("BLOCKCOMPRESSION");
25695        self.write("=");
25696        if let Some(autotemp) = &e.autotemp {
25697            self.write_keyword("AUTOTEMP");
25698            self.write("(");
25699            self.generate_expression(autotemp)?;
25700            self.write(")");
25701        }
25702        if let Some(always) = &e.always {
25703            self.generate_expression(always)?;
25704        }
25705        if let Some(default) = &e.default {
25706            self.generate_expression(default)?;
25707        }
25708        if let Some(manual) = &e.manual {
25709            self.generate_expression(manual)?;
25710        }
25711        if let Some(never) = &e.never {
25712            self.generate_expression(never)?;
25713        }
25714        Ok(())
25715    }
25716
25717    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
25718        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
25719        self.write("((");
25720        self.generate_expression(&e.this)?;
25721        self.write(") ");
25722        self.write_keyword("AND");
25723        self.write(" (");
25724        self.generate_expression(&e.expression)?;
25725        self.write("))");
25726        Ok(())
25727    }
25728
25729    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
25730        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
25731        self.write("((");
25732        self.generate_expression(&e.this)?;
25733        self.write(") ");
25734        self.write_keyword("OR");
25735        self.write(" (");
25736        self.generate_expression(&e.expression)?;
25737        self.write("))");
25738        Ok(())
25739    }
25740
25741    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
25742        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
25743        self.write_keyword("BUILD");
25744        self.write_space();
25745        self.generate_expression(&e.this)?;
25746        Ok(())
25747    }
25748
25749    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
25750        // Byte string literal like B'...' or X'...'
25751        self.generate_expression(&e.this)?;
25752        Ok(())
25753    }
25754
25755    fn generate_case_specific_column_constraint(
25756        &mut self,
25757        e: &CaseSpecificColumnConstraint,
25758    ) -> Result<()> {
25759        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
25760        if e.not_.is_some() {
25761            self.write_keyword("NOT");
25762            self.write_space();
25763        }
25764        self.write_keyword("CASESPECIFIC");
25765        Ok(())
25766    }
25767
25768    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
25769        // Cast to string type (dialect-specific)
25770        self.write_keyword("CAST");
25771        self.write("(");
25772        self.generate_expression(&e.this)?;
25773        if self.config.dialect == Some(DialectType::ClickHouse) {
25774            // ClickHouse: CAST(expr, 'type_string')
25775            self.write(", ");
25776        } else {
25777            self.write_space();
25778            self.write_keyword("AS");
25779            self.write_space();
25780        }
25781        if let Some(to) = &e.to {
25782            self.generate_expression(to)?;
25783        }
25784        self.write(")");
25785        Ok(())
25786    }
25787
25788    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
25789        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
25790        // Python: f"CHANGES ({information}){at_before}{end}"
25791        self.write_keyword("CHANGES");
25792        self.write(" (");
25793        if let Some(information) = &e.information {
25794            self.write_keyword("INFORMATION");
25795            self.write(" => ");
25796            self.generate_expression(information)?;
25797        }
25798        self.write(")");
25799        // at_before and end are HistoricalData expressions that generate their own keywords
25800        if let Some(at_before) = &e.at_before {
25801            self.write(" ");
25802            self.generate_expression(at_before)?;
25803        }
25804        if let Some(end) = &e.end {
25805            self.write(" ");
25806            self.generate_expression(end)?;
25807        }
25808        Ok(())
25809    }
25810
25811    fn generate_character_set_column_constraint(
25812        &mut self,
25813        e: &CharacterSetColumnConstraint,
25814    ) -> Result<()> {
25815        // CHARACTER SET charset_name
25816        self.write_keyword("CHARACTER SET");
25817        self.write_space();
25818        self.generate_expression(&e.this)?;
25819        Ok(())
25820    }
25821
25822    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
25823        // [DEFAULT] CHARACTER SET=value
25824        if e.default.is_some() {
25825            self.write_keyword("DEFAULT");
25826            self.write_space();
25827        }
25828        self.write_keyword("CHARACTER SET");
25829        self.write("=");
25830        self.generate_expression(&e.this)?;
25831        Ok(())
25832    }
25833
25834    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
25835        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
25836        self.write_keyword("CHECK");
25837        self.write(" (");
25838        self.generate_expression(&e.this)?;
25839        self.write(")");
25840        if e.enforced.is_some() {
25841            self.write_space();
25842            self.write_keyword("ENFORCED");
25843        }
25844        Ok(())
25845    }
25846
25847    fn generate_assume_column_constraint(&mut self, e: &AssumeColumnConstraint) -> Result<()> {
25848        // Python: return f"ASSUME ({self.sql(e, 'this')})"
25849        self.write_keyword("ASSUME");
25850        self.write(" (");
25851        self.generate_expression(&e.this)?;
25852        self.write(")");
25853        Ok(())
25854    }
25855
25856    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
25857        // CHECK_JSON(this)
25858        self.write_keyword("CHECK_JSON");
25859        self.write("(");
25860        self.generate_expression(&e.this)?;
25861        self.write(")");
25862        Ok(())
25863    }
25864
25865    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
25866        // CHECK_XML(this)
25867        self.write_keyword("CHECK_XML");
25868        self.write("(");
25869        self.generate_expression(&e.this)?;
25870        self.write(")");
25871        Ok(())
25872    }
25873
25874    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
25875        // CHECKSUM=[ON|OFF|DEFAULT]
25876        self.write_keyword("CHECKSUM");
25877        self.write("=");
25878        if e.on.is_some() {
25879            self.write_keyword("ON");
25880        } else if e.default.is_some() {
25881            self.write_keyword("DEFAULT");
25882        } else {
25883            self.write_keyword("OFF");
25884        }
25885        Ok(())
25886    }
25887
25888    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
25889        // Python: return f"{shallow}{keyword} {this}"
25890        if e.shallow.is_some() {
25891            self.write_keyword("SHALLOW");
25892            self.write_space();
25893        }
25894        if e.copy.is_some() {
25895            self.write_keyword("COPY");
25896        } else {
25897            self.write_keyword("CLONE");
25898        }
25899        self.write_space();
25900        self.generate_expression(&e.this)?;
25901        Ok(())
25902    }
25903
25904    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
25905        // CLUSTER BY (expressions)
25906        self.write_keyword("CLUSTER BY");
25907        self.write(" (");
25908        for (i, ord) in e.expressions.iter().enumerate() {
25909            if i > 0 {
25910                self.write(", ");
25911            }
25912            self.generate_ordered(ord)?;
25913        }
25914        self.write(")");
25915        Ok(())
25916    }
25917
25918    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
25919        // BigQuery table property: CLUSTER BY col1, col2
25920        self.write_keyword("CLUSTER BY");
25921        self.write_space();
25922        for (i, col) in e.columns.iter().enumerate() {
25923            if i > 0 {
25924                self.write(", ");
25925            }
25926            self.generate_identifier(col)?;
25927        }
25928        Ok(())
25929    }
25930
25931    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
25932        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
25933        self.write_keyword("CLUSTERED BY");
25934        self.write(" (");
25935        for (i, expr) in e.expressions.iter().enumerate() {
25936            if i > 0 {
25937                self.write(", ");
25938            }
25939            self.generate_expression(expr)?;
25940        }
25941        self.write(")");
25942        if let Some(sorted_by) = &e.sorted_by {
25943            self.write_space();
25944            self.write_keyword("SORTED BY");
25945            self.write(" (");
25946            // Unwrap Tuple to avoid double parentheses
25947            if let Expression::Tuple(t) = sorted_by.as_ref() {
25948                for (i, expr) in t.expressions.iter().enumerate() {
25949                    if i > 0 {
25950                        self.write(", ");
25951                    }
25952                    self.generate_expression(expr)?;
25953                }
25954            } else {
25955                self.generate_expression(sorted_by)?;
25956            }
25957            self.write(")");
25958        }
25959        if let Some(buckets) = &e.buckets {
25960            self.write_space();
25961            self.write_keyword("INTO");
25962            self.write_space();
25963            self.generate_expression(buckets)?;
25964            self.write_space();
25965            self.write_keyword("BUCKETS");
25966        }
25967        Ok(())
25968    }
25969
25970    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
25971        // [DEFAULT] COLLATE [=] value
25972        // BigQuery uses space: DEFAULT COLLATE 'en'
25973        // Others use equals: COLLATE='en'
25974        if e.default.is_some() {
25975            self.write_keyword("DEFAULT");
25976            self.write_space();
25977        }
25978        self.write_keyword("COLLATE");
25979        // BigQuery uses space between COLLATE and value
25980        match self.config.dialect {
25981            Some(DialectType::BigQuery) => self.write_space(),
25982            _ => self.write("="),
25983        }
25984        self.generate_expression(&e.this)?;
25985        Ok(())
25986    }
25987
25988    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
25989        // ColumnConstraint is an enum
25990        match e {
25991            ColumnConstraint::NotNull => {
25992                self.write_keyword("NOT NULL");
25993            }
25994            ColumnConstraint::Null => {
25995                self.write_keyword("NULL");
25996            }
25997            ColumnConstraint::Unique => {
25998                self.write_keyword("UNIQUE");
25999            }
26000            ColumnConstraint::PrimaryKey => {
26001                self.write_keyword("PRIMARY KEY");
26002            }
26003            ColumnConstraint::Default(expr) => {
26004                self.write_keyword("DEFAULT");
26005                self.write_space();
26006                self.generate_expression(expr)?;
26007            }
26008            ColumnConstraint::Check(expr) => {
26009                self.write_keyword("CHECK");
26010                self.write(" (");
26011                self.generate_expression(expr)?;
26012                self.write(")");
26013            }
26014            ColumnConstraint::References(fk_ref) => {
26015                if fk_ref.has_foreign_key_keywords {
26016                    self.write_keyword("FOREIGN KEY");
26017                    self.write_space();
26018                }
26019                self.write_keyword("REFERENCES");
26020                self.write_space();
26021                self.generate_table(&fk_ref.table)?;
26022                if !fk_ref.columns.is_empty() {
26023                    self.write(" (");
26024                    for (i, col) in fk_ref.columns.iter().enumerate() {
26025                        if i > 0 {
26026                            self.write(", ");
26027                        }
26028                        self.generate_identifier(col)?;
26029                    }
26030                    self.write(")");
26031                }
26032            }
26033            ColumnConstraint::GeneratedAsIdentity(gen) => {
26034                self.write_keyword("GENERATED");
26035                self.write_space();
26036                if gen.always {
26037                    self.write_keyword("ALWAYS");
26038                } else {
26039                    self.write_keyword("BY DEFAULT");
26040                    if gen.on_null {
26041                        self.write_space();
26042                        self.write_keyword("ON NULL");
26043                    }
26044                }
26045                self.write_space();
26046                self.write_keyword("AS IDENTITY");
26047            }
26048            ColumnConstraint::Collate(collation) => {
26049                self.write_keyword("COLLATE");
26050                self.write_space();
26051                self.generate_identifier(collation)?;
26052            }
26053            ColumnConstraint::Comment(comment) => {
26054                self.write_keyword("COMMENT");
26055                self.write(" '");
26056                self.write(comment);
26057                self.write("'");
26058            }
26059            ColumnConstraint::ComputedColumn(cc) => {
26060                self.generate_computed_column_inline(cc)?;
26061            }
26062            ColumnConstraint::GeneratedAsRow(gar) => {
26063                self.generate_generated_as_row_inline(gar)?;
26064            }
26065            ColumnConstraint::Tags(tags) => {
26066                self.write_keyword("TAG");
26067                self.write(" (");
26068                for (i, expr) in tags.expressions.iter().enumerate() {
26069                    if i > 0 {
26070                        self.write(", ");
26071                    }
26072                    self.generate_expression(expr)?;
26073                }
26074                self.write(")");
26075            }
26076            ColumnConstraint::Path(path_expr) => {
26077                self.write_keyword("PATH");
26078                self.write_space();
26079                self.generate_expression(path_expr)?;
26080            }
26081        }
26082        Ok(())
26083    }
26084
26085    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
26086        // ColumnPosition is an enum
26087        match e {
26088            ColumnPosition::First => {
26089                self.write_keyword("FIRST");
26090            }
26091            ColumnPosition::After(ident) => {
26092                self.write_keyword("AFTER");
26093                self.write_space();
26094                self.generate_identifier(ident)?;
26095            }
26096        }
26097        Ok(())
26098    }
26099
26100    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
26101        // column(prefix)
26102        self.generate_expression(&e.this)?;
26103        self.write("(");
26104        self.generate_expression(&e.expression)?;
26105        self.write(")");
26106        Ok(())
26107    }
26108
26109    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
26110        // If unpack is true, this came from * COLUMNS(pattern)
26111        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
26112        if let Some(ref unpack) = e.unpack {
26113            if let Expression::Boolean(b) = unpack.as_ref() {
26114                if b.value {
26115                    self.write("*");
26116                }
26117            }
26118        }
26119        self.write_keyword("COLUMNS");
26120        self.write("(");
26121        self.generate_expression(&e.this)?;
26122        self.write(")");
26123        Ok(())
26124    }
26125
26126    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
26127        // Combined aggregate: FUNC(args) combined
26128        self.generate_expression(&e.this)?;
26129        self.write("(");
26130        for (i, expr) in e.expressions.iter().enumerate() {
26131            if i > 0 {
26132                self.write(", ");
26133            }
26134            self.generate_expression(expr)?;
26135        }
26136        self.write(")");
26137        Ok(())
26138    }
26139
26140    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
26141        // Combined parameterized aggregate: FUNC(params)(expressions)
26142        self.generate_expression(&e.this)?;
26143        self.write("(");
26144        for (i, param) in e.params.iter().enumerate() {
26145            if i > 0 {
26146                self.write(", ");
26147            }
26148            self.generate_expression(param)?;
26149        }
26150        self.write(")(");
26151        for (i, expr) in e.expressions.iter().enumerate() {
26152            if i > 0 {
26153                self.write(", ");
26154            }
26155            self.generate_expression(expr)?;
26156        }
26157        self.write(")");
26158        Ok(())
26159    }
26160
26161    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
26162        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
26163        self.write_keyword("COMMIT");
26164
26165        // TSQL always uses COMMIT TRANSACTION
26166        if e.this.is_none()
26167            && matches!(
26168                self.config.dialect,
26169                Some(DialectType::TSQL) | Some(DialectType::Fabric)
26170            )
26171        {
26172            self.write_space();
26173            self.write_keyword("TRANSACTION");
26174        }
26175
26176        // Check if this has TRANSACTION keyword or transaction name
26177        if let Some(this) = &e.this {
26178            // Check if it's just the "TRANSACTION" marker or an actual transaction name
26179            let is_transaction_marker = matches!(
26180                this.as_ref(),
26181                Expression::Identifier(id) if id.name == "TRANSACTION"
26182            );
26183
26184            self.write_space();
26185            self.write_keyword("TRANSACTION");
26186
26187            // If it's a real transaction name, output it
26188            if !is_transaction_marker {
26189                self.write_space();
26190                self.generate_expression(this)?;
26191            }
26192        }
26193
26194        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
26195        if let Some(durability) = &e.durability {
26196            self.write_space();
26197            self.write_keyword("WITH");
26198            self.write(" (");
26199            self.write_keyword("DELAYED_DURABILITY");
26200            self.write(" = ");
26201            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
26202                self.write_keyword("ON");
26203            } else {
26204                self.write_keyword("OFF");
26205            }
26206            self.write(")");
26207        }
26208
26209        // Output AND [NO] CHAIN
26210        if let Some(chain) = &e.chain {
26211            self.write_space();
26212            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
26213                self.write_keyword("AND NO CHAIN");
26214            } else {
26215                self.write_keyword("AND CHAIN");
26216            }
26217        }
26218        Ok(())
26219    }
26220
26221    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
26222        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
26223        self.write("[");
26224        self.generate_expression(&e.this)?;
26225        self.write_space();
26226        self.write_keyword("FOR");
26227        self.write_space();
26228        self.generate_expression(&e.expression)?;
26229        // Handle optional position variable (for enumerate-like syntax)
26230        if let Some(pos) = &e.position {
26231            self.write(", ");
26232            self.generate_expression(pos)?;
26233        }
26234        if let Some(iterator) = &e.iterator {
26235            self.write_space();
26236            self.write_keyword("IN");
26237            self.write_space();
26238            self.generate_expression(iterator)?;
26239        }
26240        if let Some(condition) = &e.condition {
26241            self.write_space();
26242            self.write_keyword("IF");
26243            self.write_space();
26244            self.generate_expression(condition)?;
26245        }
26246        self.write("]");
26247        Ok(())
26248    }
26249
26250    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
26251        // COMPRESS(this[, method])
26252        self.write_keyword("COMPRESS");
26253        self.write("(");
26254        self.generate_expression(&e.this)?;
26255        if let Some(method) = &e.method {
26256            self.write(", '");
26257            self.write(method);
26258            self.write("'");
26259        }
26260        self.write(")");
26261        Ok(())
26262    }
26263
26264    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
26265        // Python: return f"COMPRESS {this}"
26266        self.write_keyword("COMPRESS");
26267        if let Some(this) = &e.this {
26268            self.write_space();
26269            self.generate_expression(this)?;
26270        }
26271        Ok(())
26272    }
26273
26274    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
26275        // Python: return f"AS {this}{persisted}"
26276        self.write_keyword("AS");
26277        self.write_space();
26278        self.generate_expression(&e.this)?;
26279        if e.not_null.is_some() {
26280            self.write_space();
26281            self.write_keyword("PERSISTED NOT NULL");
26282        } else if e.persisted.is_some() {
26283            self.write_space();
26284            self.write_keyword("PERSISTED");
26285        }
26286        Ok(())
26287    }
26288
26289    /// Generate a ComputedColumn constraint inline within a column definition.
26290    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
26291    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
26292    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
26293        let computed_expr = if matches!(
26294            self.config.dialect,
26295            Some(DialectType::TSQL) | Some(DialectType::Fabric)
26296        ) {
26297            match &*cc.expression {
26298                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
26299                {
26300                    let wrapped = Expression::Cast(Box::new(Cast {
26301                        this: y.this.clone(),
26302                        to: DataType::Date,
26303                        trailing_comments: Vec::new(),
26304                        double_colon_syntax: false,
26305                        format: None,
26306                        default: None,
26307                        inferred_type: None,
26308                    }));
26309                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
26310                }
26311                Expression::Function(f)
26312                    if f.name.eq_ignore_ascii_case("YEAR")
26313                        && f.args.len() == 1
26314                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
26315                {
26316                    let wrapped = Expression::Cast(Box::new(Cast {
26317                        this: f.args[0].clone(),
26318                        to: DataType::Date,
26319                        trailing_comments: Vec::new(),
26320                        double_colon_syntax: false,
26321                        format: None,
26322                        default: None,
26323                        inferred_type: None,
26324                    }));
26325                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
26326                }
26327                _ => *cc.expression.clone(),
26328            }
26329        } else {
26330            *cc.expression.clone()
26331        };
26332
26333        match cc.persistence_kind.as_deref() {
26334            Some("STORED") | Some("VIRTUAL") => {
26335                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
26336                self.write_keyword("GENERATED ALWAYS AS");
26337                self.write(" (");
26338                self.generate_expression(&computed_expr)?;
26339                self.write(")");
26340                self.write_space();
26341                if cc.persisted {
26342                    self.write_keyword("STORED");
26343                } else {
26344                    self.write_keyword("VIRTUAL");
26345                }
26346            }
26347            Some("PERSISTED") => {
26348                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
26349                self.write_keyword("AS");
26350                self.write(" (");
26351                self.generate_expression(&computed_expr)?;
26352                self.write(")");
26353                self.write_space();
26354                self.write_keyword("PERSISTED");
26355                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
26356                if let Some(ref dt) = cc.data_type {
26357                    self.write_space();
26358                    self.generate_data_type(dt)?;
26359                }
26360                if cc.not_null {
26361                    self.write_space();
26362                    self.write_keyword("NOT NULL");
26363                }
26364            }
26365            _ => {
26366                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
26367                // TSQL computed column without PERSISTED: AS (expr)
26368                if matches!(
26369                    self.config.dialect,
26370                    Some(DialectType::Spark)
26371                        | Some(DialectType::Databricks)
26372                        | Some(DialectType::Hive)
26373                ) {
26374                    self.write_keyword("GENERATED ALWAYS AS");
26375                    self.write(" (");
26376                    self.generate_expression(&computed_expr)?;
26377                    self.write(")");
26378                } else if matches!(
26379                    self.config.dialect,
26380                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
26381                ) {
26382                    self.write_keyword("AS");
26383                    let omit_parens = matches!(computed_expr, Expression::Year(_))
26384                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
26385                    if omit_parens {
26386                        self.write_space();
26387                        self.generate_expression(&computed_expr)?;
26388                    } else {
26389                        self.write(" (");
26390                        self.generate_expression(&computed_expr)?;
26391                        self.write(")");
26392                    }
26393                } else {
26394                    self.write_keyword("AS");
26395                    self.write(" (");
26396                    self.generate_expression(&computed_expr)?;
26397                    self.write(")");
26398                }
26399            }
26400        }
26401        Ok(())
26402    }
26403
26404    /// Generate a GeneratedAsRow constraint inline within a column definition.
26405    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
26406    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
26407        self.write_keyword("GENERATED ALWAYS AS ROW ");
26408        if gar.start {
26409            self.write_keyword("START");
26410        } else {
26411            self.write_keyword("END");
26412        }
26413        if gar.hidden {
26414            self.write_space();
26415            self.write_keyword("HIDDEN");
26416        }
26417        Ok(())
26418    }
26419
26420    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
26421    fn generate_system_versioning_content(
26422        &mut self,
26423        e: &WithSystemVersioningProperty,
26424    ) -> Result<()> {
26425        let mut parts = Vec::new();
26426
26427        if let Some(this) = &e.this {
26428            let mut s = String::from("HISTORY_TABLE=");
26429            let mut gen = Generator::with_arc_config(self.config.clone());
26430            gen.generate_expression(this)?;
26431            s.push_str(&gen.output);
26432            parts.push(s);
26433        }
26434
26435        if let Some(data_consistency) = &e.data_consistency {
26436            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
26437            let mut gen = Generator::with_arc_config(self.config.clone());
26438            gen.generate_expression(data_consistency)?;
26439            s.push_str(&gen.output);
26440            parts.push(s);
26441        }
26442
26443        if let Some(retention_period) = &e.retention_period {
26444            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
26445            let mut gen = Generator::with_arc_config(self.config.clone());
26446            gen.generate_expression(retention_period)?;
26447            s.push_str(&gen.output);
26448            parts.push(s);
26449        }
26450
26451        self.write_keyword("SYSTEM_VERSIONING");
26452        self.write("=");
26453
26454        if !parts.is_empty() {
26455            self.write_keyword("ON");
26456            self.write("(");
26457            self.write(&parts.join(", "));
26458            self.write(")");
26459        } else if e.on.is_some() {
26460            self.write_keyword("ON");
26461        } else {
26462            self.write_keyword("OFF");
26463        }
26464
26465        Ok(())
26466    }
26467
26468    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
26469        // Conditional INSERT for multi-table inserts
26470        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
26471        if e.else_.is_some() {
26472            self.write_keyword("ELSE");
26473            self.write_space();
26474        } else if let Some(expression) = &e.expression {
26475            self.write_keyword("WHEN");
26476            self.write_space();
26477            self.generate_expression(expression)?;
26478            self.write_space();
26479            self.write_keyword("THEN");
26480            self.write_space();
26481        }
26482
26483        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
26484        // without the "INSERT " prefix
26485        if let Expression::Insert(insert) = e.this.as_ref() {
26486            self.write_keyword("INTO");
26487            self.write_space();
26488            self.generate_table(&insert.table)?;
26489
26490            // Optional column list
26491            if !insert.columns.is_empty() {
26492                self.write(" (");
26493                for (i, col) in insert.columns.iter().enumerate() {
26494                    if i > 0 {
26495                        self.write(", ");
26496                    }
26497                    self.generate_identifier(col)?;
26498                }
26499                self.write(")");
26500            }
26501
26502            // Optional VALUES clause
26503            if !insert.values.is_empty() {
26504                self.write_space();
26505                self.write_keyword("VALUES");
26506                for (row_idx, row) in insert.values.iter().enumerate() {
26507                    if row_idx > 0 {
26508                        self.write(", ");
26509                    }
26510                    self.write(" (");
26511                    for (i, val) in row.iter().enumerate() {
26512                        if i > 0 {
26513                            self.write(", ");
26514                        }
26515                        self.generate_expression(val)?;
26516                    }
26517                    self.write(")");
26518                }
26519            }
26520        } else {
26521            // Fallback for non-Insert expressions
26522            self.generate_expression(&e.this)?;
26523        }
26524        Ok(())
26525    }
26526
26527    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
26528        // Python: return f"CONSTRAINT {this} {expressions}"
26529        self.write_keyword("CONSTRAINT");
26530        self.write_space();
26531        self.generate_expression(&e.this)?;
26532        if !e.expressions.is_empty() {
26533            self.write_space();
26534            for (i, expr) in e.expressions.iter().enumerate() {
26535                if i > 0 {
26536                    self.write_space();
26537                }
26538                self.generate_expression(expr)?;
26539            }
26540        }
26541        Ok(())
26542    }
26543
26544    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
26545        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
26546        self.write_keyword("CONVERT_TIMEZONE");
26547        self.write("(");
26548        let mut first = true;
26549        if let Some(source_tz) = &e.source_tz {
26550            self.generate_expression(source_tz)?;
26551            first = false;
26552        }
26553        if let Some(target_tz) = &e.target_tz {
26554            if !first {
26555                self.write(", ");
26556            }
26557            self.generate_expression(target_tz)?;
26558            first = false;
26559        }
26560        if let Some(timestamp) = &e.timestamp {
26561            if !first {
26562                self.write(", ");
26563            }
26564            self.generate_expression(timestamp)?;
26565        }
26566        self.write(")");
26567        Ok(())
26568    }
26569
26570    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
26571        // CONVERT(this USING dest)
26572        self.write_keyword("CONVERT");
26573        self.write("(");
26574        self.generate_expression(&e.this)?;
26575        if let Some(dest) = &e.dest {
26576            self.write_space();
26577            self.write_keyword("USING");
26578            self.write_space();
26579            self.generate_expression(dest)?;
26580        }
26581        self.write(")");
26582        Ok(())
26583    }
26584
26585    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
26586        self.write_keyword("COPY");
26587        if e.is_into {
26588            self.write_space();
26589            self.write_keyword("INTO");
26590        }
26591        self.write_space();
26592
26593        // Generate target table or query (or stage for COPY INTO @stage)
26594        if let Expression::Literal(lit) = &e.this {
26595            if let Literal::String(s) = lit.as_ref() {
26596                if s.starts_with('@') {
26597                    self.write(s);
26598                } else {
26599                    self.generate_expression(&e.this)?;
26600                }
26601            }
26602        } else {
26603            self.generate_expression(&e.this)?;
26604        }
26605
26606        // FROM or TO based on kind
26607        if e.kind {
26608            // kind=true means FROM (loading into table)
26609            if self.config.pretty {
26610                self.write_newline();
26611            } else {
26612                self.write_space();
26613            }
26614            self.write_keyword("FROM");
26615            self.write_space();
26616        } else if !e.files.is_empty() {
26617            // kind=false means TO (exporting)
26618            if self.config.pretty {
26619                self.write_newline();
26620            } else {
26621                self.write_space();
26622            }
26623            self.write_keyword("TO");
26624            self.write_space();
26625        }
26626
26627        // Generate source/destination files
26628        for (i, file) in e.files.iter().enumerate() {
26629            if i > 0 {
26630                self.write_space();
26631            }
26632            // For stage references (strings starting with @), output without quotes
26633            if let Expression::Literal(lit) = file {
26634                if let Literal::String(s) = lit.as_ref() {
26635                    if s.starts_with('@') {
26636                        self.write(s);
26637                    } else {
26638                        self.generate_expression(file)?;
26639                    }
26640                }
26641            } else if let Expression::Identifier(id) = file {
26642                // Backtick-quoted file path (Databricks style: `s3://link`)
26643                if id.quoted {
26644                    self.write("`");
26645                    self.write(&id.name);
26646                    self.write("`");
26647                } else {
26648                    self.generate_expression(file)?;
26649                }
26650            } else {
26651                self.generate_expression(file)?;
26652            }
26653        }
26654
26655        // Generate credentials if present (Snowflake style - not wrapped in WITH)
26656        if !e.with_wrapped {
26657            if let Some(ref creds) = e.credentials {
26658                if let Some(ref storage) = creds.storage {
26659                    if self.config.pretty {
26660                        self.write_newline();
26661                    } else {
26662                        self.write_space();
26663                    }
26664                    self.write_keyword("STORAGE_INTEGRATION");
26665                    self.write(" = ");
26666                    self.write(storage);
26667                }
26668                if creds.credentials.is_empty() {
26669                    // Empty credentials: CREDENTIALS = ()
26670                    if self.config.pretty {
26671                        self.write_newline();
26672                    } else {
26673                        self.write_space();
26674                    }
26675                    self.write_keyword("CREDENTIALS");
26676                    self.write(" = ()");
26677                } else {
26678                    if self.config.pretty {
26679                        self.write_newline();
26680                    } else {
26681                        self.write_space();
26682                    }
26683                    self.write_keyword("CREDENTIALS");
26684                    // Check if this is Redshift-style (single value with empty key)
26685                    // vs Snowflake-style (multiple key=value pairs)
26686                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
26687                        // Redshift style: CREDENTIALS 'value'
26688                        self.write(" '");
26689                        self.write(&creds.credentials[0].1);
26690                        self.write("'");
26691                    } else {
26692                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
26693                        self.write(" = (");
26694                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
26695                            if i > 0 {
26696                                self.write_space();
26697                            }
26698                            self.write(k);
26699                            self.write("='");
26700                            self.write(v);
26701                            self.write("'");
26702                        }
26703                        self.write(")");
26704                    }
26705                }
26706                if let Some(ref encryption) = creds.encryption {
26707                    self.write_space();
26708                    self.write_keyword("ENCRYPTION");
26709                    self.write(" = ");
26710                    self.write(encryption);
26711                }
26712            }
26713        }
26714
26715        // Generate parameters
26716        if !e.params.is_empty() {
26717            if e.with_wrapped {
26718                // DuckDB/PostgreSQL/TSQL WITH (...) format
26719                self.write_space();
26720                self.write_keyword("WITH");
26721                self.write(" (");
26722                for (i, param) in e.params.iter().enumerate() {
26723                    if i > 0 {
26724                        self.write(", ");
26725                    }
26726                    self.generate_copy_param_with_format(param)?;
26727                }
26728                self.write(")");
26729            } else {
26730                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
26731                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
26732                // For Snowflake: KEY = VALUE
26733                for param in &e.params {
26734                    if self.config.pretty {
26735                        self.write_newline();
26736                    } else {
26737                        self.write_space();
26738                    }
26739                    // Preserve original case of parameter name (important for Redshift COPY options)
26740                    self.write(&param.name);
26741                    if let Some(ref value) = param.value {
26742                        // Use = only if it was present in the original (param.eq)
26743                        if param.eq {
26744                            self.write(" = ");
26745                        } else {
26746                            self.write(" ");
26747                        }
26748                        if !param.values.is_empty() {
26749                            self.write("(");
26750                            for (i, v) in param.values.iter().enumerate() {
26751                                if i > 0 {
26752                                    self.write_space();
26753                                }
26754                                self.generate_copy_nested_param(v)?;
26755                            }
26756                            self.write(")");
26757                        } else {
26758                            // For COPY parameter values, output identifiers without quoting
26759                            self.generate_copy_param_value(value)?;
26760                        }
26761                    } else if !param.values.is_empty() {
26762                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
26763                        if param.eq {
26764                            self.write(" = (");
26765                        } else {
26766                            self.write(" (");
26767                        }
26768                        // Determine separator for values inside parentheses:
26769                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
26770                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
26771                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
26772                        let is_key_value_pairs = param
26773                            .values
26774                            .first()
26775                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
26776                        let sep = if is_key_value_pairs && param.eq {
26777                            " "
26778                        } else {
26779                            ", "
26780                        };
26781                        for (i, v) in param.values.iter().enumerate() {
26782                            if i > 0 {
26783                                self.write(sep);
26784                            }
26785                            self.generate_copy_nested_param(v)?;
26786                        }
26787                        self.write(")");
26788                    }
26789                }
26790            }
26791        }
26792
26793        Ok(())
26794    }
26795
26796    /// Generate a COPY parameter in WITH (...) format
26797    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
26798    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
26799        self.write_keyword(&param.name);
26800        if !param.values.is_empty() {
26801            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
26802            self.write(" = (");
26803            for (i, v) in param.values.iter().enumerate() {
26804                if i > 0 {
26805                    self.write(", ");
26806                }
26807                self.generate_copy_nested_param(v)?;
26808            }
26809            self.write(")");
26810        } else if let Some(ref value) = param.value {
26811            if param.eq {
26812                self.write(" = ");
26813            } else {
26814                self.write(" ");
26815            }
26816            self.generate_expression(value)?;
26817        }
26818        Ok(())
26819    }
26820
26821    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
26822    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
26823        match expr {
26824            Expression::Eq(eq) => {
26825                // Generate key
26826                match &eq.left {
26827                    Expression::Column(c) => self.write(&c.name.name),
26828                    _ => self.generate_expression(&eq.left)?,
26829                }
26830                self.write("=");
26831                // Generate value
26832                match &eq.right {
26833                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
26834                        let Literal::String(s) = lit.as_ref() else {
26835                            unreachable!()
26836                        };
26837                        self.write("'");
26838                        self.write(s);
26839                        self.write("'");
26840                    }
26841                    Expression::Tuple(t) => {
26842                        // For lists like NULL_IF=('', 'str1')
26843                        self.write("(");
26844                        if self.config.pretty {
26845                            self.write_newline();
26846                            self.indent_level += 1;
26847                            for (i, item) in t.expressions.iter().enumerate() {
26848                                if i > 0 {
26849                                    self.write(", ");
26850                                }
26851                                self.write_indent();
26852                                self.generate_expression(item)?;
26853                            }
26854                            self.write_newline();
26855                            self.indent_level -= 1;
26856                        } else {
26857                            for (i, item) in t.expressions.iter().enumerate() {
26858                                if i > 0 {
26859                                    self.write(", ");
26860                                }
26861                                self.generate_expression(item)?;
26862                            }
26863                        }
26864                        self.write(")");
26865                    }
26866                    _ => self.generate_expression(&eq.right)?,
26867                }
26868                Ok(())
26869            }
26870            Expression::Column(c) => {
26871                // Standalone keyword like COMPRESSION
26872                self.write(&c.name.name);
26873                Ok(())
26874            }
26875            _ => self.generate_expression(expr),
26876        }
26877    }
26878
26879    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
26880    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
26881    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
26882        match expr {
26883            Expression::Column(c) => {
26884                // Output identifier, preserving quotes if originally quoted
26885                if c.name.quoted {
26886                    self.write("\"");
26887                    self.write(&c.name.name);
26888                    self.write("\"");
26889                } else {
26890                    self.write(&c.name.name);
26891                }
26892                Ok(())
26893            }
26894            Expression::Identifier(id) => {
26895                // Output identifier, preserving quotes if originally quoted
26896                if id.quoted {
26897                    self.write("\"");
26898                    self.write(&id.name);
26899                    self.write("\"");
26900                } else {
26901                    self.write(&id.name);
26902                }
26903                Ok(())
26904            }
26905            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
26906                let Literal::String(s) = lit.as_ref() else {
26907                    unreachable!()
26908                };
26909                // Output string with quotes
26910                self.write("'");
26911                self.write(s);
26912                self.write("'");
26913                Ok(())
26914            }
26915            _ => self.generate_expression(expr),
26916        }
26917    }
26918
26919    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
26920        self.write_keyword(&e.name);
26921        if let Some(ref value) = e.value {
26922            if e.eq {
26923                self.write(" = ");
26924            } else {
26925                self.write(" ");
26926            }
26927            self.generate_expression(value)?;
26928        }
26929        if !e.values.is_empty() {
26930            if e.eq {
26931                self.write(" = ");
26932            } else {
26933                self.write(" ");
26934            }
26935            self.write("(");
26936            for (i, v) in e.values.iter().enumerate() {
26937                if i > 0 {
26938                    self.write(", ");
26939                }
26940                self.generate_expression(v)?;
26941            }
26942            self.write(")");
26943        }
26944        Ok(())
26945    }
26946
26947    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
26948        // CORR(this, expression)
26949        self.write_keyword("CORR");
26950        self.write("(");
26951        self.generate_expression(&e.this)?;
26952        self.write(", ");
26953        self.generate_expression(&e.expression)?;
26954        self.write(")");
26955        Ok(())
26956    }
26957
26958    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
26959        // COSINE_DISTANCE(this, expression)
26960        self.write_keyword("COSINE_DISTANCE");
26961        self.write("(");
26962        self.generate_expression(&e.this)?;
26963        self.write(", ");
26964        self.generate_expression(&e.expression)?;
26965        self.write(")");
26966        Ok(())
26967    }
26968
26969    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
26970        // COVAR_POP(this, expression)
26971        self.write_keyword("COVAR_POP");
26972        self.write("(");
26973        self.generate_expression(&e.this)?;
26974        self.write(", ");
26975        self.generate_expression(&e.expression)?;
26976        self.write(")");
26977        Ok(())
26978    }
26979
26980    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
26981        // COVAR_SAMP(this, expression)
26982        self.write_keyword("COVAR_SAMP");
26983        self.write("(");
26984        self.generate_expression(&e.this)?;
26985        self.write(", ");
26986        self.generate_expression(&e.expression)?;
26987        self.write(")");
26988        Ok(())
26989    }
26990
26991    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
26992        // CREDENTIALS (key1='value1', key2='value2')
26993        self.write_keyword("CREDENTIALS");
26994        self.write(" (");
26995        for (i, (key, value)) in e.credentials.iter().enumerate() {
26996            if i > 0 {
26997                self.write(", ");
26998            }
26999            self.write(key);
27000            self.write("='");
27001            self.write(value);
27002            self.write("'");
27003        }
27004        self.write(")");
27005        Ok(())
27006    }
27007
27008    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
27009        // CREDENTIALS=(expressions)
27010        self.write_keyword("CREDENTIALS");
27011        self.write("=(");
27012        for (i, expr) in e.expressions.iter().enumerate() {
27013            if i > 0 {
27014                self.write(", ");
27015            }
27016            self.generate_expression(expr)?;
27017        }
27018        self.write(")");
27019        Ok(())
27020    }
27021
27022    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
27023        use crate::dialects::DialectType;
27024
27025        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
27026        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
27027        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
27028            self.generate_expression(&e.this)?;
27029            self.write_space();
27030            self.write_keyword("AS");
27031            self.write_space();
27032            self.generate_identifier(&e.alias)?;
27033            return Ok(());
27034        }
27035        self.write(&e.alias.name);
27036
27037        // BigQuery doesn't support column aliases in CTE definitions
27038        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
27039
27040        if !e.columns.is_empty() && !skip_cte_columns {
27041            self.write("(");
27042            for (i, col) in e.columns.iter().enumerate() {
27043                if i > 0 {
27044                    self.write(", ");
27045                }
27046                self.write(&col.name);
27047            }
27048            self.write(")");
27049        }
27050        // USING KEY (columns) for DuckDB recursive CTEs
27051        if !e.key_expressions.is_empty() {
27052            self.write_space();
27053            self.write_keyword("USING KEY");
27054            self.write(" (");
27055            for (i, key) in e.key_expressions.iter().enumerate() {
27056                if i > 0 {
27057                    self.write(", ");
27058                }
27059                self.write(&key.name);
27060            }
27061            self.write(")");
27062        }
27063        self.write_space();
27064        self.write_keyword("AS");
27065        self.write_space();
27066        if let Some(materialized) = e.materialized {
27067            if materialized {
27068                self.write_keyword("MATERIALIZED");
27069            } else {
27070                self.write_keyword("NOT MATERIALIZED");
27071            }
27072            self.write_space();
27073        }
27074        self.write("(");
27075        self.generate_expression(&e.this)?;
27076        self.write(")");
27077        Ok(())
27078    }
27079
27080    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
27081        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
27082        if e.expressions.is_empty() {
27083            self.write_keyword("WITH CUBE");
27084        } else {
27085            self.write_keyword("CUBE");
27086            self.write("(");
27087            for (i, expr) in e.expressions.iter().enumerate() {
27088                if i > 0 {
27089                    self.write(", ");
27090                }
27091                self.generate_expression(expr)?;
27092            }
27093            self.write(")");
27094        }
27095        Ok(())
27096    }
27097
27098    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
27099        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
27100        self.write_keyword("CURRENT_DATETIME");
27101        if let Some(this) = &e.this {
27102            self.write("(");
27103            self.generate_expression(this)?;
27104            self.write(")");
27105        }
27106        Ok(())
27107    }
27108
27109    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
27110        // CURRENT_SCHEMA - no arguments
27111        self.write_keyword("CURRENT_SCHEMA");
27112        Ok(())
27113    }
27114
27115    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
27116        // CURRENT_SCHEMAS(include_implicit)
27117        self.write_keyword("CURRENT_SCHEMAS");
27118        self.write("(");
27119        // Snowflake: drop the argument (CURRENT_SCHEMAS() takes no args)
27120        if !matches!(
27121            self.config.dialect,
27122            Some(crate::dialects::DialectType::Snowflake)
27123        ) {
27124            if let Some(this) = &e.this {
27125                self.generate_expression(this)?;
27126            }
27127        }
27128        self.write(")");
27129        Ok(())
27130    }
27131
27132    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
27133        // CURRENT_USER or CURRENT_USER()
27134        self.write_keyword("CURRENT_USER");
27135        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
27136        let needs_parens = e.this.is_some()
27137            || matches!(
27138                self.config.dialect,
27139                Some(DialectType::Snowflake)
27140                    | Some(DialectType::Spark)
27141                    | Some(DialectType::Hive)
27142                    | Some(DialectType::DuckDB)
27143                    | Some(DialectType::BigQuery)
27144                    | Some(DialectType::MySQL)
27145                    | Some(DialectType::Databricks)
27146            );
27147        if needs_parens {
27148            self.write("()");
27149        }
27150        Ok(())
27151    }
27152
27153    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
27154        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
27155        if self.config.dialect == Some(DialectType::Solr) {
27156            self.generate_expression(&e.this)?;
27157            self.write(" ");
27158            self.write_keyword("OR");
27159            self.write(" ");
27160            self.generate_expression(&e.expression)?;
27161        } else if self.config.dialect == Some(DialectType::MySQL) {
27162            self.generate_mysql_concat_from_dpipe(e)?;
27163        } else {
27164            // String concatenation: this || expression
27165            self.generate_expression(&e.this)?;
27166            self.write(" || ");
27167            self.generate_expression(&e.expression)?;
27168        }
27169        Ok(())
27170    }
27171
27172    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
27173        // DATABLOCKSIZE=... (Teradata)
27174        self.write_keyword("DATABLOCKSIZE");
27175        self.write("=");
27176        if let Some(size) = e.size {
27177            self.write(&size.to_string());
27178            if let Some(units) = &e.units {
27179                self.write_space();
27180                self.generate_expression(units)?;
27181            }
27182        } else if e.minimum.is_some() {
27183            self.write_keyword("MINIMUM");
27184        } else if e.maximum.is_some() {
27185            self.write_keyword("MAXIMUM");
27186        } else if e.default.is_some() {
27187            self.write_keyword("DEFAULT");
27188        }
27189        Ok(())
27190    }
27191
27192    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
27193        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
27194        self.write_keyword("DATA_DELETION");
27195        self.write("=");
27196
27197        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
27198        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
27199
27200        if is_on {
27201            self.write_keyword("ON");
27202            if has_options {
27203                self.write("(");
27204                let mut first = true;
27205                if let Some(filter_column) = &e.filter_column {
27206                    self.write_keyword("FILTER_COLUMN");
27207                    self.write("=");
27208                    self.generate_expression(filter_column)?;
27209                    first = false;
27210                }
27211                if let Some(retention_period) = &e.retention_period {
27212                    if !first {
27213                        self.write(", ");
27214                    }
27215                    self.write_keyword("RETENTION_PERIOD");
27216                    self.write("=");
27217                    self.generate_expression(retention_period)?;
27218                }
27219                self.write(")");
27220            }
27221        } else {
27222            self.write_keyword("OFF");
27223        }
27224        Ok(())
27225    }
27226
27227    /// Generate a Date function expression
27228    /// For Exasol: {d'value'} -> TO_DATE('value')
27229    /// For other dialects: DATE('value')
27230    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
27231        use crate::dialects::DialectType;
27232        use crate::expressions::Literal;
27233
27234        match self.config.dialect {
27235            // Exasol uses TO_DATE for Date expressions
27236            Some(DialectType::Exasol) => {
27237                self.write_keyword("TO_DATE");
27238                self.write("(");
27239                // Extract the string value from the expression if it's a string literal
27240                match &e.this {
27241                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
27242                        let Literal::String(s) = lit.as_ref() else {
27243                            unreachable!()
27244                        };
27245                        self.write("'");
27246                        self.write(s);
27247                        self.write("'");
27248                    }
27249                    _ => {
27250                        self.generate_expression(&e.this)?;
27251                    }
27252                }
27253                self.write(")");
27254            }
27255            // Standard: DATE(value)
27256            _ => {
27257                self.write_keyword("DATE");
27258                self.write("(");
27259                self.generate_expression(&e.this)?;
27260                self.write(")");
27261            }
27262        }
27263        Ok(())
27264    }
27265
27266    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
27267        // DATE_BIN(interval, timestamp[, origin])
27268        self.write_keyword("DATE_BIN");
27269        self.write("(");
27270        self.generate_expression(&e.this)?;
27271        self.write(", ");
27272        self.generate_expression(&e.expression)?;
27273        if let Some(origin) = &e.origin {
27274            self.write(", ");
27275            self.generate_expression(origin)?;
27276        }
27277        self.write(")");
27278        Ok(())
27279    }
27280
27281    fn generate_date_format_column_constraint(
27282        &mut self,
27283        e: &DateFormatColumnConstraint,
27284    ) -> Result<()> {
27285        // FORMAT 'format_string' (Teradata)
27286        self.write_keyword("FORMAT");
27287        self.write_space();
27288        self.generate_expression(&e.this)?;
27289        Ok(())
27290    }
27291
27292    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
27293        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
27294        self.write_keyword("DATE_FROM_PARTS");
27295        self.write("(");
27296        let mut first = true;
27297        if let Some(year) = &e.year {
27298            self.generate_expression(year)?;
27299            first = false;
27300        }
27301        if let Some(month) = &e.month {
27302            if !first {
27303                self.write(", ");
27304            }
27305            self.generate_expression(month)?;
27306            first = false;
27307        }
27308        if let Some(day) = &e.day {
27309            if !first {
27310                self.write(", ");
27311            }
27312            self.generate_expression(day)?;
27313        }
27314        self.write(")");
27315        Ok(())
27316    }
27317
27318    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
27319        // DATETIME(this) or DATETIME(this, expression)
27320        self.write_keyword("DATETIME");
27321        self.write("(");
27322        self.generate_expression(&e.this)?;
27323        if let Some(expr) = &e.expression {
27324            self.write(", ");
27325            self.generate_expression(expr)?;
27326        }
27327        self.write(")");
27328        Ok(())
27329    }
27330
27331    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
27332        // DATETIME_ADD(this, expression, unit)
27333        self.write_keyword("DATETIME_ADD");
27334        self.write("(");
27335        self.generate_expression(&e.this)?;
27336        self.write(", ");
27337        self.generate_expression(&e.expression)?;
27338        if let Some(unit) = &e.unit {
27339            self.write(", ");
27340            self.write_keyword(unit);
27341        }
27342        self.write(")");
27343        Ok(())
27344    }
27345
27346    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
27347        // DATETIME_DIFF(this, expression, unit)
27348        self.write_keyword("DATETIME_DIFF");
27349        self.write("(");
27350        self.generate_expression(&e.this)?;
27351        self.write(", ");
27352        self.generate_expression(&e.expression)?;
27353        if let Some(unit) = &e.unit {
27354            self.write(", ");
27355            self.write_keyword(unit);
27356        }
27357        self.write(")");
27358        Ok(())
27359    }
27360
27361    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
27362        // DATETIME_SUB(this, expression, unit)
27363        self.write_keyword("DATETIME_SUB");
27364        self.write("(");
27365        self.generate_expression(&e.this)?;
27366        self.write(", ");
27367        self.generate_expression(&e.expression)?;
27368        if let Some(unit) = &e.unit {
27369            self.write(", ");
27370            self.write_keyword(unit);
27371        }
27372        self.write(")");
27373        Ok(())
27374    }
27375
27376    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
27377        // DATETIME_TRUNC(this, unit, zone)
27378        self.write_keyword("DATETIME_TRUNC");
27379        self.write("(");
27380        self.generate_expression(&e.this)?;
27381        self.write(", ");
27382        self.write_keyword(&e.unit);
27383        if let Some(zone) = &e.zone {
27384            self.write(", ");
27385            self.generate_expression(zone)?;
27386        }
27387        self.write(")");
27388        Ok(())
27389    }
27390
27391    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
27392        // DAYNAME(this)
27393        self.write_keyword("DAYNAME");
27394        self.write("(");
27395        self.generate_expression(&e.this)?;
27396        self.write(")");
27397        Ok(())
27398    }
27399
27400    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
27401        // DECLARE [OR REPLACE] var1 AS type1, var2 AS type2, ...
27402        self.write_keyword("DECLARE");
27403        self.write_space();
27404        if e.replace {
27405            self.write_keyword("OR");
27406            self.write_space();
27407            self.write_keyword("REPLACE");
27408            self.write_space();
27409        }
27410        for (i, expr) in e.expressions.iter().enumerate() {
27411            if i > 0 {
27412                self.write(", ");
27413            }
27414            self.generate_expression(expr)?;
27415        }
27416        Ok(())
27417    }
27418
27419    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
27420        use crate::dialects::DialectType;
27421
27422        // variable TYPE [DEFAULT default]
27423        self.generate_expression(&e.this)?;
27424        // BigQuery multi-variable: DECLARE X, Y, Z INT64
27425        for name in &e.additional_names {
27426            self.write(", ");
27427            self.generate_expression(name)?;
27428        }
27429        if let Some(kind) = &e.kind {
27430            self.write_space();
27431            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
27432            // TSQL: Always includes AS (normalization)
27433            // Others: Include AS if present in original
27434            match self.config.dialect {
27435                Some(DialectType::BigQuery) => {
27436                    self.write(kind);
27437                }
27438                Some(DialectType::TSQL) => {
27439                    // TSQL DECLARE: no AS keyword (sqlglot convention)
27440                    // Normalize INT to INTEGER for simple declarations
27441                    // Complex TABLE declarations (with CLUSTERED/INDEX) are preserved as-is
27442                    let is_complex_table = kind.starts_with("TABLE")
27443                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
27444                    if is_complex_table {
27445                        self.write(kind);
27446                    } else if kind == "INT" {
27447                        self.write("INTEGER");
27448                    } else if kind.starts_with("TABLE") {
27449                        // Normalize INT to INTEGER inside simple TABLE column definitions
27450                        let normalized = kind
27451                            .replace(" INT ", " INTEGER ")
27452                            .replace(" INT,", " INTEGER,")
27453                            .replace(" INT)", " INTEGER)")
27454                            .replace("(INT ", "(INTEGER ");
27455                        self.write(&normalized);
27456                    } else {
27457                        self.write(kind);
27458                    }
27459                }
27460                _ => {
27461                    if e.has_as {
27462                        self.write_keyword("AS");
27463                        self.write_space();
27464                    }
27465                    self.write(kind);
27466                }
27467            }
27468        }
27469        if let Some(default) = &e.default {
27470            // BigQuery uses DEFAULT, others use =
27471            match self.config.dialect {
27472                Some(DialectType::BigQuery) => {
27473                    self.write_space();
27474                    self.write_keyword("DEFAULT");
27475                    self.write_space();
27476                }
27477                _ => {
27478                    self.write(" = ");
27479                }
27480            }
27481            self.generate_expression(default)?;
27482        }
27483        Ok(())
27484    }
27485
27486    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
27487        // DECODE(expr, search1, result1, search2, result2, ..., default)
27488        self.write_keyword("DECODE");
27489        self.write("(");
27490        for (i, expr) in e.expressions.iter().enumerate() {
27491            if i > 0 {
27492                self.write(", ");
27493            }
27494            self.generate_expression(expr)?;
27495        }
27496        self.write(")");
27497        Ok(())
27498    }
27499
27500    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
27501        // DECOMPRESS(expr, 'method')
27502        self.write_keyword("DECOMPRESS");
27503        self.write("(");
27504        self.generate_expression(&e.this)?;
27505        self.write(", '");
27506        self.write(&e.method);
27507        self.write("')");
27508        Ok(())
27509    }
27510
27511    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
27512        // DECOMPRESS(expr, 'method')
27513        self.write_keyword("DECOMPRESS");
27514        self.write("(");
27515        self.generate_expression(&e.this)?;
27516        self.write(", '");
27517        self.write(&e.method);
27518        self.write("')");
27519        Ok(())
27520    }
27521
27522    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
27523        // DECRYPT(value, passphrase [, aad [, algorithm]])
27524        self.write_keyword("DECRYPT");
27525        self.write("(");
27526        self.generate_expression(&e.this)?;
27527        if let Some(passphrase) = &e.passphrase {
27528            self.write(", ");
27529            self.generate_expression(passphrase)?;
27530        }
27531        if let Some(aad) = &e.aad {
27532            self.write(", ");
27533            self.generate_expression(aad)?;
27534        }
27535        if let Some(method) = &e.encryption_method {
27536            self.write(", ");
27537            self.generate_expression(method)?;
27538        }
27539        self.write(")");
27540        Ok(())
27541    }
27542
27543    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
27544        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
27545        self.write_keyword("DECRYPT_RAW");
27546        self.write("(");
27547        self.generate_expression(&e.this)?;
27548        if let Some(key) = &e.key {
27549            self.write(", ");
27550            self.generate_expression(key)?;
27551        }
27552        if let Some(iv) = &e.iv {
27553            self.write(", ");
27554            self.generate_expression(iv)?;
27555        }
27556        if let Some(aad) = &e.aad {
27557            self.write(", ");
27558            self.generate_expression(aad)?;
27559        }
27560        if let Some(method) = &e.encryption_method {
27561            self.write(", ");
27562            self.generate_expression(method)?;
27563        }
27564        self.write(")");
27565        Ok(())
27566    }
27567
27568    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
27569        // DEFINER = user
27570        self.write_keyword("DEFINER");
27571        self.write(" = ");
27572        self.generate_expression(&e.this)?;
27573        Ok(())
27574    }
27575
27576    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
27577        // Python: DETACH[DATABASE IF EXISTS] this
27578        self.write_keyword("DETACH");
27579        if e.exists {
27580            self.write_keyword(" DATABASE IF EXISTS");
27581        }
27582        self.write_space();
27583        self.generate_expression(&e.this)?;
27584        Ok(())
27585    }
27586
27587    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
27588        let property_name = match e.this.as_ref() {
27589            Expression::Identifier(id) => id.name.as_str(),
27590            Expression::Var(v) => v.this.as_str(),
27591            _ => "DICTIONARY",
27592        };
27593        self.write_keyword(property_name);
27594        self.write("(");
27595        self.write(&e.kind);
27596        if let Some(settings) = &e.settings {
27597            self.write("(");
27598            if let Expression::Tuple(t) = settings.as_ref() {
27599                if self.config.pretty && !t.expressions.is_empty() {
27600                    self.write_newline();
27601                    self.indent_level += 1;
27602                    for (i, pair) in t.expressions.iter().enumerate() {
27603                        if i > 0 {
27604                            self.write(",");
27605                            self.write_newline();
27606                        }
27607                        self.write_indent();
27608                        if let Expression::Tuple(pair_tuple) = pair {
27609                            if let Some(k) = pair_tuple.expressions.first() {
27610                                self.generate_expression(k)?;
27611                            }
27612                            if let Some(v) = pair_tuple.expressions.get(1) {
27613                                self.write(" ");
27614                                self.generate_expression(v)?;
27615                            }
27616                        } else {
27617                            self.generate_expression(pair)?;
27618                        }
27619                    }
27620                    self.indent_level -= 1;
27621                    self.write_newline();
27622                    self.write_indent();
27623                } else {
27624                    for (i, pair) in t.expressions.iter().enumerate() {
27625                        if i > 0 {
27626                            // ClickHouse dict properties are space-separated, not comma-separated
27627                            self.write(" ");
27628                        }
27629                        if let Expression::Tuple(pair_tuple) = pair {
27630                            if let Some(k) = pair_tuple.expressions.first() {
27631                                self.generate_expression(k)?;
27632                            }
27633                            if let Some(v) = pair_tuple.expressions.get(1) {
27634                                self.write(" ");
27635                                self.generate_expression(v)?;
27636                            }
27637                        } else {
27638                            self.generate_expression(pair)?;
27639                        }
27640                    }
27641                }
27642            } else {
27643                self.generate_expression(settings)?;
27644            }
27645            self.write(")");
27646        } else {
27647            // No settings but kind had parens (e.g., SOURCE(NULL()), LAYOUT(FLAT()))
27648            self.write("()");
27649        }
27650        self.write(")");
27651        Ok(())
27652    }
27653
27654    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
27655        let property_name = match e.this.as_ref() {
27656            Expression::Identifier(id) => id.name.as_str(),
27657            Expression::Var(v) => v.this.as_str(),
27658            _ => "RANGE",
27659        };
27660        self.write_keyword(property_name);
27661        self.write("(");
27662        if let Some(min) = &e.min {
27663            self.write_keyword("MIN");
27664            self.write_space();
27665            self.generate_expression(min)?;
27666        }
27667        if let Some(max) = &e.max {
27668            self.write_space();
27669            self.write_keyword("MAX");
27670            self.write_space();
27671            self.generate_expression(max)?;
27672        }
27673        self.write(")");
27674        Ok(())
27675    }
27676
27677    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
27678        // Python: {local}DIRECTORY {this}{row_format}
27679        if e.local.is_some() {
27680            self.write_keyword("LOCAL ");
27681        }
27682        self.write_keyword("DIRECTORY");
27683        self.write_space();
27684        self.generate_expression(&e.this)?;
27685        if let Some(row_format) = &e.row_format {
27686            self.write_space();
27687            self.generate_expression(row_format)?;
27688        }
27689        Ok(())
27690    }
27691
27692    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
27693        // Redshift: DISTKEY(column)
27694        self.write_keyword("DISTKEY");
27695        self.write("(");
27696        self.generate_expression(&e.this)?;
27697        self.write(")");
27698        Ok(())
27699    }
27700
27701    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
27702        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
27703        self.write_keyword("DISTSTYLE");
27704        self.write_space();
27705        self.generate_expression(&e.this)?;
27706        Ok(())
27707    }
27708
27709    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
27710        // Python: "DISTRIBUTE BY" expressions
27711        self.write_keyword("DISTRIBUTE BY");
27712        self.write_space();
27713        for (i, expr) in e.expressions.iter().enumerate() {
27714            if i > 0 {
27715                self.write(", ");
27716            }
27717            self.generate_expression(expr)?;
27718        }
27719        Ok(())
27720    }
27721
27722    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
27723        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
27724        self.write_keyword("DISTRIBUTED BY");
27725        self.write_space();
27726        self.write(&e.kind);
27727        if !e.expressions.is_empty() {
27728            self.write(" (");
27729            for (i, expr) in e.expressions.iter().enumerate() {
27730                if i > 0 {
27731                    self.write(", ");
27732                }
27733                self.generate_expression(expr)?;
27734            }
27735            self.write(")");
27736        }
27737        if let Some(buckets) = &e.buckets {
27738            self.write_space();
27739            self.write_keyword("BUCKETS");
27740            self.write_space();
27741            self.generate_expression(buckets)?;
27742        }
27743        if let Some(order) = &e.order {
27744            self.write_space();
27745            self.generate_expression(order)?;
27746        }
27747        Ok(())
27748    }
27749
27750    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
27751        // DOT_PRODUCT(vector1, vector2)
27752        self.write_keyword("DOT_PRODUCT");
27753        self.write("(");
27754        self.generate_expression(&e.this)?;
27755        self.write(", ");
27756        self.generate_expression(&e.expression)?;
27757        self.write(")");
27758        Ok(())
27759    }
27760
27761    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
27762        // Python: DROP{IF EXISTS }expressions
27763        self.write_keyword("DROP");
27764        if e.exists {
27765            self.write_keyword(" IF EXISTS ");
27766        } else {
27767            self.write_space();
27768        }
27769        for (i, expr) in e.expressions.iter().enumerate() {
27770            if i > 0 {
27771                self.write(", ");
27772            }
27773            self.generate_expression(expr)?;
27774        }
27775        Ok(())
27776    }
27777
27778    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
27779        // Python: DUPLICATE KEY (expressions)
27780        self.write_keyword("DUPLICATE KEY");
27781        self.write(" (");
27782        for (i, expr) in e.expressions.iter().enumerate() {
27783            if i > 0 {
27784                self.write(", ");
27785            }
27786            self.generate_expression(expr)?;
27787        }
27788        self.write(")");
27789        Ok(())
27790    }
27791
27792    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
27793        // ELT(index, str1, str2, ...)
27794        self.write_keyword("ELT");
27795        self.write("(");
27796        self.generate_expression(&e.this)?;
27797        for expr in &e.expressions {
27798            self.write(", ");
27799            self.generate_expression(expr)?;
27800        }
27801        self.write(")");
27802        Ok(())
27803    }
27804
27805    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
27806        // ENCODE(string, charset)
27807        self.write_keyword("ENCODE");
27808        self.write("(");
27809        self.generate_expression(&e.this)?;
27810        if let Some(charset) = &e.charset {
27811            self.write(", ");
27812            self.generate_expression(charset)?;
27813        }
27814        self.write(")");
27815        Ok(())
27816    }
27817
27818    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
27819        // Python: [KEY ]ENCODE this [properties]
27820        if e.key.is_some() {
27821            self.write_keyword("KEY ");
27822        }
27823        self.write_keyword("ENCODE");
27824        self.write_space();
27825        self.generate_expression(&e.this)?;
27826        if !e.properties.is_empty() {
27827            self.write(" (");
27828            for (i, prop) in e.properties.iter().enumerate() {
27829                if i > 0 {
27830                    self.write(", ");
27831                }
27832                self.generate_expression(prop)?;
27833            }
27834            self.write(")");
27835        }
27836        Ok(())
27837    }
27838
27839    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
27840        // ENCRYPT(value, passphrase [, aad [, algorithm]])
27841        self.write_keyword("ENCRYPT");
27842        self.write("(");
27843        self.generate_expression(&e.this)?;
27844        if let Some(passphrase) = &e.passphrase {
27845            self.write(", ");
27846            self.generate_expression(passphrase)?;
27847        }
27848        if let Some(aad) = &e.aad {
27849            self.write(", ");
27850            self.generate_expression(aad)?;
27851        }
27852        if let Some(method) = &e.encryption_method {
27853            self.write(", ");
27854            self.generate_expression(method)?;
27855        }
27856        self.write(")");
27857        Ok(())
27858    }
27859
27860    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
27861        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
27862        self.write_keyword("ENCRYPT_RAW");
27863        self.write("(");
27864        self.generate_expression(&e.this)?;
27865        if let Some(key) = &e.key {
27866            self.write(", ");
27867            self.generate_expression(key)?;
27868        }
27869        if let Some(iv) = &e.iv {
27870            self.write(", ");
27871            self.generate_expression(iv)?;
27872        }
27873        if let Some(aad) = &e.aad {
27874            self.write(", ");
27875            self.generate_expression(aad)?;
27876        }
27877        if let Some(method) = &e.encryption_method {
27878            self.write(", ");
27879            self.generate_expression(method)?;
27880        }
27881        self.write(")");
27882        Ok(())
27883    }
27884
27885    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
27886        // MySQL: ENGINE = InnoDB
27887        self.write_keyword("ENGINE");
27888        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
27889            self.write("=");
27890        } else {
27891            self.write(" = ");
27892        }
27893        self.generate_expression(&e.this)?;
27894        Ok(())
27895    }
27896
27897    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
27898        // ENVIRONMENT (expressions)
27899        self.write_keyword("ENVIRONMENT");
27900        self.write(" (");
27901        for (i, expr) in e.expressions.iter().enumerate() {
27902            if i > 0 {
27903                self.write(", ");
27904            }
27905            self.generate_expression(expr)?;
27906        }
27907        self.write(")");
27908        Ok(())
27909    }
27910
27911    fn generate_ephemeral_column_constraint(
27912        &mut self,
27913        e: &EphemeralColumnConstraint,
27914    ) -> Result<()> {
27915        // MySQL: EPHEMERAL [expr]
27916        self.write_keyword("EPHEMERAL");
27917        if let Some(this) = &e.this {
27918            self.write_space();
27919            self.generate_expression(this)?;
27920        }
27921        Ok(())
27922    }
27923
27924    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
27925        // Snowflake: EQUAL_NULL(a, b)
27926        self.write_keyword("EQUAL_NULL");
27927        self.write("(");
27928        self.generate_expression(&e.this)?;
27929        self.write(", ");
27930        self.generate_expression(&e.expression)?;
27931        self.write(")");
27932        Ok(())
27933    }
27934
27935    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
27936        use crate::dialects::DialectType;
27937
27938        // PostgreSQL uses <-> operator syntax
27939        match self.config.dialect {
27940            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
27941                self.generate_expression(&e.this)?;
27942                self.write(" <-> ");
27943                self.generate_expression(&e.expression)?;
27944            }
27945            _ => {
27946                // Other dialects use EUCLIDEAN_DISTANCE function
27947                self.write_keyword("EUCLIDEAN_DISTANCE");
27948                self.write("(");
27949                self.generate_expression(&e.this)?;
27950                self.write(", ");
27951                self.generate_expression(&e.expression)?;
27952                self.write(")");
27953            }
27954        }
27955        Ok(())
27956    }
27957
27958    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
27959        // EXECUTE AS CALLER|OWNER|user
27960        self.write_keyword("EXECUTE AS");
27961        self.write_space();
27962        self.generate_expression(&e.this)?;
27963        Ok(())
27964    }
27965
27966    fn generate_export(&mut self, e: &Export) -> Result<()> {
27967        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
27968        self.write_keyword("EXPORT DATA");
27969        if let Some(connection) = &e.connection {
27970            self.write_space();
27971            self.write_keyword("WITH CONNECTION");
27972            self.write_space();
27973            self.generate_expression(connection)?;
27974        }
27975        if !e.options.is_empty() {
27976            self.write_space();
27977            self.generate_options_clause(&e.options)?;
27978        }
27979        self.write_space();
27980        self.write_keyword("AS");
27981        self.write_space();
27982        self.generate_expression(&e.this)?;
27983        Ok(())
27984    }
27985
27986    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
27987        // EXTERNAL [this]
27988        self.write_keyword("EXTERNAL");
27989        if let Some(this) = &e.this {
27990            self.write_space();
27991            self.generate_expression(this)?;
27992        }
27993        Ok(())
27994    }
27995
27996    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
27997        // Python: {no}FALLBACK{protection}
27998        if e.no.is_some() {
27999            self.write_keyword("NO ");
28000        }
28001        self.write_keyword("FALLBACK");
28002        if e.protection.is_some() {
28003            self.write_keyword(" PROTECTION");
28004        }
28005        Ok(())
28006    }
28007
28008    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
28009        // BigQuery: FARM_FINGERPRINT(value)
28010        self.write_keyword("FARM_FINGERPRINT");
28011        self.write("(");
28012        for (i, expr) in e.expressions.iter().enumerate() {
28013            if i > 0 {
28014                self.write(", ");
28015            }
28016            self.generate_expression(expr)?;
28017        }
28018        self.write(")");
28019        Ok(())
28020    }
28021
28022    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
28023        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
28024        self.write_keyword("FEATURES_AT_TIME");
28025        self.write("(");
28026        self.generate_expression(&e.this)?;
28027        if let Some(time) = &e.time {
28028            self.write(", ");
28029            self.generate_expression(time)?;
28030        }
28031        if let Some(num_rows) = &e.num_rows {
28032            self.write(", ");
28033            self.generate_expression(num_rows)?;
28034        }
28035        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
28036            self.write(", ");
28037            self.generate_expression(ignore_nulls)?;
28038        }
28039        self.write(")");
28040        Ok(())
28041    }
28042
28043    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
28044        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
28045        let use_limit = !e.percent
28046            && !e.with_ties
28047            && e.count.is_some()
28048            && matches!(
28049                self.config.dialect,
28050                Some(DialectType::Spark)
28051                    | Some(DialectType::Hive)
28052                    | Some(DialectType::DuckDB)
28053                    | Some(DialectType::SQLite)
28054                    | Some(DialectType::MySQL)
28055                    | Some(DialectType::BigQuery)
28056                    | Some(DialectType::Databricks)
28057                    | Some(DialectType::StarRocks)
28058                    | Some(DialectType::Doris)
28059                    | Some(DialectType::Athena)
28060                    | Some(DialectType::ClickHouse)
28061            );
28062
28063        if use_limit {
28064            self.write_keyword("LIMIT");
28065            self.write_space();
28066            self.generate_expression(e.count.as_ref().unwrap())?;
28067            return Ok(());
28068        }
28069
28070        // Python: FETCH direction count limit_options
28071        self.write_keyword("FETCH");
28072        if !e.direction.is_empty() {
28073            self.write_space();
28074            self.write_keyword(&e.direction);
28075        }
28076        if let Some(count) = &e.count {
28077            self.write_space();
28078            self.generate_expression(count)?;
28079        }
28080        // Generate PERCENT, ROWS, WITH TIES/ONLY
28081        if e.percent {
28082            self.write_keyword(" PERCENT");
28083        }
28084        if e.rows {
28085            self.write_keyword(" ROWS");
28086        }
28087        if e.with_ties {
28088            self.write_keyword(" WITH TIES");
28089        } else if e.rows {
28090            self.write_keyword(" ONLY");
28091        } else {
28092            self.write_keyword(" ROWS ONLY");
28093        }
28094        Ok(())
28095    }
28096
28097    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
28098        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
28099        // For Spark/Databricks without hive_format: USING this
28100        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
28101        if e.hive_format.is_some() {
28102            // Hive format: STORED AS ...
28103            self.write_keyword("STORED AS");
28104            self.write_space();
28105            if let Some(this) = &e.this {
28106                // Uppercase the format name (e.g., parquet -> PARQUET)
28107                if let Expression::Identifier(id) = this.as_ref() {
28108                    self.write_keyword(&id.name.to_ascii_uppercase());
28109                } else {
28110                    self.generate_expression(this)?;
28111                }
28112            }
28113        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
28114            // Hive: STORED AS format
28115            self.write_keyword("STORED AS");
28116            self.write_space();
28117            if let Some(this) = &e.this {
28118                if let Expression::Identifier(id) = this.as_ref() {
28119                    self.write_keyword(&id.name.to_ascii_uppercase());
28120                } else {
28121                    self.generate_expression(this)?;
28122                }
28123            }
28124        } else if matches!(
28125            self.config.dialect,
28126            Some(DialectType::Spark) | Some(DialectType::Databricks)
28127        ) {
28128            // Spark/Databricks: USING format (e.g., USING DELTA)
28129            self.write_keyword("USING");
28130            self.write_space();
28131            if let Some(this) = &e.this {
28132                self.generate_expression(this)?;
28133            }
28134        } else {
28135            // Snowflake/standard format
28136            self.write_keyword("FILE_FORMAT");
28137            self.write(" = ");
28138            if let Some(this) = &e.this {
28139                self.generate_expression(this)?;
28140            } else if !e.expressions.is_empty() {
28141                self.write("(");
28142                for (i, expr) in e.expressions.iter().enumerate() {
28143                    if i > 0 {
28144                        self.write(", ");
28145                    }
28146                    self.generate_expression(expr)?;
28147                }
28148                self.write(")");
28149            }
28150        }
28151        Ok(())
28152    }
28153
28154    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
28155        // agg_func FILTER(WHERE condition)
28156        self.generate_expression(&e.this)?;
28157        self.write_space();
28158        self.write_keyword("FILTER");
28159        self.write("(");
28160        self.write_keyword("WHERE");
28161        self.write_space();
28162        self.generate_expression(&e.expression)?;
28163        self.write(")");
28164        Ok(())
28165    }
28166
28167    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
28168        // FLOAT64(this) or FLOAT64(this, expression)
28169        self.write_keyword("FLOAT64");
28170        self.write("(");
28171        self.generate_expression(&e.this)?;
28172        if let Some(expr) = &e.expression {
28173            self.write(", ");
28174            self.generate_expression(expr)?;
28175        }
28176        self.write(")");
28177        Ok(())
28178    }
28179
28180    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
28181        // FOR this DO expression
28182        self.write_keyword("FOR");
28183        self.write_space();
28184        self.generate_expression(&e.this)?;
28185        self.write_space();
28186        self.write_keyword("DO");
28187        self.write_space();
28188        self.generate_expression(&e.expression)?;
28189        Ok(())
28190    }
28191
28192    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
28193        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
28194        self.write_keyword("FOREIGN KEY");
28195        if !e.expressions.is_empty() {
28196            self.write(" (");
28197            for (i, expr) in e.expressions.iter().enumerate() {
28198                if i > 0 {
28199                    self.write(", ");
28200                }
28201                self.generate_expression(expr)?;
28202            }
28203            self.write(")");
28204        }
28205        if let Some(reference) = &e.reference {
28206            self.write_space();
28207            self.generate_expression(reference)?;
28208        }
28209        if let Some(delete) = &e.delete {
28210            self.write_space();
28211            self.write_keyword("ON DELETE");
28212            self.write_space();
28213            self.generate_expression(delete)?;
28214        }
28215        if let Some(update) = &e.update {
28216            self.write_space();
28217            self.write_keyword("ON UPDATE");
28218            self.write_space();
28219            self.generate_expression(update)?;
28220        }
28221        if !e.options.is_empty() {
28222            self.write_space();
28223            for (i, opt) in e.options.iter().enumerate() {
28224                if i > 0 {
28225                    self.write_space();
28226                }
28227                self.generate_expression(opt)?;
28228            }
28229        }
28230        Ok(())
28231    }
28232
28233    fn generate_format(&mut self, e: &Format) -> Result<()> {
28234        // FORMAT(this, expressions...)
28235        self.write_keyword("FORMAT");
28236        self.write("(");
28237        self.generate_expression(&e.this)?;
28238        for expr in &e.expressions {
28239            self.write(", ");
28240            self.generate_expression(expr)?;
28241        }
28242        self.write(")");
28243        Ok(())
28244    }
28245
28246    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
28247        // Teradata: column (FORMAT 'format_string')
28248        self.generate_expression(&e.this)?;
28249        self.write(" (");
28250        self.write_keyword("FORMAT");
28251        self.write(" '");
28252        self.write(&e.format);
28253        self.write("')");
28254        Ok(())
28255    }
28256
28257    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
28258        // Python: FREESPACE=this[PERCENT]
28259        self.write_keyword("FREESPACE");
28260        self.write("=");
28261        self.generate_expression(&e.this)?;
28262        if e.percent.is_some() {
28263            self.write_keyword(" PERCENT");
28264        }
28265        Ok(())
28266    }
28267
28268    fn generate_from(&mut self, e: &From) -> Result<()> {
28269        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
28270        self.write_keyword("FROM");
28271        self.write_space();
28272
28273        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
28274        // But keep commas when TABLESAMPLE is present
28275        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
28276        use crate::dialects::DialectType;
28277        let has_tablesample = e
28278            .expressions
28279            .iter()
28280            .any(|expr| matches!(expr, Expression::TableSample(_)));
28281        let is_cross_join_dialect = matches!(
28282            self.config.dialect,
28283            Some(DialectType::BigQuery)
28284                | Some(DialectType::Hive)
28285                | Some(DialectType::Spark)
28286                | Some(DialectType::Databricks)
28287                | Some(DialectType::SQLite)
28288                | Some(DialectType::ClickHouse)
28289        );
28290        let source_is_same_as_target2 = self.config.source_dialect.is_some()
28291            && self.config.source_dialect == self.config.dialect;
28292        let source_is_cross_join_dialect2 = matches!(
28293            self.config.source_dialect,
28294            Some(DialectType::BigQuery)
28295                | Some(DialectType::Hive)
28296                | Some(DialectType::Spark)
28297                | Some(DialectType::Databricks)
28298                | Some(DialectType::SQLite)
28299                | Some(DialectType::ClickHouse)
28300        );
28301        let use_cross_join = !has_tablesample
28302            && is_cross_join_dialect
28303            && (source_is_same_as_target2
28304                || source_is_cross_join_dialect2
28305                || self.config.source_dialect.is_none());
28306
28307        // Snowflake wraps standalone VALUES in FROM clause with parentheses
28308        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
28309
28310        for (i, expr) in e.expressions.iter().enumerate() {
28311            if i > 0 {
28312                if use_cross_join {
28313                    self.write(" CROSS JOIN ");
28314                } else {
28315                    self.write(", ");
28316                }
28317            }
28318            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
28319                self.write("(");
28320                self.generate_expression(expr)?;
28321                self.write(")");
28322            } else {
28323                self.generate_expression(expr)?;
28324            }
28325            // Output leading comments that were on the table name before FROM
28326            // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
28327            let leading = Self::extract_table_leading_comments(expr);
28328            for comment in &leading {
28329                self.write_space();
28330                self.write_formatted_comment(comment);
28331            }
28332        }
28333        Ok(())
28334    }
28335
28336    /// Extract leading_comments from a table expression (possibly wrapped in PIVOT/UNPIVOT)
28337    fn extract_table_leading_comments(expr: &Expression) -> Vec<String> {
28338        match expr {
28339            Expression::Table(t) => t.leading_comments.clone(),
28340            Expression::Pivot(p) => {
28341                if let Expression::Table(t) = &p.this {
28342                    t.leading_comments.clone()
28343                } else {
28344                    Vec::new()
28345                }
28346            }
28347            _ => Vec::new(),
28348        }
28349    }
28350
28351    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
28352        // FROM_BASE(this, expression) - convert from base N
28353        self.write_keyword("FROM_BASE");
28354        self.write("(");
28355        self.generate_expression(&e.this)?;
28356        self.write(", ");
28357        self.generate_expression(&e.expression)?;
28358        self.write(")");
28359        Ok(())
28360    }
28361
28362    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
28363        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
28364        self.generate_expression(&e.this)?;
28365        if let Some(zone) = &e.zone {
28366            self.write_space();
28367            self.write_keyword("AT TIME ZONE");
28368            self.write_space();
28369            self.generate_expression(zone)?;
28370            self.write_space();
28371            self.write_keyword("AT TIME ZONE");
28372            self.write(" 'UTC'");
28373        }
28374        Ok(())
28375    }
28376
28377    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
28378        // GAP_FILL(this, ts_column, bucket_width, ...)
28379        self.write_keyword("GAP_FILL");
28380        self.write("(");
28381        self.generate_expression(&e.this)?;
28382        if let Some(ts_column) = &e.ts_column {
28383            self.write(", ");
28384            self.generate_expression(ts_column)?;
28385        }
28386        if let Some(bucket_width) = &e.bucket_width {
28387            self.write(", ");
28388            self.generate_expression(bucket_width)?;
28389        }
28390        if let Some(partitioning_columns) = &e.partitioning_columns {
28391            self.write(", ");
28392            self.generate_expression(partitioning_columns)?;
28393        }
28394        if let Some(value_columns) = &e.value_columns {
28395            self.write(", ");
28396            self.generate_expression(value_columns)?;
28397        }
28398        self.write(")");
28399        Ok(())
28400    }
28401
28402    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
28403        // GENERATE_DATE_ARRAY(start, end, step)
28404        self.write_keyword("GENERATE_DATE_ARRAY");
28405        self.write("(");
28406        let mut first = true;
28407        if let Some(start) = &e.start {
28408            self.generate_expression(start)?;
28409            first = false;
28410        }
28411        if let Some(end) = &e.end {
28412            if !first {
28413                self.write(", ");
28414            }
28415            self.generate_expression(end)?;
28416            first = false;
28417        }
28418        if let Some(step) = &e.step {
28419            if !first {
28420                self.write(", ");
28421            }
28422            self.generate_expression(step)?;
28423        }
28424        self.write(")");
28425        Ok(())
28426    }
28427
28428    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
28429        // ML.GENERATE_EMBEDDING(model, content, params)
28430        self.write_keyword("ML.GENERATE_EMBEDDING");
28431        self.write("(");
28432        self.generate_expression(&e.this)?;
28433        self.write(", ");
28434        self.generate_expression(&e.expression)?;
28435        if let Some(params) = &e.params_struct {
28436            self.write(", ");
28437            self.generate_expression(params)?;
28438        }
28439        self.write(")");
28440        Ok(())
28441    }
28442
28443    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
28444        // Dialect-specific function name
28445        let fn_name = match self.config.dialect {
28446            Some(DialectType::Presto)
28447            | Some(DialectType::Trino)
28448            | Some(DialectType::Athena)
28449            | Some(DialectType::Spark)
28450            | Some(DialectType::Databricks)
28451            | Some(DialectType::Hive) => "SEQUENCE",
28452            _ => "GENERATE_SERIES",
28453        };
28454        self.write_keyword(fn_name);
28455        self.write("(");
28456        let mut first = true;
28457        if let Some(start) = &e.start {
28458            self.generate_expression(start)?;
28459            first = false;
28460        }
28461        if let Some(end) = &e.end {
28462            if !first {
28463                self.write(", ");
28464            }
28465            self.generate_expression(end)?;
28466            first = false;
28467        }
28468        if let Some(step) = &e.step {
28469            if !first {
28470                self.write(", ");
28471            }
28472            // For Presto/Trino: convert WEEK intervals to DAY multiples
28473            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
28474            if matches!(
28475                self.config.dialect,
28476                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
28477            ) {
28478                if let Some(converted) = self.convert_week_interval_to_day(step) {
28479                    self.generate_expression(&converted)?;
28480                } else {
28481                    self.generate_expression(step)?;
28482                }
28483            } else {
28484                self.generate_expression(step)?;
28485            }
28486        }
28487        self.write(")");
28488        Ok(())
28489    }
28490
28491    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
28492    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
28493    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
28494        use crate::expressions::*;
28495        if let Expression::Interval(ref iv) = expr {
28496            // Check for structured WEEK unit
28497            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
28498                unit: IntervalUnit::Week,
28499                ..
28500            }) = &iv.unit
28501            {
28502                // Value is in iv.this
28503                let count = match &iv.this {
28504                    Some(Expression::Literal(lit)) => match lit.as_ref() {
28505                        Literal::String(s) | Literal::Number(s) => s.clone(),
28506                        _ => return None,
28507                    },
28508                    _ => return None,
28509                };
28510                (true, count)
28511            } else if iv.unit.is_none() {
28512                // Check for string-encoded interval like "1 WEEK"
28513                if let Some(Expression::Literal(lit)) = &iv.this {
28514                    if let Literal::String(s) = lit.as_ref() {
28515                        let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
28516                        if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
28517                            (true, parts[0].to_string())
28518                        } else {
28519                            (false, String::new())
28520                        }
28521                    } else {
28522                        (false, String::new())
28523                    }
28524                } else {
28525                    (false, String::new())
28526                }
28527            } else {
28528                (false, String::new())
28529            };
28530
28531            if is_week {
28532                // Build: (N * INTERVAL '7' DAY)
28533                let count_expr = Expression::Literal(Box::new(Literal::Number(count_str)));
28534                let day_interval = Expression::Interval(Box::new(Interval {
28535                    this: Some(Expression::Literal(Box::new(Literal::String(
28536                        "7".to_string(),
28537                    )))),
28538                    unit: Some(IntervalUnitSpec::Simple {
28539                        unit: IntervalUnit::Day,
28540                        use_plural: false,
28541                    }),
28542                }));
28543                let mul = Expression::Mul(Box::new(BinaryOp {
28544                    left: count_expr,
28545                    right: day_interval,
28546                    left_comments: vec![],
28547                    operator_comments: vec![],
28548                    trailing_comments: vec![],
28549                    inferred_type: None,
28550                }));
28551                return Some(Expression::Paren(Box::new(Paren {
28552                    this: mul,
28553                    trailing_comments: vec![],
28554                })));
28555            }
28556        }
28557        None
28558    }
28559
28560    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
28561        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
28562        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
28563        self.write("(");
28564        let mut first = true;
28565        if let Some(start) = &e.start {
28566            self.generate_expression(start)?;
28567            first = false;
28568        }
28569        if let Some(end) = &e.end {
28570            if !first {
28571                self.write(", ");
28572            }
28573            self.generate_expression(end)?;
28574            first = false;
28575        }
28576        if let Some(step) = &e.step {
28577            if !first {
28578                self.write(", ");
28579            }
28580            self.generate_expression(step)?;
28581        }
28582        self.write(")");
28583        Ok(())
28584    }
28585
28586    fn generate_generated_as_identity_column_constraint(
28587        &mut self,
28588        e: &GeneratedAsIdentityColumnConstraint,
28589    ) -> Result<()> {
28590        use crate::dialects::DialectType;
28591
28592        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
28593        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
28594            self.write_keyword("AUTOINCREMENT");
28595            if let Some(start) = &e.start {
28596                self.write_keyword(" START ");
28597                self.generate_expression(start)?;
28598            }
28599            if let Some(increment) = &e.increment {
28600                self.write_keyword(" INCREMENT ");
28601                self.generate_expression(increment)?;
28602            }
28603            return Ok(());
28604        }
28605
28606        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
28607        self.write_keyword("GENERATED");
28608        if let Some(this) = &e.this {
28609            // Check if it's a truthy boolean expression
28610            if let Expression::Boolean(b) = this.as_ref() {
28611                if b.value {
28612                    self.write_keyword(" ALWAYS");
28613                } else {
28614                    self.write_keyword(" BY DEFAULT");
28615                    if e.on_null.is_some() {
28616                        self.write_keyword(" ON NULL");
28617                    }
28618                }
28619            } else {
28620                self.write_keyword(" ALWAYS");
28621            }
28622        }
28623        self.write_keyword(" AS IDENTITY");
28624        // Add sequence options if any
28625        let has_options = e.start.is_some()
28626            || e.increment.is_some()
28627            || e.minvalue.is_some()
28628            || e.maxvalue.is_some();
28629        if has_options {
28630            self.write(" (");
28631            let mut first = true;
28632            if let Some(start) = &e.start {
28633                self.write_keyword("START WITH ");
28634                self.generate_expression(start)?;
28635                first = false;
28636            }
28637            if let Some(increment) = &e.increment {
28638                if !first {
28639                    self.write(" ");
28640                }
28641                self.write_keyword("INCREMENT BY ");
28642                self.generate_expression(increment)?;
28643                first = false;
28644            }
28645            if let Some(minvalue) = &e.minvalue {
28646                if !first {
28647                    self.write(" ");
28648                }
28649                self.write_keyword("MINVALUE ");
28650                self.generate_expression(minvalue)?;
28651                first = false;
28652            }
28653            if let Some(maxvalue) = &e.maxvalue {
28654                if !first {
28655                    self.write(" ");
28656                }
28657                self.write_keyword("MAXVALUE ");
28658                self.generate_expression(maxvalue)?;
28659            }
28660            self.write(")");
28661        }
28662        Ok(())
28663    }
28664
28665    fn generate_generated_as_row_column_constraint(
28666        &mut self,
28667        e: &GeneratedAsRowColumnConstraint,
28668    ) -> Result<()> {
28669        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
28670        self.write_keyword("GENERATED ALWAYS AS ROW ");
28671        if e.start.is_some() {
28672            self.write_keyword("START");
28673        } else {
28674            self.write_keyword("END");
28675        }
28676        if e.hidden.is_some() {
28677            self.write_keyword(" HIDDEN");
28678        }
28679        Ok(())
28680    }
28681
28682    fn generate_get(&mut self, e: &Get) -> Result<()> {
28683        // GET this target properties
28684        self.write_keyword("GET");
28685        self.write_space();
28686        self.generate_expression(&e.this)?;
28687        if let Some(target) = &e.target {
28688            self.write_space();
28689            self.generate_expression(target)?;
28690        }
28691        for prop in &e.properties {
28692            self.write_space();
28693            self.generate_expression(prop)?;
28694        }
28695        Ok(())
28696    }
28697
28698    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
28699        // GetExtract generates bracket access: this[expression]
28700        self.generate_expression(&e.this)?;
28701        self.write("[");
28702        self.generate_expression(&e.expression)?;
28703        self.write("]");
28704        Ok(())
28705    }
28706
28707    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
28708        // GETBIT(this, expression) or GET_BIT(this, expression)
28709        self.write_keyword("GETBIT");
28710        self.write("(");
28711        self.generate_expression(&e.this)?;
28712        self.write(", ");
28713        self.generate_expression(&e.expression)?;
28714        self.write(")");
28715        Ok(())
28716    }
28717
28718    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
28719        // [ROLE|GROUP|SHARE] name (e.g., "ROLE admin", "GROUP qa_users", "SHARE s1", or just "user1")
28720        if e.is_role {
28721            self.write_keyword("ROLE");
28722            self.write_space();
28723        } else if e.is_group {
28724            self.write_keyword("GROUP");
28725            self.write_space();
28726        } else if e.is_share {
28727            self.write_keyword("SHARE");
28728            self.write_space();
28729        }
28730        self.write(&e.name.name);
28731        Ok(())
28732    }
28733
28734    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
28735        // privilege(columns) or just privilege
28736        self.generate_expression(&e.this)?;
28737        if !e.expressions.is_empty() {
28738            self.write("(");
28739            for (i, expr) in e.expressions.iter().enumerate() {
28740                if i > 0 {
28741                    self.write(", ");
28742                }
28743                self.generate_expression(expr)?;
28744            }
28745            self.write(")");
28746        }
28747        Ok(())
28748    }
28749
28750    fn generate_group(&mut self, e: &Group) -> Result<()> {
28751        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
28752        self.write_keyword("GROUP BY");
28753        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
28754        match e.all {
28755            Some(true) => {
28756                self.write_space();
28757                self.write_keyword("ALL");
28758            }
28759            Some(false) => {
28760                self.write_space();
28761                self.write_keyword("DISTINCT");
28762            }
28763            None => {}
28764        }
28765        if !e.expressions.is_empty() {
28766            self.write_space();
28767            for (i, expr) in e.expressions.iter().enumerate() {
28768                if i > 0 {
28769                    self.write(", ");
28770                }
28771                self.generate_expression(expr)?;
28772            }
28773        }
28774        // Handle CUBE, ROLLUP, GROUPING SETS
28775        if let Some(cube) = &e.cube {
28776            if !e.expressions.is_empty() {
28777                self.write(", ");
28778            } else {
28779                self.write_space();
28780            }
28781            self.generate_expression(cube)?;
28782        }
28783        if let Some(rollup) = &e.rollup {
28784            if !e.expressions.is_empty() || e.cube.is_some() {
28785                self.write(", ");
28786            } else {
28787                self.write_space();
28788            }
28789            self.generate_expression(rollup)?;
28790        }
28791        if let Some(grouping_sets) = &e.grouping_sets {
28792            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
28793                self.write(", ");
28794            } else {
28795                self.write_space();
28796            }
28797            self.generate_expression(grouping_sets)?;
28798        }
28799        if let Some(totals) = &e.totals {
28800            self.write_space();
28801            self.write_keyword("WITH TOTALS");
28802            self.generate_expression(totals)?;
28803        }
28804        Ok(())
28805    }
28806
28807    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
28808        // GROUP BY expressions
28809        self.write_keyword("GROUP BY");
28810        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
28811        match e.all {
28812            Some(true) => {
28813                self.write_space();
28814                self.write_keyword("ALL");
28815            }
28816            Some(false) => {
28817                self.write_space();
28818                self.write_keyword("DISTINCT");
28819            }
28820            None => {}
28821        }
28822
28823        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
28824        // These are represented as Cube/Rollup expressions with empty expressions at the end
28825        let mut trailing_cube = false;
28826        let mut trailing_rollup = false;
28827        let mut regular_expressions: Vec<&Expression> = Vec::new();
28828
28829        for expr in &e.expressions {
28830            match expr {
28831                Expression::Cube(c) if c.expressions.is_empty() => {
28832                    trailing_cube = true;
28833                }
28834                Expression::Rollup(r) if r.expressions.is_empty() => {
28835                    trailing_rollup = true;
28836                }
28837                _ => {
28838                    regular_expressions.push(expr);
28839                }
28840            }
28841        }
28842
28843        // In pretty mode, put columns on separate lines
28844        if self.config.pretty {
28845            self.write_newline();
28846            self.indent_level += 1;
28847            for (i, expr) in regular_expressions.iter().enumerate() {
28848                if i > 0 {
28849                    self.write(",");
28850                    self.write_newline();
28851                }
28852                self.write_indent();
28853                self.generate_expression(expr)?;
28854            }
28855            self.indent_level -= 1;
28856        } else {
28857            self.write_space();
28858            for (i, expr) in regular_expressions.iter().enumerate() {
28859                if i > 0 {
28860                    self.write(", ");
28861                }
28862                self.generate_expression(expr)?;
28863            }
28864        }
28865
28866        // Output trailing WITH CUBE or WITH ROLLUP
28867        if trailing_cube {
28868            self.write_space();
28869            self.write_keyword("WITH CUBE");
28870        } else if trailing_rollup {
28871            self.write_space();
28872            self.write_keyword("WITH ROLLUP");
28873        }
28874
28875        // ClickHouse: WITH TOTALS
28876        if e.totals {
28877            self.write_space();
28878            self.write_keyword("WITH TOTALS");
28879        }
28880
28881        Ok(())
28882    }
28883
28884    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
28885        // GROUPING(col1, col2, ...)
28886        self.write_keyword("GROUPING");
28887        self.write("(");
28888        for (i, expr) in e.expressions.iter().enumerate() {
28889            if i > 0 {
28890                self.write(", ");
28891            }
28892            self.generate_expression(expr)?;
28893        }
28894        self.write(")");
28895        Ok(())
28896    }
28897
28898    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
28899        // GROUPING_ID(col1, col2, ...)
28900        self.write_keyword("GROUPING_ID");
28901        self.write("(");
28902        for (i, expr) in e.expressions.iter().enumerate() {
28903            if i > 0 {
28904                self.write(", ");
28905            }
28906            self.generate_expression(expr)?;
28907        }
28908        self.write(")");
28909        Ok(())
28910    }
28911
28912    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
28913        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
28914        self.write_keyword("GROUPING SETS");
28915        self.write(" (");
28916        for (i, expr) in e.expressions.iter().enumerate() {
28917            if i > 0 {
28918                self.write(", ");
28919            }
28920            self.generate_expression(expr)?;
28921        }
28922        self.write(")");
28923        Ok(())
28924    }
28925
28926    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
28927        // HASH_AGG(this, expressions...)
28928        self.write_keyword("HASH_AGG");
28929        self.write("(");
28930        self.generate_expression(&e.this)?;
28931        for expr in &e.expressions {
28932            self.write(", ");
28933            self.generate_expression(expr)?;
28934        }
28935        self.write(")");
28936        Ok(())
28937    }
28938
28939    fn generate_having(&mut self, e: &Having) -> Result<()> {
28940        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
28941        self.write_keyword("HAVING");
28942        self.write_space();
28943        self.generate_expression(&e.this)?;
28944        Ok(())
28945    }
28946
28947    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
28948        // Python: this HAVING MAX|MIN expression
28949        self.generate_expression(&e.this)?;
28950        self.write_space();
28951        self.write_keyword("HAVING");
28952        self.write_space();
28953        if e.max.is_some() {
28954            self.write_keyword("MAX");
28955        } else {
28956            self.write_keyword("MIN");
28957        }
28958        self.write_space();
28959        self.generate_expression(&e.expression)?;
28960        Ok(())
28961    }
28962
28963    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
28964        use crate::dialects::DialectType;
28965        // DuckDB: convert dollar-tagged strings to single-quoted
28966        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
28967            // Extract the string content and output as single-quoted
28968            if let Expression::Literal(ref lit) = *e.this {
28969                if let Literal::String(ref s) = lit.as_ref() {
28970                    return self.generate_string_literal(s);
28971                }
28972            }
28973        }
28974        // PostgreSQL: preserve dollar-quoting
28975        if matches!(
28976            self.config.dialect,
28977            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
28978        ) {
28979            self.write("$");
28980            if let Some(tag) = &e.tag {
28981                self.generate_expression(tag)?;
28982            }
28983            self.write("$");
28984            self.generate_expression(&e.this)?;
28985            self.write("$");
28986            if let Some(tag) = &e.tag {
28987                self.generate_expression(tag)?;
28988            }
28989            self.write("$");
28990            return Ok(());
28991        }
28992        // Default: output as dollar-tagged
28993        self.write("$");
28994        if let Some(tag) = &e.tag {
28995            self.generate_expression(tag)?;
28996        }
28997        self.write("$");
28998        self.generate_expression(&e.this)?;
28999        self.write("$");
29000        if let Some(tag) = &e.tag {
29001            self.generate_expression(tag)?;
29002        }
29003        self.write("$");
29004        Ok(())
29005    }
29006
29007    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
29008        // HEX_ENCODE(this)
29009        self.write_keyword("HEX_ENCODE");
29010        self.write("(");
29011        self.generate_expression(&e.this)?;
29012        self.write(")");
29013        Ok(())
29014    }
29015
29016    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
29017        // Python: this (kind => expression)
29018        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
29019        match e.this.as_ref() {
29020            Expression::Identifier(id) => self.write(&id.name),
29021            other => self.generate_expression(other)?,
29022        }
29023        self.write(" (");
29024        self.write(&e.kind);
29025        self.write(" => ");
29026        self.generate_expression(&e.expression)?;
29027        self.write(")");
29028        Ok(())
29029    }
29030
29031    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
29032        // HLL(this, expressions...)
29033        self.write_keyword("HLL");
29034        self.write("(");
29035        self.generate_expression(&e.this)?;
29036        for expr in &e.expressions {
29037            self.write(", ");
29038            self.generate_expression(expr)?;
29039        }
29040        self.write(")");
29041        Ok(())
29042    }
29043
29044    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
29045        // Python: IN|OUT|IN OUT
29046        if e.input_.is_some() && e.output.is_some() {
29047            self.write_keyword("IN OUT");
29048        } else if e.input_.is_some() {
29049            self.write_keyword("IN");
29050        } else if e.output.is_some() {
29051            self.write_keyword("OUT");
29052        }
29053        Ok(())
29054    }
29055
29056    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
29057        // Python: INCLUDE this [column_def] [AS alias]
29058        self.write_keyword("INCLUDE");
29059        self.write_space();
29060        self.generate_expression(&e.this)?;
29061        if let Some(column_def) = &e.column_def {
29062            self.write_space();
29063            self.generate_expression(column_def)?;
29064        }
29065        if let Some(alias) = &e.alias {
29066            self.write_space();
29067            self.write_keyword("AS");
29068            self.write_space();
29069            self.write(alias);
29070        }
29071        Ok(())
29072    }
29073
29074    fn generate_index(&mut self, e: &Index) -> Result<()> {
29075        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
29076        if e.unique {
29077            self.write_keyword("UNIQUE");
29078            self.write_space();
29079        }
29080        if e.primary.is_some() {
29081            self.write_keyword("PRIMARY");
29082            self.write_space();
29083        }
29084        if e.amp.is_some() {
29085            self.write_keyword("AMP");
29086            self.write_space();
29087        }
29088        if e.table.is_none() {
29089            self.write_keyword("INDEX");
29090            self.write_space();
29091        }
29092        if let Some(name) = &e.this {
29093            self.generate_expression(name)?;
29094            self.write_space();
29095        }
29096        if let Some(table) = &e.table {
29097            self.write_keyword("ON");
29098            self.write_space();
29099            self.generate_expression(table)?;
29100        }
29101        if !e.params.is_empty() {
29102            self.write("(");
29103            for (i, param) in e.params.iter().enumerate() {
29104                if i > 0 {
29105                    self.write(", ");
29106                }
29107                self.generate_expression(param)?;
29108            }
29109            self.write(")");
29110        }
29111        Ok(())
29112    }
29113
29114    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
29115        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
29116        if let Some(kind) = &e.kind {
29117            self.write(kind);
29118            self.write_space();
29119        }
29120        self.write_keyword("INDEX");
29121        if let Some(this) = &e.this {
29122            self.write_space();
29123            self.generate_expression(this)?;
29124        }
29125        if let Some(index_type) = &e.index_type {
29126            self.write_space();
29127            self.write_keyword("USING");
29128            self.write_space();
29129            self.generate_expression(index_type)?;
29130        }
29131        if !e.expressions.is_empty() {
29132            self.write(" (");
29133            for (i, expr) in e.expressions.iter().enumerate() {
29134                if i > 0 {
29135                    self.write(", ");
29136                }
29137                self.generate_expression(expr)?;
29138            }
29139            self.write(")");
29140        }
29141        for opt in &e.options {
29142            self.write_space();
29143            self.generate_expression(opt)?;
29144        }
29145        Ok(())
29146    }
29147
29148    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
29149        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
29150        if let Some(key_block_size) = &e.key_block_size {
29151            self.write_keyword("KEY_BLOCK_SIZE");
29152            self.write(" = ");
29153            self.generate_expression(key_block_size)?;
29154        } else if let Some(using) = &e.using {
29155            self.write_keyword("USING");
29156            self.write_space();
29157            self.generate_expression(using)?;
29158        } else if let Some(parser) = &e.parser {
29159            self.write_keyword("WITH PARSER");
29160            self.write_space();
29161            self.generate_expression(parser)?;
29162        } else if let Some(comment) = &e.comment {
29163            self.write_keyword("COMMENT");
29164            self.write_space();
29165            self.generate_expression(comment)?;
29166        } else if let Some(visible) = &e.visible {
29167            self.generate_expression(visible)?;
29168        } else if let Some(engine_attr) = &e.engine_attr {
29169            self.write_keyword("ENGINE_ATTRIBUTE");
29170            self.write(" = ");
29171            self.generate_expression(engine_attr)?;
29172        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
29173            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
29174            self.write(" = ");
29175            self.generate_expression(secondary_engine_attr)?;
29176        }
29177        Ok(())
29178    }
29179
29180    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
29181        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
29182        if let Some(using) = &e.using {
29183            self.write_keyword("USING");
29184            self.write_space();
29185            self.generate_expression(using)?;
29186        }
29187        if !e.columns.is_empty() {
29188            self.write("(");
29189            for (i, col) in e.columns.iter().enumerate() {
29190                if i > 0 {
29191                    self.write(", ");
29192                }
29193                self.generate_expression(col)?;
29194            }
29195            self.write(")");
29196        }
29197        if let Some(partition_by) = &e.partition_by {
29198            self.write_space();
29199            self.write_keyword("PARTITION BY");
29200            self.write_space();
29201            self.generate_expression(partition_by)?;
29202        }
29203        if let Some(where_) = &e.where_ {
29204            self.write_space();
29205            self.generate_expression(where_)?;
29206        }
29207        if let Some(include) = &e.include {
29208            self.write_space();
29209            self.write_keyword("INCLUDE");
29210            self.write(" (");
29211            self.generate_expression(include)?;
29212            self.write(")");
29213        }
29214        if let Some(with_storage) = &e.with_storage {
29215            self.write_space();
29216            self.write_keyword("WITH");
29217            self.write(" (");
29218            self.generate_expression(with_storage)?;
29219            self.write(")");
29220        }
29221        if let Some(tablespace) = &e.tablespace {
29222            self.write_space();
29223            self.write_keyword("USING INDEX TABLESPACE");
29224            self.write_space();
29225            self.generate_expression(tablespace)?;
29226        }
29227        Ok(())
29228    }
29229
29230    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
29231        // Python: this INDEX [FOR target] (expressions)
29232        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
29233        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
29234        if let Expression::Identifier(id) = &*e.this {
29235            self.write_keyword(&id.name);
29236        } else {
29237            self.generate_expression(&e.this)?;
29238        }
29239        self.write_space();
29240        self.write_keyword("INDEX");
29241        if let Some(target) = &e.target {
29242            self.write_space();
29243            self.write_keyword("FOR");
29244            self.write_space();
29245            if let Expression::Identifier(id) = &**target {
29246                self.write_keyword(&id.name);
29247            } else {
29248                self.generate_expression(target)?;
29249            }
29250        }
29251        // Always output parentheses (even if empty, e.g. USE INDEX ())
29252        self.write(" (");
29253        for (i, expr) in e.expressions.iter().enumerate() {
29254            if i > 0 {
29255                self.write(", ");
29256            }
29257            self.generate_expression(expr)?;
29258        }
29259        self.write(")");
29260        Ok(())
29261    }
29262
29263    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
29264        // INHERITS (table1, table2, ...)
29265        self.write_keyword("INHERITS");
29266        self.write(" (");
29267        for (i, expr) in e.expressions.iter().enumerate() {
29268            if i > 0 {
29269                self.write(", ");
29270            }
29271            self.generate_expression(expr)?;
29272        }
29273        self.write(")");
29274        Ok(())
29275    }
29276
29277    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
29278        // INPUT(model)
29279        self.write_keyword("INPUT");
29280        self.write("(");
29281        self.generate_expression(&e.this)?;
29282        self.write(")");
29283        Ok(())
29284    }
29285
29286    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
29287        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
29288        if let Some(input_format) = &e.input_format {
29289            self.write_keyword("INPUTFORMAT");
29290            self.write_space();
29291            self.generate_expression(input_format)?;
29292        }
29293        if let Some(output_format) = &e.output_format {
29294            if e.input_format.is_some() {
29295                self.write(" ");
29296            }
29297            self.write_keyword("OUTPUTFORMAT");
29298            self.write_space();
29299            self.generate_expression(output_format)?;
29300        }
29301        Ok(())
29302    }
29303
29304    fn generate_install(&mut self, e: &Install) -> Result<()> {
29305        // [FORCE] INSTALL extension [FROM source]
29306        if e.force.is_some() {
29307            self.write_keyword("FORCE");
29308            self.write_space();
29309        }
29310        self.write_keyword("INSTALL");
29311        self.write_space();
29312        self.generate_expression(&e.this)?;
29313        if let Some(from) = &e.from_ {
29314            self.write_space();
29315            self.write_keyword("FROM");
29316            self.write_space();
29317            self.generate_expression(from)?;
29318        }
29319        Ok(())
29320    }
29321
29322    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
29323        // INTERVAL 'expression' unit
29324        self.write_keyword("INTERVAL");
29325        self.write_space();
29326        // When a unit is specified and the expression is a number,
29327        self.generate_expression(&e.expression)?;
29328        if let Some(unit) = &e.unit {
29329            self.write_space();
29330            self.write(unit);
29331        }
29332        Ok(())
29333    }
29334
29335    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
29336        // unit TO unit (e.g., HOUR TO SECOND)
29337        self.write(&format!("{:?}", e.this).to_ascii_uppercase());
29338        self.write_space();
29339        self.write_keyword("TO");
29340        self.write_space();
29341        self.write(&format!("{:?}", e.expression).to_ascii_uppercase());
29342        Ok(())
29343    }
29344
29345    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
29346        // INTO [TEMPORARY|UNLOGGED] table
29347        self.write_keyword("INTO");
29348        if e.temporary {
29349            self.write_keyword(" TEMPORARY");
29350        }
29351        if e.unlogged.is_some() {
29352            self.write_keyword(" UNLOGGED");
29353        }
29354        if let Some(this) = &e.this {
29355            self.write_space();
29356            self.generate_expression(this)?;
29357        }
29358        if !e.expressions.is_empty() {
29359            self.write(" (");
29360            for (i, expr) in e.expressions.iter().enumerate() {
29361                if i > 0 {
29362                    self.write(", ");
29363                }
29364                self.generate_expression(expr)?;
29365            }
29366            self.write(")");
29367        }
29368        Ok(())
29369    }
29370
29371    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
29372        // Python: this expression (e.g., _utf8 'string')
29373        self.generate_expression(&e.this)?;
29374        self.write_space();
29375        self.generate_expression(&e.expression)?;
29376        Ok(())
29377    }
29378
29379    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
29380        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
29381        self.write_keyword("WITH");
29382        if e.no.is_some() {
29383            self.write_keyword(" NO");
29384        }
29385        if e.concurrent.is_some() {
29386            self.write_keyword(" CONCURRENT");
29387        }
29388        self.write_keyword(" ISOLATED LOADING");
29389        if let Some(target) = &e.target {
29390            self.write_space();
29391            self.generate_expression(target)?;
29392        }
29393        Ok(())
29394    }
29395
29396    fn generate_json(&mut self, e: &JSON) -> Result<()> {
29397        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
29398        self.write_keyword("JSON");
29399        if let Some(this) = &e.this {
29400            self.write_space();
29401            self.generate_expression(this)?;
29402        }
29403        if let Some(with_) = &e.with_ {
29404            // Check if it's a truthy boolean
29405            if let Expression::Boolean(b) = with_.as_ref() {
29406                if b.value {
29407                    self.write_keyword(" WITH");
29408                } else {
29409                    self.write_keyword(" WITHOUT");
29410                }
29411            }
29412        }
29413        if e.unique {
29414            self.write_keyword(" UNIQUE KEYS");
29415        }
29416        Ok(())
29417    }
29418
29419    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
29420        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
29421        self.write_keyword("JSON_ARRAY");
29422        self.write("(");
29423        for (i, expr) in e.expressions.iter().enumerate() {
29424            if i > 0 {
29425                self.write(", ");
29426            }
29427            self.generate_expression(expr)?;
29428        }
29429        if let Some(null_handling) = &e.null_handling {
29430            self.write_space();
29431            self.generate_expression(null_handling)?;
29432        }
29433        if let Some(return_type) = &e.return_type {
29434            self.write_space();
29435            self.write_keyword("RETURNING");
29436            self.write_space();
29437            self.generate_expression(return_type)?;
29438        }
29439        if e.strict.is_some() {
29440            self.write_space();
29441            self.write_keyword("STRICT");
29442        }
29443        self.write(")");
29444        Ok(())
29445    }
29446
29447    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
29448        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
29449        self.write_keyword("JSON_ARRAYAGG");
29450        self.write("(");
29451        self.generate_expression(&e.this)?;
29452        if let Some(order) = &e.order {
29453            self.write_space();
29454            // Order is stored as an OrderBy expression
29455            if let Expression::OrderBy(ob) = order.as_ref() {
29456                self.write_keyword("ORDER BY");
29457                self.write_space();
29458                for (i, ord) in ob.expressions.iter().enumerate() {
29459                    if i > 0 {
29460                        self.write(", ");
29461                    }
29462                    self.generate_ordered(ord)?;
29463                }
29464            } else {
29465                // Fallback: generate the expression directly
29466                self.generate_expression(order)?;
29467            }
29468        }
29469        if let Some(null_handling) = &e.null_handling {
29470            self.write_space();
29471            self.generate_expression(null_handling)?;
29472        }
29473        if let Some(return_type) = &e.return_type {
29474            self.write_space();
29475            self.write_keyword("RETURNING");
29476            self.write_space();
29477            self.generate_expression(return_type)?;
29478        }
29479        if e.strict.is_some() {
29480            self.write_space();
29481            self.write_keyword("STRICT");
29482        }
29483        self.write(")");
29484        Ok(())
29485    }
29486
29487    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
29488        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
29489        self.write_keyword("JSON_OBJECTAGG");
29490        self.write("(");
29491        for (i, expr) in e.expressions.iter().enumerate() {
29492            if i > 0 {
29493                self.write(", ");
29494            }
29495            self.generate_expression(expr)?;
29496        }
29497        if let Some(null_handling) = &e.null_handling {
29498            self.write_space();
29499            self.generate_expression(null_handling)?;
29500        }
29501        if let Some(unique_keys) = &e.unique_keys {
29502            self.write_space();
29503            if let Expression::Boolean(b) = unique_keys.as_ref() {
29504                if b.value {
29505                    self.write_keyword("WITH UNIQUE KEYS");
29506                } else {
29507                    self.write_keyword("WITHOUT UNIQUE KEYS");
29508                }
29509            }
29510        }
29511        if let Some(return_type) = &e.return_type {
29512            self.write_space();
29513            self.write_keyword("RETURNING");
29514            self.write_space();
29515            self.generate_expression(return_type)?;
29516        }
29517        self.write(")");
29518        Ok(())
29519    }
29520
29521    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
29522        // JSON_ARRAY_APPEND(this, path, value, ...)
29523        self.write_keyword("JSON_ARRAY_APPEND");
29524        self.write("(");
29525        self.generate_expression(&e.this)?;
29526        for expr in &e.expressions {
29527            self.write(", ");
29528            self.generate_expression(expr)?;
29529        }
29530        self.write(")");
29531        Ok(())
29532    }
29533
29534    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
29535        // JSON_ARRAY_CONTAINS(this, expression)
29536        self.write_keyword("JSON_ARRAY_CONTAINS");
29537        self.write("(");
29538        self.generate_expression(&e.this)?;
29539        self.write(", ");
29540        self.generate_expression(&e.expression)?;
29541        self.write(")");
29542        Ok(())
29543    }
29544
29545    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
29546        // JSON_ARRAY_INSERT(this, path, value, ...)
29547        self.write_keyword("JSON_ARRAY_INSERT");
29548        self.write("(");
29549        self.generate_expression(&e.this)?;
29550        for expr in &e.expressions {
29551            self.write(", ");
29552            self.generate_expression(expr)?;
29553        }
29554        self.write(")");
29555        Ok(())
29556    }
29557
29558    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
29559        // JSONB_EXISTS(this, path)
29560        self.write_keyword("JSONB_EXISTS");
29561        self.write("(");
29562        self.generate_expression(&e.this)?;
29563        if let Some(path) = &e.path {
29564            self.write(", ");
29565            self.generate_expression(path)?;
29566        }
29567        self.write(")");
29568        Ok(())
29569    }
29570
29571    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
29572        // JSONB_EXTRACT_SCALAR(this, expression)
29573        self.write_keyword("JSONB_EXTRACT_SCALAR");
29574        self.write("(");
29575        self.generate_expression(&e.this)?;
29576        self.write(", ");
29577        self.generate_expression(&e.expression)?;
29578        self.write(")");
29579        Ok(())
29580    }
29581
29582    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
29583        // JSONB_OBJECT_AGG(this, expression)
29584        self.write_keyword("JSONB_OBJECT_AGG");
29585        self.write("(");
29586        self.generate_expression(&e.this)?;
29587        self.write(", ");
29588        self.generate_expression(&e.expression)?;
29589        self.write(")");
29590        Ok(())
29591    }
29592
29593    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
29594        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
29595        if let Some(nested_schema) = &e.nested_schema {
29596            self.write_keyword("NESTED");
29597            if let Some(path) = &e.path {
29598                self.write_space();
29599                self.write_keyword("PATH");
29600                self.write_space();
29601                self.generate_expression(path)?;
29602            }
29603            self.write_space();
29604            self.generate_expression(nested_schema)?;
29605        } else {
29606            if let Some(this) = &e.this {
29607                self.generate_expression(this)?;
29608            }
29609            if let Some(kind) = &e.kind {
29610                self.write_space();
29611                self.write(kind);
29612            }
29613            if let Some(path) = &e.path {
29614                self.write_space();
29615                self.write_keyword("PATH");
29616                self.write_space();
29617                self.generate_expression(path)?;
29618            }
29619            if e.ordinality.is_some() {
29620                self.write_keyword(" FOR ORDINALITY");
29621            }
29622        }
29623        Ok(())
29624    }
29625
29626    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
29627        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
29628        self.write_keyword("JSON_EXISTS");
29629        self.write("(");
29630        self.generate_expression(&e.this)?;
29631        if let Some(path) = &e.path {
29632            self.write(", ");
29633            self.generate_expression(path)?;
29634        }
29635        if let Some(passing) = &e.passing {
29636            self.write_space();
29637            self.write_keyword("PASSING");
29638            self.write_space();
29639            self.generate_expression(passing)?;
29640        }
29641        if let Some(on_condition) = &e.on_condition {
29642            self.write_space();
29643            self.generate_expression(on_condition)?;
29644        }
29645        self.write(")");
29646        Ok(())
29647    }
29648
29649    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
29650        self.generate_expression(&e.this)?;
29651        self.write(".:");
29652        // If the data type has nested type parameters (like Array(JSON), Map(String, Int)),
29653        // wrap the entire type string in double quotes.
29654        // This matches Python sqlglot's ClickHouse _json_cast_sql behavior.
29655        if Self::data_type_has_nested_expressions(&e.to) {
29656            // Generate the data type to a temporary string buffer, then wrap in quotes
29657            let saved = std::mem::take(&mut self.output);
29658            self.generate_data_type(&e.to)?;
29659            let type_sql = std::mem::replace(&mut self.output, saved);
29660            self.write("\"");
29661            self.write(&type_sql);
29662            self.write("\"");
29663        } else {
29664            self.generate_data_type(&e.to)?;
29665        }
29666        Ok(())
29667    }
29668
29669    /// Check if a DataType has nested type expressions (sub-types).
29670    /// This corresponds to Python sqlglot's `to.expressions` being non-empty.
29671    fn data_type_has_nested_expressions(dt: &DataType) -> bool {
29672        matches!(
29673            dt,
29674            DataType::Array { .. } | DataType::Map { .. } | DataType::Struct { .. }
29675        )
29676    }
29677
29678    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
29679        // JSON_EXTRACT_ARRAY(this, expression)
29680        self.write_keyword("JSON_EXTRACT_ARRAY");
29681        self.write("(");
29682        self.generate_expression(&e.this)?;
29683        if let Some(expr) = &e.expression {
29684            self.write(", ");
29685            self.generate_expression(expr)?;
29686        }
29687        self.write(")");
29688        Ok(())
29689    }
29690
29691    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
29692        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
29693        if let Some(option) = &e.option {
29694            self.generate_expression(option)?;
29695            self.write_space();
29696        }
29697        self.write_keyword("QUOTES");
29698        if e.scalar.is_some() {
29699            self.write_keyword(" SCALAR_ONLY");
29700        }
29701        Ok(())
29702    }
29703
29704    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
29705        // JSON_EXTRACT_SCALAR(this, expression)
29706        self.write_keyword("JSON_EXTRACT_SCALAR");
29707        self.write("(");
29708        self.generate_expression(&e.this)?;
29709        self.write(", ");
29710        self.generate_expression(&e.expression)?;
29711        self.write(")");
29712        Ok(())
29713    }
29714
29715    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
29716        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
29717        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
29718        // Otherwise output JSON_EXTRACT(this, expression)
29719        if e.variant_extract.is_some() {
29720            use crate::dialects::DialectType;
29721            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
29722                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
29723                // Keys that are not safe identifiers (contain hyphens, spaces, etc.) must use
29724                // bracket notation: c:["x-y"] instead of c:x-y
29725                self.generate_expression(&e.this)?;
29726                self.write(":");
29727                match e.expression.as_ref() {
29728                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
29729                        let Literal::String(s) = lit.as_ref() else {
29730                            unreachable!()
29731                        };
29732                        self.write_databricks_json_path(s);
29733                    }
29734                    _ => {
29735                        // Fallback: generate as-is (shouldn't happen in typical cases)
29736                        self.generate_expression(&e.expression)?;
29737                    }
29738                }
29739            } else {
29740                // Snowflake and others: use GET_PATH(col, 'path')
29741                self.write_keyword("GET_PATH");
29742                self.write("(");
29743                self.generate_expression(&e.this)?;
29744                self.write(", ");
29745                self.generate_expression(&e.expression)?;
29746                self.write(")");
29747            }
29748        } else {
29749            self.write_keyword("JSON_EXTRACT");
29750            self.write("(");
29751            self.generate_expression(&e.this)?;
29752            self.write(", ");
29753            self.generate_expression(&e.expression)?;
29754            for expr in &e.expressions {
29755                self.write(", ");
29756                self.generate_expression(expr)?;
29757            }
29758            self.write(")");
29759        }
29760        Ok(())
29761    }
29762
29763    /// Write a Databricks JSON colon-path, using bracket notation for keys
29764    /// that are not safe identifiers (e.g., contain hyphens, spaces, etc.)
29765    /// Safe identifier regex: ^[_a-zA-Z]\w*$
29766    fn write_databricks_json_path(&mut self, path: &str) {
29767        // If the path already starts with bracket notation (e.g., '["fr\'uit"]'),
29768        // it was already formatted by the parser - output as-is
29769        if path.starts_with("[\"") || path.starts_with("['") {
29770            self.write(path);
29771            return;
29772        }
29773        // Split the path into segments at '.' boundaries, but preserve bracket subscripts
29774        // e.g., "price.items[0].name" -> ["price", "items[0]", "name"]
29775        // e.g., "x-y" -> ["x-y"]
29776        let mut first = true;
29777        for segment in path.split('.') {
29778            if !first {
29779                self.write(".");
29780            }
29781            first = false;
29782            // Check if there's a bracket subscript in this segment: "items[0]"
29783            if let Some(bracket_pos) = segment.find('[') {
29784                let key = &segment[..bracket_pos];
29785                let subscript = &segment[bracket_pos..];
29786                if key.is_empty() {
29787                    // Bracket notation at start of segment (e.g., already formatted)
29788                    self.write(segment);
29789                } else if Self::is_safe_json_path_key(key) {
29790                    self.write(key);
29791                    self.write(subscript);
29792                } else {
29793                    self.write("[\"");
29794                    self.write(key);
29795                    self.write("\"]");
29796                    self.write(subscript);
29797                }
29798            } else if Self::is_safe_json_path_key(segment) {
29799                self.write(segment);
29800            } else {
29801                self.write("[\"");
29802                self.write(segment);
29803                self.write("\"]");
29804            }
29805        }
29806    }
29807
29808    /// Check if a JSON path key is a safe identifier that doesn't need bracket quoting.
29809    /// Matches Python sqlglot's SAFE_IDENTIFIER_RE: ^[_a-zA-Z]\w*$
29810    fn is_safe_json_path_key(key: &str) -> bool {
29811        if key.is_empty() {
29812            return false;
29813        }
29814        let mut chars = key.chars();
29815        let first = chars.next().unwrap();
29816        if first != '_' && !first.is_ascii_alphabetic() {
29817            return false;
29818        }
29819        chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
29820    }
29821
29822    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
29823        // Output: {expr} FORMAT JSON
29824        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
29825        if let Some(this) = &e.this {
29826            self.generate_expression(this)?;
29827            self.write_space();
29828        }
29829        self.write_keyword("FORMAT JSON");
29830        Ok(())
29831    }
29832
29833    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
29834        // key: value (for JSON objects)
29835        self.generate_expression(&e.this)?;
29836        self.write(": ");
29837        self.generate_expression(&e.expression)?;
29838        Ok(())
29839    }
29840
29841    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
29842        // JSON_KEYS(this, expression, expressions...)
29843        self.write_keyword("JSON_KEYS");
29844        self.write("(");
29845        self.generate_expression(&e.this)?;
29846        if let Some(expr) = &e.expression {
29847            self.write(", ");
29848            self.generate_expression(expr)?;
29849        }
29850        for expr in &e.expressions {
29851            self.write(", ");
29852            self.generate_expression(expr)?;
29853        }
29854        self.write(")");
29855        Ok(())
29856    }
29857
29858    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
29859        // JSON_KEYS(this, expression)
29860        self.write_keyword("JSON_KEYS");
29861        self.write("(");
29862        self.generate_expression(&e.this)?;
29863        if let Some(expr) = &e.expression {
29864            self.write(", ");
29865            self.generate_expression(expr)?;
29866        }
29867        self.write(")");
29868        Ok(())
29869    }
29870
29871    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
29872        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
29873        // The path components are concatenated without spaces
29874        let mut path_str = String::new();
29875        for expr in &e.expressions {
29876            match expr {
29877                Expression::JSONPathRoot(_) => {
29878                    path_str.push('$');
29879                }
29880                Expression::JSONPathKey(k) => {
29881                    // .key or ."key" (quote if key has special characters)
29882                    if let Expression::Literal(lit) = k.this.as_ref() {
29883                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
29884                            path_str.push('.');
29885                            // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
29886                            let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
29887                            if needs_quoting {
29888                                path_str.push('"');
29889                                path_str.push_str(s);
29890                                path_str.push('"');
29891                            } else {
29892                                path_str.push_str(s);
29893                            }
29894                        }
29895                    }
29896                }
29897                Expression::JSONPathSubscript(s) => {
29898                    // [index]
29899                    if let Expression::Literal(lit) = s.this.as_ref() {
29900                        if let crate::expressions::Literal::Number(n) = lit.as_ref() {
29901                            path_str.push('[');
29902                            path_str.push_str(n);
29903                            path_str.push(']');
29904                        }
29905                    }
29906                }
29907                _ => {
29908                    // For other path parts, try to generate them
29909                    let mut temp_gen = Self::with_arc_config(self.config.clone());
29910                    temp_gen.generate_expression(expr)?;
29911                    path_str.push_str(&temp_gen.output);
29912                }
29913            }
29914        }
29915        // Output as quoted string
29916        self.write("'");
29917        self.write(&path_str);
29918        self.write("'");
29919        Ok(())
29920    }
29921
29922    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
29923        // JSON path filter: ?(predicate)
29924        self.write("?(");
29925        self.generate_expression(&e.this)?;
29926        self.write(")");
29927        Ok(())
29928    }
29929
29930    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
29931        // JSON path key: .key or ["key"]
29932        self.write(".");
29933        self.generate_expression(&e.this)?;
29934        Ok(())
29935    }
29936
29937    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
29938        // JSON path recursive descent: ..
29939        self.write("..");
29940        if let Some(this) = &e.this {
29941            self.generate_expression(this)?;
29942        }
29943        Ok(())
29944    }
29945
29946    fn generate_json_path_root(&mut self) -> Result<()> {
29947        // JSON path root: $
29948        self.write("$");
29949        Ok(())
29950    }
29951
29952    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
29953        // JSON path script: (expression)
29954        self.write("(");
29955        self.generate_expression(&e.this)?;
29956        self.write(")");
29957        Ok(())
29958    }
29959
29960    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
29961        // JSON path selector: *
29962        self.generate_expression(&e.this)?;
29963        Ok(())
29964    }
29965
29966    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
29967        // JSON path slice: [start:end:step]
29968        self.write("[");
29969        if let Some(start) = &e.start {
29970            self.generate_expression(start)?;
29971        }
29972        self.write(":");
29973        if let Some(end) = &e.end {
29974            self.generate_expression(end)?;
29975        }
29976        if let Some(step) = &e.step {
29977            self.write(":");
29978            self.generate_expression(step)?;
29979        }
29980        self.write("]");
29981        Ok(())
29982    }
29983
29984    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
29985        // JSON path subscript: [index] or [*]
29986        self.write("[");
29987        self.generate_expression(&e.this)?;
29988        self.write("]");
29989        Ok(())
29990    }
29991
29992    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
29993        // JSON path union: [key1, key2, ...]
29994        self.write("[");
29995        for (i, expr) in e.expressions.iter().enumerate() {
29996            if i > 0 {
29997                self.write(", ");
29998            }
29999            self.generate_expression(expr)?;
30000        }
30001        self.write("]");
30002        Ok(())
30003    }
30004
30005    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
30006        // JSON_REMOVE(this, path1, path2, ...)
30007        self.write_keyword("JSON_REMOVE");
30008        self.write("(");
30009        self.generate_expression(&e.this)?;
30010        for expr in &e.expressions {
30011            self.write(", ");
30012            self.generate_expression(expr)?;
30013        }
30014        self.write(")");
30015        Ok(())
30016    }
30017
30018    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
30019        // COLUMNS(col1 type, col2 type, ...)
30020        // When pretty printing and content is too wide, format with each column on a separate line
30021        self.write_keyword("COLUMNS");
30022        self.write("(");
30023
30024        if self.config.pretty && !e.expressions.is_empty() {
30025            // First, generate all expressions into strings to check width
30026            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
30027            for expr in &e.expressions {
30028                let mut temp_gen = Generator::with_arc_config(self.config.clone());
30029                temp_gen.generate_expression(expr)?;
30030                expr_strings.push(temp_gen.output);
30031            }
30032
30033            // Check if total width exceeds max_text_width
30034            if self.too_wide(&expr_strings) {
30035                // Pretty print: each column on its own line
30036                self.write_newline();
30037                self.indent_level += 1;
30038                for (i, expr_str) in expr_strings.iter().enumerate() {
30039                    if i > 0 {
30040                        self.write(",");
30041                        self.write_newline();
30042                    }
30043                    self.write_indent();
30044                    self.write(expr_str);
30045                }
30046                self.write_newline();
30047                self.indent_level -= 1;
30048                self.write_indent();
30049            } else {
30050                // Compact: all on one line
30051                for (i, expr_str) in expr_strings.iter().enumerate() {
30052                    if i > 0 {
30053                        self.write(", ");
30054                    }
30055                    self.write(expr_str);
30056                }
30057            }
30058        } else {
30059            // Non-pretty mode: compact format
30060            for (i, expr) in e.expressions.iter().enumerate() {
30061                if i > 0 {
30062                    self.write(", ");
30063                }
30064                self.generate_expression(expr)?;
30065            }
30066        }
30067        self.write(")");
30068        Ok(())
30069    }
30070
30071    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
30072        // JSON_SET(this, path, value, ...)
30073        self.write_keyword("JSON_SET");
30074        self.write("(");
30075        self.generate_expression(&e.this)?;
30076        for expr in &e.expressions {
30077            self.write(", ");
30078            self.generate_expression(expr)?;
30079        }
30080        self.write(")");
30081        Ok(())
30082    }
30083
30084    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
30085        // JSON_STRIP_NULLS(this, expression)
30086        self.write_keyword("JSON_STRIP_NULLS");
30087        self.write("(");
30088        self.generate_expression(&e.this)?;
30089        if let Some(expr) = &e.expression {
30090            self.write(", ");
30091            self.generate_expression(expr)?;
30092        }
30093        self.write(")");
30094        Ok(())
30095    }
30096
30097    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
30098        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
30099        self.write_keyword("JSON_TABLE");
30100        self.write("(");
30101        self.generate_expression(&e.this)?;
30102        if let Some(path) = &e.path {
30103            self.write(", ");
30104            self.generate_expression(path)?;
30105        }
30106        if let Some(error_handling) = &e.error_handling {
30107            self.write_space();
30108            self.generate_expression(error_handling)?;
30109        }
30110        if let Some(empty_handling) = &e.empty_handling {
30111            self.write_space();
30112            self.generate_expression(empty_handling)?;
30113        }
30114        if let Some(schema) = &e.schema {
30115            self.write_space();
30116            self.generate_expression(schema)?;
30117        }
30118        self.write(")");
30119        Ok(())
30120    }
30121
30122    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
30123        // JSON_TYPE(this)
30124        self.write_keyword("JSON_TYPE");
30125        self.write("(");
30126        self.generate_expression(&e.this)?;
30127        self.write(")");
30128        Ok(())
30129    }
30130
30131    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
30132        // JSON_VALUE(this, path RETURNING type ON condition)
30133        self.write_keyword("JSON_VALUE");
30134        self.write("(");
30135        self.generate_expression(&e.this)?;
30136        if let Some(path) = &e.path {
30137            self.write(", ");
30138            self.generate_expression(path)?;
30139        }
30140        if let Some(returning) = &e.returning {
30141            self.write_space();
30142            self.write_keyword("RETURNING");
30143            self.write_space();
30144            self.generate_expression(returning)?;
30145        }
30146        if let Some(on_condition) = &e.on_condition {
30147            self.write_space();
30148            self.generate_expression(on_condition)?;
30149        }
30150        self.write(")");
30151        Ok(())
30152    }
30153
30154    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
30155        // JSON_VALUE_ARRAY(this)
30156        self.write_keyword("JSON_VALUE_ARRAY");
30157        self.write("(");
30158        self.generate_expression(&e.this)?;
30159        self.write(")");
30160        Ok(())
30161    }
30162
30163    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
30164        // JAROWINKLER_SIMILARITY(str1, str2)
30165        self.write_keyword("JAROWINKLER_SIMILARITY");
30166        self.write("(");
30167        self.generate_expression(&e.this)?;
30168        self.write(", ");
30169        self.generate_expression(&e.expression)?;
30170        self.write(")");
30171        Ok(())
30172    }
30173
30174    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
30175        // Python: this(expressions)
30176        self.generate_expression(&e.this)?;
30177        self.write("(");
30178        for (i, expr) in e.expressions.iter().enumerate() {
30179            if i > 0 {
30180                self.write(", ");
30181            }
30182            self.generate_expression(expr)?;
30183        }
30184        self.write(")");
30185        Ok(())
30186    }
30187
30188    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
30189        // Python: {no}{local}{dual}{before}{after}JOURNAL
30190        if e.no.is_some() {
30191            self.write_keyword("NO ");
30192        }
30193        if let Some(local) = &e.local {
30194            self.generate_expression(local)?;
30195            self.write_space();
30196        }
30197        if e.dual.is_some() {
30198            self.write_keyword("DUAL ");
30199        }
30200        if e.before.is_some() {
30201            self.write_keyword("BEFORE ");
30202        }
30203        if e.after.is_some() {
30204            self.write_keyword("AFTER ");
30205        }
30206        self.write_keyword("JOURNAL");
30207        Ok(())
30208    }
30209
30210    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
30211        // LANGUAGE language_name
30212        self.write_keyword("LANGUAGE");
30213        self.write_space();
30214        self.generate_expression(&e.this)?;
30215        Ok(())
30216    }
30217
30218    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
30219        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
30220        if e.view.is_some() {
30221            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
30222            self.write_keyword("LATERAL VIEW");
30223            if e.outer.is_some() {
30224                self.write_space();
30225                self.write_keyword("OUTER");
30226            }
30227            self.write_space();
30228            self.generate_expression(&e.this)?;
30229            if let Some(alias) = &e.alias {
30230                self.write_space();
30231                self.write(alias);
30232            }
30233        } else {
30234            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
30235            self.write_keyword("LATERAL");
30236            self.write_space();
30237            self.generate_expression(&e.this)?;
30238            if e.ordinality.is_some() {
30239                self.write_space();
30240                self.write_keyword("WITH ORDINALITY");
30241            }
30242            if let Some(alias) = &e.alias {
30243                self.write_space();
30244                self.write_keyword("AS");
30245                self.write_space();
30246                self.write(alias);
30247                if !e.column_aliases.is_empty() {
30248                    self.write("(");
30249                    for (i, col) in e.column_aliases.iter().enumerate() {
30250                        if i > 0 {
30251                            self.write(", ");
30252                        }
30253                        self.write(col);
30254                    }
30255                    self.write(")");
30256                }
30257            }
30258        }
30259        Ok(())
30260    }
30261
30262    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
30263        // Python: LIKE this [options]
30264        self.write_keyword("LIKE");
30265        self.write_space();
30266        self.generate_expression(&e.this)?;
30267        for expr in &e.expressions {
30268            self.write_space();
30269            self.generate_expression(expr)?;
30270        }
30271        Ok(())
30272    }
30273
30274    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
30275        self.write_keyword("LIMIT");
30276        self.write_space();
30277        self.write_limit_expr(&e.this)?;
30278        if e.percent {
30279            self.write_space();
30280            self.write_keyword("PERCENT");
30281        }
30282        // Emit any comments that were captured from before the LIMIT keyword
30283        for comment in &e.comments {
30284            self.write(" ");
30285            self.write_formatted_comment(comment);
30286        }
30287        Ok(())
30288    }
30289
30290    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
30291        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
30292        if e.percent.is_some() {
30293            self.write_keyword(" PERCENT");
30294        }
30295        if e.rows.is_some() {
30296            self.write_keyword(" ROWS");
30297        }
30298        if e.with_ties.is_some() {
30299            self.write_keyword(" WITH TIES");
30300        } else if e.rows.is_some() {
30301            self.write_keyword(" ONLY");
30302        }
30303        Ok(())
30304    }
30305
30306    fn generate_list(&mut self, e: &List) -> Result<()> {
30307        use crate::dialects::DialectType;
30308        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
30309
30310        // Check if this is a subquery-based list (LIST(SELECT ...))
30311        if e.expressions.len() == 1 {
30312            if let Expression::Select(_) = &e.expressions[0] {
30313                self.write_keyword("LIST");
30314                self.write("(");
30315                self.generate_expression(&e.expressions[0])?;
30316                self.write(")");
30317                return Ok(());
30318            }
30319        }
30320
30321        // For Materialize, output as LIST[expr, expr, ...]
30322        if is_materialize {
30323            self.write_keyword("LIST");
30324            self.write("[");
30325            for (i, expr) in e.expressions.iter().enumerate() {
30326                if i > 0 {
30327                    self.write(", ");
30328                }
30329                self.generate_expression(expr)?;
30330            }
30331            self.write("]");
30332        } else {
30333            // For other dialects, output as LIST(expr, expr, ...)
30334            self.write_keyword("LIST");
30335            self.write("(");
30336            for (i, expr) in e.expressions.iter().enumerate() {
30337                if i > 0 {
30338                    self.write(", ");
30339                }
30340                self.generate_expression(expr)?;
30341            }
30342            self.write(")");
30343        }
30344        Ok(())
30345    }
30346
30347    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
30348        // Check if this is a subquery-based map (MAP(SELECT ...))
30349        if let Expression::Select(_) = &*e.this {
30350            self.write_keyword("MAP");
30351            self.write("(");
30352            self.generate_expression(&e.this)?;
30353            self.write(")");
30354            return Ok(());
30355        }
30356
30357        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
30358
30359        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
30360        self.write_keyword("MAP");
30361        if is_duckdb {
30362            self.write(" {");
30363        } else {
30364            self.write("[");
30365        }
30366        if let Expression::Struct(s) = &*e.this {
30367            for (i, (_, expr)) in s.fields.iter().enumerate() {
30368                if i > 0 {
30369                    self.write(", ");
30370                }
30371                if let Expression::PropertyEQ(op) = expr {
30372                    self.generate_expression(&op.left)?;
30373                    if is_duckdb {
30374                        self.write(": ");
30375                    } else {
30376                        self.write(" => ");
30377                    }
30378                    self.generate_expression(&op.right)?;
30379                } else {
30380                    self.generate_expression(expr)?;
30381                }
30382            }
30383        }
30384        if is_duckdb {
30385            self.write("}");
30386        } else {
30387            self.write("]");
30388        }
30389        Ok(())
30390    }
30391
30392    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
30393        // Python: LOCALTIME or LOCALTIME(precision)
30394        self.write_keyword("LOCALTIME");
30395        if let Some(precision) = &e.this {
30396            self.write("(");
30397            self.generate_expression(precision)?;
30398            self.write(")");
30399        }
30400        Ok(())
30401    }
30402
30403    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
30404        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
30405        self.write_keyword("LOCALTIMESTAMP");
30406        if let Some(precision) = &e.this {
30407            self.write("(");
30408            self.generate_expression(precision)?;
30409            self.write(")");
30410        }
30411        Ok(())
30412    }
30413
30414    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
30415        // LOCATION 'path'
30416        self.write_keyword("LOCATION");
30417        self.write_space();
30418        self.generate_expression(&e.this)?;
30419        Ok(())
30420    }
30421
30422    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
30423        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
30424        if e.update.is_some() {
30425            if e.key.is_some() {
30426                self.write_keyword("FOR NO KEY UPDATE");
30427            } else {
30428                self.write_keyword("FOR UPDATE");
30429            }
30430        } else {
30431            if e.key.is_some() {
30432                self.write_keyword("FOR KEY SHARE");
30433            } else {
30434                self.write_keyword("FOR SHARE");
30435            }
30436        }
30437        if !e.expressions.is_empty() {
30438            self.write_keyword(" OF ");
30439            for (i, expr) in e.expressions.iter().enumerate() {
30440                if i > 0 {
30441                    self.write(", ");
30442                }
30443                self.generate_expression(expr)?;
30444            }
30445        }
30446        // Handle wait option following Python sqlglot convention:
30447        // - Boolean(true) -> NOWAIT
30448        // - Boolean(false) -> SKIP LOCKED
30449        // - Literal (number) -> WAIT n
30450        if let Some(wait) = &e.wait {
30451            match wait.as_ref() {
30452                Expression::Boolean(b) => {
30453                    if b.value {
30454                        self.write_keyword(" NOWAIT");
30455                    } else {
30456                        self.write_keyword(" SKIP LOCKED");
30457                    }
30458                }
30459                _ => {
30460                    // It's a literal (number), output WAIT n
30461                    self.write_keyword(" WAIT ");
30462                    self.generate_expression(wait)?;
30463                }
30464            }
30465        }
30466        Ok(())
30467    }
30468
30469    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
30470        // LOCK property
30471        self.write_keyword("LOCK");
30472        self.write_space();
30473        self.generate_expression(&e.this)?;
30474        Ok(())
30475    }
30476
30477    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
30478        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
30479        self.write_keyword("LOCKING");
30480        self.write_space();
30481        self.write(&e.kind);
30482        if let Some(this) = &e.this {
30483            self.write_space();
30484            self.generate_expression(this)?;
30485        }
30486        if let Some(for_or_in) = &e.for_or_in {
30487            self.write_space();
30488            self.generate_expression(for_or_in)?;
30489        }
30490        if let Some(lock_type) = &e.lock_type {
30491            self.write_space();
30492            self.generate_expression(lock_type)?;
30493        }
30494        if e.override_.is_some() {
30495            self.write_keyword(" OVERRIDE");
30496        }
30497        Ok(())
30498    }
30499
30500    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
30501        // this expression
30502        self.generate_expression(&e.this)?;
30503        self.write_space();
30504        self.generate_expression(&e.expression)?;
30505        Ok(())
30506    }
30507
30508    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
30509        // [NO] LOG
30510        if e.no.is_some() {
30511            self.write_keyword("NO ");
30512        }
30513        self.write_keyword("LOG");
30514        Ok(())
30515    }
30516
30517    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
30518        // MD5(this, expressions...)
30519        self.write_keyword("MD5");
30520        self.write("(");
30521        self.generate_expression(&e.this)?;
30522        for expr in &e.expressions {
30523            self.write(", ");
30524            self.generate_expression(expr)?;
30525        }
30526        self.write(")");
30527        Ok(())
30528    }
30529
30530    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
30531        // ML.FORECAST(model, [params])
30532        self.write_keyword("ML.FORECAST");
30533        self.write("(");
30534        self.generate_expression(&e.this)?;
30535        if let Some(expression) = &e.expression {
30536            self.write(", ");
30537            self.generate_expression(expression)?;
30538        }
30539        if let Some(params) = &e.params_struct {
30540            self.write(", ");
30541            self.generate_expression(params)?;
30542        }
30543        self.write(")");
30544        Ok(())
30545    }
30546
30547    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
30548        // ML.TRANSLATE(model, input, [params])
30549        self.write_keyword("ML.TRANSLATE");
30550        self.write("(");
30551        self.generate_expression(&e.this)?;
30552        self.write(", ");
30553        self.generate_expression(&e.expression)?;
30554        if let Some(params) = &e.params_struct {
30555            self.write(", ");
30556            self.generate_expression(params)?;
30557        }
30558        self.write(")");
30559        Ok(())
30560    }
30561
30562    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
30563        // MAKE_INTERVAL(years => x, months => y, ...)
30564        self.write_keyword("MAKE_INTERVAL");
30565        self.write("(");
30566        let mut first = true;
30567        if let Some(year) = &e.year {
30568            self.write("years => ");
30569            self.generate_expression(year)?;
30570            first = false;
30571        }
30572        if let Some(month) = &e.month {
30573            if !first {
30574                self.write(", ");
30575            }
30576            self.write("months => ");
30577            self.generate_expression(month)?;
30578            first = false;
30579        }
30580        if let Some(week) = &e.week {
30581            if !first {
30582                self.write(", ");
30583            }
30584            self.write("weeks => ");
30585            self.generate_expression(week)?;
30586            first = false;
30587        }
30588        if let Some(day) = &e.day {
30589            if !first {
30590                self.write(", ");
30591            }
30592            self.write("days => ");
30593            self.generate_expression(day)?;
30594            first = false;
30595        }
30596        if let Some(hour) = &e.hour {
30597            if !first {
30598                self.write(", ");
30599            }
30600            self.write("hours => ");
30601            self.generate_expression(hour)?;
30602            first = false;
30603        }
30604        if let Some(minute) = &e.minute {
30605            if !first {
30606                self.write(", ");
30607            }
30608            self.write("mins => ");
30609            self.generate_expression(minute)?;
30610            first = false;
30611        }
30612        if let Some(second) = &e.second {
30613            if !first {
30614                self.write(", ");
30615            }
30616            self.write("secs => ");
30617            self.generate_expression(second)?;
30618        }
30619        self.write(")");
30620        Ok(())
30621    }
30622
30623    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
30624        // MANHATTAN_DISTANCE(vector1, vector2)
30625        self.write_keyword("MANHATTAN_DISTANCE");
30626        self.write("(");
30627        self.generate_expression(&e.this)?;
30628        self.write(", ");
30629        self.generate_expression(&e.expression)?;
30630        self.write(")");
30631        Ok(())
30632    }
30633
30634    fn generate_map(&mut self, e: &Map) -> Result<()> {
30635        // MAP(key1, value1, key2, value2, ...)
30636        self.write_keyword("MAP");
30637        self.write("(");
30638        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
30639            if i > 0 {
30640                self.write(", ");
30641            }
30642            self.generate_expression(key)?;
30643            self.write(", ");
30644            self.generate_expression(value)?;
30645        }
30646        self.write(")");
30647        Ok(())
30648    }
30649
30650    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
30651        // MAP_CAT(map1, map2)
30652        self.write_keyword("MAP_CAT");
30653        self.write("(");
30654        self.generate_expression(&e.this)?;
30655        self.write(", ");
30656        self.generate_expression(&e.expression)?;
30657        self.write(")");
30658        Ok(())
30659    }
30660
30661    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
30662        // MAP_DELETE(map, key1, key2, ...)
30663        self.write_keyword("MAP_DELETE");
30664        self.write("(");
30665        self.generate_expression(&e.this)?;
30666        for expr in &e.expressions {
30667            self.write(", ");
30668            self.generate_expression(expr)?;
30669        }
30670        self.write(")");
30671        Ok(())
30672    }
30673
30674    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
30675        // MAP_INSERT(map, key, value, [update_flag])
30676        self.write_keyword("MAP_INSERT");
30677        self.write("(");
30678        self.generate_expression(&e.this)?;
30679        if let Some(key) = &e.key {
30680            self.write(", ");
30681            self.generate_expression(key)?;
30682        }
30683        if let Some(value) = &e.value {
30684            self.write(", ");
30685            self.generate_expression(value)?;
30686        }
30687        if let Some(update_flag) = &e.update_flag {
30688            self.write(", ");
30689            self.generate_expression(update_flag)?;
30690        }
30691        self.write(")");
30692        Ok(())
30693    }
30694
30695    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
30696        // MAP_PICK(map, key1, key2, ...)
30697        self.write_keyword("MAP_PICK");
30698        self.write("(");
30699        self.generate_expression(&e.this)?;
30700        for expr in &e.expressions {
30701            self.write(", ");
30702            self.generate_expression(expr)?;
30703        }
30704        self.write(")");
30705        Ok(())
30706    }
30707
30708    fn generate_masking_policy_column_constraint(
30709        &mut self,
30710        e: &MaskingPolicyColumnConstraint,
30711    ) -> Result<()> {
30712        // Python: MASKING POLICY name [USING (cols)]
30713        self.write_keyword("MASKING POLICY");
30714        self.write_space();
30715        self.generate_expression(&e.this)?;
30716        if !e.expressions.is_empty() {
30717            self.write_keyword(" USING");
30718            self.write(" (");
30719            for (i, expr) in e.expressions.iter().enumerate() {
30720                if i > 0 {
30721                    self.write(", ");
30722                }
30723                self.generate_expression(expr)?;
30724            }
30725            self.write(")");
30726        }
30727        Ok(())
30728    }
30729
30730    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
30731        if matches!(
30732            self.config.dialect,
30733            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
30734        ) {
30735            if e.expressions.len() > 1 {
30736                self.write("(");
30737            }
30738            for (i, expr) in e.expressions.iter().enumerate() {
30739                if i > 0 {
30740                    self.write_keyword(" OR ");
30741                }
30742                self.generate_expression(expr)?;
30743                self.write_space();
30744                self.write("@@");
30745                self.write_space();
30746                self.generate_expression(&e.this)?;
30747            }
30748            if e.expressions.len() > 1 {
30749                self.write(")");
30750            }
30751            return Ok(());
30752        }
30753
30754        // MATCH(columns) AGAINST(expr [modifier])
30755        self.write_keyword("MATCH");
30756        self.write("(");
30757        for (i, expr) in e.expressions.iter().enumerate() {
30758            if i > 0 {
30759                self.write(", ");
30760            }
30761            self.generate_expression(expr)?;
30762        }
30763        self.write(")");
30764        self.write_keyword(" AGAINST");
30765        self.write("(");
30766        self.generate_expression(&e.this)?;
30767        if let Some(modifier) = &e.modifier {
30768            self.write_space();
30769            self.generate_expression(modifier)?;
30770        }
30771        self.write(")");
30772        Ok(())
30773    }
30774
30775    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
30776        // Python: [window_frame] this
30777        if let Some(window_frame) = &e.window_frame {
30778            self.write(&format!("{:?}", window_frame).to_ascii_uppercase());
30779            self.write_space();
30780        }
30781        self.generate_expression(&e.this)?;
30782        Ok(())
30783    }
30784
30785    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
30786        // MATERIALIZED [this]
30787        self.write_keyword("MATERIALIZED");
30788        if let Some(this) = &e.this {
30789            self.write_space();
30790            self.generate_expression(this)?;
30791        }
30792        Ok(())
30793    }
30794
30795    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
30796        // MERGE INTO target USING source ON condition WHEN ...
30797        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
30798        if let Some(with_) = &e.with_ {
30799            self.generate_expression(with_)?;
30800            self.write_space();
30801        }
30802        self.write_keyword("MERGE INTO");
30803        self.write_space();
30804        self.generate_expression(&e.this)?;
30805
30806        // USING clause - newline before in pretty mode
30807        if self.config.pretty {
30808            self.write_newline();
30809            self.write_indent();
30810        } else {
30811            self.write_space();
30812        }
30813        self.write_keyword("USING");
30814        self.write_space();
30815        self.generate_expression(&e.using)?;
30816
30817        // ON clause - newline before in pretty mode
30818        if let Some(on) = &e.on {
30819            if self.config.pretty {
30820                self.write_newline();
30821                self.write_indent();
30822            } else {
30823                self.write_space();
30824            }
30825            self.write_keyword("ON");
30826            self.write_space();
30827            self.generate_expression(on)?;
30828        }
30829        // DuckDB USING (key_columns) clause
30830        if let Some(using_cond) = &e.using_cond {
30831            self.write_space();
30832            self.write_keyword("USING");
30833            self.write_space();
30834            self.write("(");
30835            // using_cond is a Tuple containing the column identifiers
30836            if let Expression::Tuple(tuple) = using_cond.as_ref() {
30837                for (i, col) in tuple.expressions.iter().enumerate() {
30838                    if i > 0 {
30839                        self.write(", ");
30840                    }
30841                    self.generate_expression(col)?;
30842                }
30843            } else {
30844                self.generate_expression(using_cond)?;
30845            }
30846            self.write(")");
30847        }
30848        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
30849        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
30850        if matches!(
30851            self.config.dialect,
30852            Some(crate::DialectType::PostgreSQL)
30853                | Some(crate::DialectType::Redshift)
30854                | Some(crate::DialectType::Trino)
30855                | Some(crate::DialectType::Presto)
30856                | Some(crate::DialectType::Athena)
30857        ) {
30858            let mut names = Vec::new();
30859            match e.this.as_ref() {
30860                Expression::Alias(a) => {
30861                    // e.g., "x AS z" -> strip both "x" and "z"
30862                    if let Expression::Table(t) = &a.this {
30863                        names.push(t.name.name.clone());
30864                    } else if let Expression::Identifier(id) = &a.this {
30865                        names.push(id.name.clone());
30866                    }
30867                    names.push(a.alias.name.clone());
30868                }
30869                Expression::Table(t) => {
30870                    names.push(t.name.name.clone());
30871                }
30872                Expression::Identifier(id) => {
30873                    names.push(id.name.clone());
30874                }
30875                _ => {}
30876            }
30877            self.merge_strip_qualifiers = names;
30878        }
30879
30880        // WHEN clauses - newline before each in pretty mode
30881        if let Some(whens) = &e.whens {
30882            if self.config.pretty {
30883                self.write_newline();
30884                self.write_indent();
30885            } else {
30886                self.write_space();
30887            }
30888            self.generate_expression(whens)?;
30889        }
30890
30891        // Restore merge_strip_qualifiers
30892        self.merge_strip_qualifiers = saved_merge_strip;
30893
30894        // OUTPUT/RETURNING clause - newline before in pretty mode
30895        if let Some(returning) = &e.returning {
30896            if self.config.pretty {
30897                self.write_newline();
30898                self.write_indent();
30899            } else {
30900                self.write_space();
30901            }
30902            self.generate_expression(returning)?;
30903        }
30904        Ok(())
30905    }
30906
30907    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
30908        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
30909        if e.no.is_some() {
30910            self.write_keyword("NO MERGEBLOCKRATIO");
30911        } else if e.default.is_some() {
30912            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
30913        } else {
30914            self.write_keyword("MERGEBLOCKRATIO");
30915            self.write("=");
30916            if let Some(this) = &e.this {
30917                self.generate_expression(this)?;
30918            }
30919            if e.percent.is_some() {
30920                self.write_keyword(" PERCENT");
30921            }
30922        }
30923        Ok(())
30924    }
30925
30926    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
30927        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
30928        self.write_keyword("TTL");
30929        let pretty_clickhouse = self.config.pretty
30930            && matches!(
30931                self.config.dialect,
30932                Some(crate::dialects::DialectType::ClickHouse)
30933            );
30934
30935        if pretty_clickhouse {
30936            self.write_newline();
30937            self.indent_level += 1;
30938            for (i, expr) in e.expressions.iter().enumerate() {
30939                if i > 0 {
30940                    self.write(",");
30941                    self.write_newline();
30942                }
30943                self.write_indent();
30944                self.generate_expression(expr)?;
30945            }
30946            self.indent_level -= 1;
30947        } else {
30948            self.write_space();
30949            for (i, expr) in e.expressions.iter().enumerate() {
30950                if i > 0 {
30951                    self.write(", ");
30952                }
30953                self.generate_expression(expr)?;
30954            }
30955        }
30956
30957        if let Some(where_) = &e.where_ {
30958            if pretty_clickhouse {
30959                self.write_newline();
30960                if let Expression::Where(w) = where_.as_ref() {
30961                    self.write_indent();
30962                    self.write_keyword("WHERE");
30963                    self.write_newline();
30964                    self.indent_level += 1;
30965                    self.write_indent();
30966                    self.generate_expression(&w.this)?;
30967                    self.indent_level -= 1;
30968                } else {
30969                    self.write_indent();
30970                    self.generate_expression(where_)?;
30971                }
30972            } else {
30973                self.write_space();
30974                self.generate_expression(where_)?;
30975            }
30976        }
30977        if let Some(group) = &e.group {
30978            if pretty_clickhouse {
30979                self.write_newline();
30980                if let Expression::Group(g) = group.as_ref() {
30981                    self.write_indent();
30982                    self.write_keyword("GROUP BY");
30983                    self.write_newline();
30984                    self.indent_level += 1;
30985                    for (i, expr) in g.expressions.iter().enumerate() {
30986                        if i > 0 {
30987                            self.write(",");
30988                            self.write_newline();
30989                        }
30990                        self.write_indent();
30991                        self.generate_expression(expr)?;
30992                    }
30993                    self.indent_level -= 1;
30994                } else {
30995                    self.write_indent();
30996                    self.generate_expression(group)?;
30997                }
30998            } else {
30999                self.write_space();
31000                self.generate_expression(group)?;
31001            }
31002        }
31003        if let Some(aggregates) = &e.aggregates {
31004            if pretty_clickhouse {
31005                self.write_newline();
31006                self.write_indent();
31007                self.write_keyword("SET");
31008                self.write_newline();
31009                self.indent_level += 1;
31010                if let Expression::Tuple(t) = aggregates.as_ref() {
31011                    for (i, agg) in t.expressions.iter().enumerate() {
31012                        if i > 0 {
31013                            self.write(",");
31014                            self.write_newline();
31015                        }
31016                        self.write_indent();
31017                        self.generate_expression(agg)?;
31018                    }
31019                } else {
31020                    self.write_indent();
31021                    self.generate_expression(aggregates)?;
31022                }
31023                self.indent_level -= 1;
31024            } else {
31025                self.write_space();
31026                self.write_keyword("SET");
31027                self.write_space();
31028                self.generate_expression(aggregates)?;
31029            }
31030        }
31031        Ok(())
31032    }
31033
31034    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
31035        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
31036        self.generate_expression(&e.this)?;
31037        if e.delete.is_some() {
31038            self.write_keyword(" DELETE");
31039        }
31040        if let Some(recompress) = &e.recompress {
31041            self.write_keyword(" RECOMPRESS ");
31042            self.generate_expression(recompress)?;
31043        }
31044        if let Some(to_disk) = &e.to_disk {
31045            self.write_keyword(" TO DISK ");
31046            self.generate_expression(to_disk)?;
31047        }
31048        if let Some(to_volume) = &e.to_volume {
31049            self.write_keyword(" TO VOLUME ");
31050            self.generate_expression(to_volume)?;
31051        }
31052        Ok(())
31053    }
31054
31055    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
31056        // MINHASH(this, expressions...)
31057        self.write_keyword("MINHASH");
31058        self.write("(");
31059        self.generate_expression(&e.this)?;
31060        for expr in &e.expressions {
31061            self.write(", ");
31062            self.generate_expression(expr)?;
31063        }
31064        self.write(")");
31065        Ok(())
31066    }
31067
31068    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
31069        // model!attribute - Snowflake syntax
31070        self.generate_expression(&e.this)?;
31071        self.write("!");
31072        self.generate_expression(&e.expression)?;
31073        Ok(())
31074    }
31075
31076    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
31077        // MONTHNAME(this)
31078        self.write_keyword("MONTHNAME");
31079        self.write("(");
31080        self.generate_expression(&e.this)?;
31081        self.write(")");
31082        Ok(())
31083    }
31084
31085    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
31086        // Output leading comments
31087        for comment in &e.leading_comments {
31088            self.write_formatted_comment(comment);
31089            if self.config.pretty {
31090                self.write_newline();
31091                self.write_indent();
31092            } else {
31093                self.write_space();
31094            }
31095        }
31096        // Python: INSERT kind expressions source
31097        self.write_keyword("INSERT");
31098        self.write_space();
31099        self.write(&e.kind);
31100        if self.config.pretty {
31101            self.indent_level += 1;
31102            for expr in &e.expressions {
31103                self.write_newline();
31104                self.write_indent();
31105                self.generate_expression(expr)?;
31106            }
31107            self.indent_level -= 1;
31108        } else {
31109            for expr in &e.expressions {
31110                self.write_space();
31111                self.generate_expression(expr)?;
31112            }
31113        }
31114        if let Some(source) = &e.source {
31115            if self.config.pretty {
31116                self.write_newline();
31117                self.write_indent();
31118            } else {
31119                self.write_space();
31120            }
31121            self.generate_expression(source)?;
31122        }
31123        Ok(())
31124    }
31125
31126    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
31127        // Python: NEXT VALUE FOR this [OVER (order)]
31128        self.write_keyword("NEXT VALUE FOR");
31129        self.write_space();
31130        self.generate_expression(&e.this)?;
31131        if let Some(order) = &e.order {
31132            self.write_space();
31133            self.write_keyword("OVER");
31134            self.write(" (");
31135            self.generate_expression(order)?;
31136            self.write(")");
31137        }
31138        Ok(())
31139    }
31140
31141    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
31142        // NORMAL(mean, stddev, gen)
31143        self.write_keyword("NORMAL");
31144        self.write("(");
31145        self.generate_expression(&e.this)?;
31146        if let Some(stddev) = &e.stddev {
31147            self.write(", ");
31148            self.generate_expression(stddev)?;
31149        }
31150        if let Some(gen) = &e.gen {
31151            self.write(", ");
31152            self.generate_expression(gen)?;
31153        }
31154        self.write(")");
31155        Ok(())
31156    }
31157
31158    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
31159        // NORMALIZE(this, form) or CASEFOLD version
31160        if e.is_casefold.is_some() {
31161            self.write_keyword("NORMALIZE_AND_CASEFOLD");
31162        } else {
31163            self.write_keyword("NORMALIZE");
31164        }
31165        self.write("(");
31166        self.generate_expression(&e.this)?;
31167        if let Some(form) = &e.form {
31168            self.write(", ");
31169            self.generate_expression(form)?;
31170        }
31171        self.write(")");
31172        Ok(())
31173    }
31174
31175    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
31176        // Python: [NOT ]NULL
31177        if e.allow_null.is_none() {
31178            self.write_keyword("NOT ");
31179        }
31180        self.write_keyword("NULL");
31181        Ok(())
31182    }
31183
31184    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
31185        // NULLIF(this, expression)
31186        self.write_keyword("NULLIF");
31187        self.write("(");
31188        self.generate_expression(&e.this)?;
31189        self.write(", ");
31190        self.generate_expression(&e.expression)?;
31191        self.write(")");
31192        Ok(())
31193    }
31194
31195    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
31196        // FORMAT(this, format, culture)
31197        self.write_keyword("FORMAT");
31198        self.write("(");
31199        self.generate_expression(&e.this)?;
31200        self.write(", '");
31201        self.write(&e.format);
31202        self.write("'");
31203        if let Some(culture) = &e.culture {
31204            self.write(", ");
31205            self.generate_expression(culture)?;
31206        }
31207        self.write(")");
31208        Ok(())
31209    }
31210
31211    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
31212        // OBJECT_AGG(key, value)
31213        self.write_keyword("OBJECT_AGG");
31214        self.write("(");
31215        self.generate_expression(&e.this)?;
31216        self.write(", ");
31217        self.generate_expression(&e.expression)?;
31218        self.write(")");
31219        Ok(())
31220    }
31221
31222    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
31223        // Python: Just returns the name
31224        self.generate_expression(&e.this)?;
31225        Ok(())
31226    }
31227
31228    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
31229        // OBJECT_INSERT(obj, key, value, [update_flag])
31230        self.write_keyword("OBJECT_INSERT");
31231        self.write("(");
31232        self.generate_expression(&e.this)?;
31233        if let Some(key) = &e.key {
31234            self.write(", ");
31235            self.generate_expression(key)?;
31236        }
31237        if let Some(value) = &e.value {
31238            self.write(", ");
31239            self.generate_expression(value)?;
31240        }
31241        if let Some(update_flag) = &e.update_flag {
31242            self.write(", ");
31243            self.generate_expression(update_flag)?;
31244        }
31245        self.write(")");
31246        Ok(())
31247    }
31248
31249    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
31250        // OFFSET value [ROW|ROWS]
31251        self.write_keyword("OFFSET");
31252        self.write_space();
31253        self.generate_expression(&e.this)?;
31254        // Output ROWS keyword only for TSQL/Oracle targets
31255        if e.rows == Some(true)
31256            && matches!(
31257                self.config.dialect,
31258                Some(crate::dialects::DialectType::TSQL)
31259                    | Some(crate::dialects::DialectType::Oracle)
31260            )
31261        {
31262            self.write_space();
31263            self.write_keyword("ROWS");
31264        }
31265        Ok(())
31266    }
31267
31268    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
31269        // QUALIFY condition (Snowflake/BigQuery)
31270        self.write_keyword("QUALIFY");
31271        self.write_space();
31272        self.generate_expression(&e.this)?;
31273        Ok(())
31274    }
31275
31276    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
31277        // ON CLUSTER cluster_name
31278        self.write_keyword("ON CLUSTER");
31279        self.write_space();
31280        self.generate_expression(&e.this)?;
31281        Ok(())
31282    }
31283
31284    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
31285        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
31286        self.write_keyword("ON COMMIT");
31287        if e.delete.is_some() {
31288            self.write_keyword(" DELETE ROWS");
31289        } else {
31290            self.write_keyword(" PRESERVE ROWS");
31291        }
31292        Ok(())
31293    }
31294
31295    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
31296        // Python: error/empty/null handling
31297        if let Some(empty) = &e.empty {
31298            self.generate_expression(empty)?;
31299            self.write_keyword(" ON EMPTY");
31300        }
31301        if let Some(error) = &e.error {
31302            if e.empty.is_some() {
31303                self.write_space();
31304            }
31305            self.generate_expression(error)?;
31306            self.write_keyword(" ON ERROR");
31307        }
31308        if let Some(null) = &e.null {
31309            if e.empty.is_some() || e.error.is_some() {
31310                self.write_space();
31311            }
31312            self.generate_expression(null)?;
31313            self.write_keyword(" ON NULL");
31314        }
31315        Ok(())
31316    }
31317
31318    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
31319        // Materialize doesn't support ON CONFLICT - skip entirely
31320        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
31321            return Ok(());
31322        }
31323        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
31324        if e.duplicate.is_some() {
31325            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
31326            self.write_keyword("ON DUPLICATE KEY UPDATE");
31327            for (i, expr) in e.expressions.iter().enumerate() {
31328                if i > 0 {
31329                    self.write(",");
31330                }
31331                self.write_space();
31332                self.generate_expression(expr)?;
31333            }
31334            return Ok(());
31335        } else {
31336            self.write_keyword("ON CONFLICT");
31337        }
31338        if let Some(constraint) = &e.constraint {
31339            self.write_keyword(" ON CONSTRAINT ");
31340            self.generate_expression(constraint)?;
31341        }
31342        if let Some(conflict_keys) = &e.conflict_keys {
31343            // conflict_keys can be a Tuple containing expressions
31344            if let Expression::Tuple(t) = conflict_keys.as_ref() {
31345                self.write("(");
31346                for (i, expr) in t.expressions.iter().enumerate() {
31347                    if i > 0 {
31348                        self.write(", ");
31349                    }
31350                    self.generate_expression(expr)?;
31351                }
31352                self.write(")");
31353            } else {
31354                self.write("(");
31355                self.generate_expression(conflict_keys)?;
31356                self.write(")");
31357            }
31358        }
31359        if let Some(index_predicate) = &e.index_predicate {
31360            self.write_keyword(" WHERE ");
31361            self.generate_expression(index_predicate)?;
31362        }
31363        if let Some(action) = &e.action {
31364            // Check if action is "NOTHING" or an UPDATE set
31365            if let Expression::Identifier(id) = action.as_ref() {
31366                if id.name.eq_ignore_ascii_case("NOTHING") {
31367                    self.write_keyword(" DO NOTHING");
31368                } else {
31369                    self.write_keyword(" DO ");
31370                    self.generate_expression(action)?;
31371                }
31372            } else if let Expression::Tuple(t) = action.as_ref() {
31373                // DO UPDATE SET col1 = val1, col2 = val2
31374                self.write_keyword(" DO UPDATE SET ");
31375                for (i, expr) in t.expressions.iter().enumerate() {
31376                    if i > 0 {
31377                        self.write(", ");
31378                    }
31379                    self.generate_expression(expr)?;
31380                }
31381            } else {
31382                self.write_keyword(" DO ");
31383                self.generate_expression(action)?;
31384            }
31385        }
31386        // WHERE clause for the UPDATE action
31387        if let Some(where_) = &e.where_ {
31388            self.write_keyword(" WHERE ");
31389            self.generate_expression(where_)?;
31390        }
31391        Ok(())
31392    }
31393
31394    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
31395        // ON property_value
31396        self.write_keyword("ON");
31397        self.write_space();
31398        self.generate_expression(&e.this)?;
31399        Ok(())
31400    }
31401
31402    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
31403        // Python: this expression (e.g., column opclass)
31404        self.generate_expression(&e.this)?;
31405        self.write_space();
31406        self.generate_expression(&e.expression)?;
31407        Ok(())
31408    }
31409
31410    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
31411        // Python: OPENJSON(this[, path]) [WITH (columns)]
31412        self.write_keyword("OPENJSON");
31413        self.write("(");
31414        self.generate_expression(&e.this)?;
31415        if let Some(path) = &e.path {
31416            self.write(", ");
31417            self.generate_expression(path)?;
31418        }
31419        self.write(")");
31420        if !e.expressions.is_empty() {
31421            self.write_keyword(" WITH");
31422            if self.config.pretty {
31423                self.write(" (\n");
31424                self.indent_level += 2;
31425                for (i, expr) in e.expressions.iter().enumerate() {
31426                    if i > 0 {
31427                        self.write(",\n");
31428                    }
31429                    self.write_indent();
31430                    self.generate_expression(expr)?;
31431                }
31432                self.write("\n");
31433                self.indent_level -= 2;
31434                self.write(")");
31435            } else {
31436                self.write(" (");
31437                for (i, expr) in e.expressions.iter().enumerate() {
31438                    if i > 0 {
31439                        self.write(", ");
31440                    }
31441                    self.generate_expression(expr)?;
31442                }
31443                self.write(")");
31444            }
31445        }
31446        Ok(())
31447    }
31448
31449    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
31450        // Python: this kind [path] [AS JSON]
31451        self.generate_expression(&e.this)?;
31452        self.write_space();
31453        // Use parsed data_type if available, otherwise fall back to kind string
31454        if let Some(ref dt) = e.data_type {
31455            self.generate_data_type(dt)?;
31456        } else if !e.kind.is_empty() {
31457            self.write(&e.kind);
31458        }
31459        if let Some(path) = &e.path {
31460            self.write_space();
31461            self.generate_expression(path)?;
31462        }
31463        if e.as_json.is_some() {
31464            self.write_keyword(" AS JSON");
31465        }
31466        Ok(())
31467    }
31468
31469    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
31470        // this OPERATOR(op) expression
31471        self.generate_expression(&e.this)?;
31472        self.write_space();
31473        if let Some(op) = &e.operator {
31474            self.write_keyword("OPERATOR");
31475            self.write("(");
31476            self.generate_expression(op)?;
31477            self.write(")");
31478        }
31479        // Emit inline comments between OPERATOR() and the RHS
31480        for comment in &e.comments {
31481            self.write_space();
31482            self.write_formatted_comment(comment);
31483        }
31484        self.write_space();
31485        self.generate_expression(&e.expression)?;
31486        Ok(())
31487    }
31488
31489    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
31490        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
31491        self.write_keyword("ORDER BY");
31492        let pretty_clickhouse_single_paren = self.config.pretty
31493            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
31494            && e.expressions.len() == 1
31495            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
31496        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
31497            && e.expressions.len() == 1
31498            && matches!(e.expressions[0].this, Expression::Tuple(_))
31499            && !e.expressions[0].desc
31500            && e.expressions[0].nulls_first.is_none();
31501
31502        if pretty_clickhouse_single_paren {
31503            self.write_space();
31504            if let Expression::Paren(p) = &e.expressions[0].this {
31505                self.write("(");
31506                self.write_newline();
31507                self.indent_level += 1;
31508                self.write_indent();
31509                self.generate_expression(&p.this)?;
31510                self.indent_level -= 1;
31511                self.write_newline();
31512                self.write(")");
31513            }
31514            return Ok(());
31515        }
31516
31517        if clickhouse_single_tuple {
31518            self.write_space();
31519            if let Expression::Tuple(t) = &e.expressions[0].this {
31520                self.write("(");
31521                for (i, expr) in t.expressions.iter().enumerate() {
31522                    if i > 0 {
31523                        self.write(", ");
31524                    }
31525                    self.generate_expression(expr)?;
31526                }
31527                self.write(")");
31528            }
31529            return Ok(());
31530        }
31531
31532        self.write_space();
31533        for (i, ordered) in e.expressions.iter().enumerate() {
31534            if i > 0 {
31535                self.write(", ");
31536            }
31537            self.generate_expression(&ordered.this)?;
31538            if ordered.desc {
31539                self.write_space();
31540                self.write_keyword("DESC");
31541            } else if ordered.explicit_asc {
31542                self.write_space();
31543                self.write_keyword("ASC");
31544            }
31545            if let Some(nulls_first) = ordered.nulls_first {
31546                // In Dremio, NULLS LAST is the default, so skip generating it
31547                let skip_nulls_last =
31548                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
31549                if !skip_nulls_last {
31550                    self.write_space();
31551                    self.write_keyword("NULLS");
31552                    self.write_space();
31553                    if nulls_first {
31554                        self.write_keyword("FIRST");
31555                    } else {
31556                        self.write_keyword("LAST");
31557                    }
31558                }
31559            }
31560        }
31561        Ok(())
31562    }
31563
31564    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
31565        // OUTPUT(model)
31566        self.write_keyword("OUTPUT");
31567        self.write("(");
31568        if self.config.pretty {
31569            self.indent_level += 1;
31570            self.write_newline();
31571            self.write_indent();
31572            self.generate_expression(&e.this)?;
31573            self.indent_level -= 1;
31574            self.write_newline();
31575        } else {
31576            self.generate_expression(&e.this)?;
31577        }
31578        self.write(")");
31579        Ok(())
31580    }
31581
31582    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
31583        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
31584        self.write_keyword("TRUNCATE");
31585        if let Some(this) = &e.this {
31586            self.write_space();
31587            self.generate_expression(this)?;
31588        }
31589        if e.with_count.is_some() {
31590            self.write_keyword(" WITH COUNT");
31591        } else {
31592            self.write_keyword(" WITHOUT COUNT");
31593        }
31594        Ok(())
31595    }
31596
31597    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
31598        // Python: name(expressions)(params)
31599        self.generate_expression(&e.this)?;
31600        self.write("(");
31601        for (i, expr) in e.expressions.iter().enumerate() {
31602            if i > 0 {
31603                self.write(", ");
31604            }
31605            self.generate_expression(expr)?;
31606        }
31607        self.write(")(");
31608        for (i, param) in e.params.iter().enumerate() {
31609            if i > 0 {
31610                self.write(", ");
31611            }
31612            self.generate_expression(param)?;
31613        }
31614        self.write(")");
31615        Ok(())
31616    }
31617
31618    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
31619        // PARSE_DATETIME(format, this) or similar
31620        self.write_keyword("PARSE_DATETIME");
31621        self.write("(");
31622        if let Some(format) = &e.format {
31623            self.write("'");
31624            self.write(format);
31625            self.write("', ");
31626        }
31627        self.generate_expression(&e.this)?;
31628        if let Some(zone) = &e.zone {
31629            self.write(", ");
31630            self.generate_expression(zone)?;
31631        }
31632        self.write(")");
31633        Ok(())
31634    }
31635
31636    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
31637        // PARSE_IP(this, type, permissive)
31638        self.write_keyword("PARSE_IP");
31639        self.write("(");
31640        self.generate_expression(&e.this)?;
31641        if let Some(type_) = &e.type_ {
31642            self.write(", ");
31643            self.generate_expression(type_)?;
31644        }
31645        if let Some(permissive) = &e.permissive {
31646            self.write(", ");
31647            self.generate_expression(permissive)?;
31648        }
31649        self.write(")");
31650        Ok(())
31651    }
31652
31653    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
31654        // PARSE_JSON(this, [expression])
31655        self.write_keyword("PARSE_JSON");
31656        self.write("(");
31657        self.generate_expression(&e.this)?;
31658        if let Some(expression) = &e.expression {
31659            self.write(", ");
31660            self.generate_expression(expression)?;
31661        }
31662        self.write(")");
31663        Ok(())
31664    }
31665
31666    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
31667        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
31668        self.write_keyword("PARSE_TIME");
31669        self.write("(");
31670        self.write(&format!("'{}'", e.format));
31671        self.write(", ");
31672        self.generate_expression(&e.this)?;
31673        self.write(")");
31674        Ok(())
31675    }
31676
31677    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
31678        // PARSE_URL(this, [part_to_extract], [key], [permissive])
31679        self.write_keyword("PARSE_URL");
31680        self.write("(");
31681        self.generate_expression(&e.this)?;
31682        if let Some(part) = &e.part_to_extract {
31683            self.write(", ");
31684            self.generate_expression(part)?;
31685        }
31686        if let Some(key) = &e.key {
31687            self.write(", ");
31688            self.generate_expression(key)?;
31689        }
31690        if let Some(permissive) = &e.permissive {
31691            self.write(", ");
31692            self.generate_expression(permissive)?;
31693        }
31694        self.write(")");
31695        Ok(())
31696    }
31697
31698    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
31699        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
31700        if e.subpartition {
31701            self.write_keyword("SUBPARTITION");
31702        } else {
31703            self.write_keyword("PARTITION");
31704        }
31705        self.write("(");
31706        for (i, expr) in e.expressions.iter().enumerate() {
31707            if i > 0 {
31708                self.write(", ");
31709            }
31710            self.generate_expression(expr)?;
31711        }
31712        self.write(")");
31713        Ok(())
31714    }
31715
31716    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
31717        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
31718        if let Some(this) = &e.this {
31719            if let Some(expression) = &e.expression {
31720                // WITH (MODULUS this, REMAINDER expression)
31721                self.write_keyword("WITH");
31722                self.write(" (");
31723                self.write_keyword("MODULUS");
31724                self.write_space();
31725                self.generate_expression(this)?;
31726                self.write(", ");
31727                self.write_keyword("REMAINDER");
31728                self.write_space();
31729                self.generate_expression(expression)?;
31730                self.write(")");
31731            } else {
31732                // IN (this) - this could be a list
31733                self.write_keyword("IN");
31734                self.write(" (");
31735                self.generate_partition_bound_values(this)?;
31736                self.write(")");
31737            }
31738        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
31739            // FROM (from_expressions) TO (to_expressions)
31740            self.write_keyword("FROM");
31741            self.write(" (");
31742            self.generate_partition_bound_values(from)?;
31743            self.write(") ");
31744            self.write_keyword("TO");
31745            self.write(" (");
31746            self.generate_partition_bound_values(to)?;
31747            self.write(")");
31748        }
31749        Ok(())
31750    }
31751
31752    /// Generate partition bound values - handles Tuple expressions by outputting
31753    /// contents without wrapping parens (since caller provides the parens)
31754    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
31755        if let Expression::Tuple(t) = expr {
31756            for (i, e) in t.expressions.iter().enumerate() {
31757                if i > 0 {
31758                    self.write(", ");
31759                }
31760                self.generate_expression(e)?;
31761            }
31762            Ok(())
31763        } else {
31764            self.generate_expression(expr)
31765        }
31766    }
31767
31768    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
31769        // PARTITION BY LIST (partition_expressions) (create_expressions)
31770        self.write_keyword("PARTITION BY LIST");
31771        if let Some(partition_exprs) = &e.partition_expressions {
31772            self.write(" (");
31773            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
31774            self.generate_doris_partition_expressions(partition_exprs)?;
31775            self.write(")");
31776        }
31777        if let Some(create_exprs) = &e.create_expressions {
31778            self.write(" (");
31779            // Unwrap Tuple for partition definitions
31780            self.generate_doris_partition_definitions(create_exprs)?;
31781            self.write(")");
31782        }
31783        Ok(())
31784    }
31785
31786    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
31787        // PARTITION BY RANGE (partition_expressions) (create_expressions)
31788        self.write_keyword("PARTITION BY RANGE");
31789        if let Some(partition_exprs) = &e.partition_expressions {
31790            self.write(" (");
31791            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
31792            self.generate_doris_partition_expressions(partition_exprs)?;
31793            self.write(")");
31794        }
31795        if let Some(create_exprs) = &e.create_expressions {
31796            self.write(" (");
31797            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
31798            self.generate_doris_partition_definitions(create_exprs)?;
31799            self.write(")");
31800        }
31801        Ok(())
31802    }
31803
31804    /// Generate Doris partition column expressions (unwrap Tuple)
31805    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
31806        if let Expression::Tuple(t) = expr {
31807            for (i, e) in t.expressions.iter().enumerate() {
31808                if i > 0 {
31809                    self.write(", ");
31810                }
31811                self.generate_expression(e)?;
31812            }
31813        } else {
31814            self.generate_expression(expr)?;
31815        }
31816        Ok(())
31817    }
31818
31819    /// Generate Doris partition definitions (comma-separated Partition expressions)
31820    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
31821        match expr {
31822            Expression::Tuple(t) => {
31823                // Multiple partitions, comma-separated
31824                for (i, part) in t.expressions.iter().enumerate() {
31825                    if i > 0 {
31826                        self.write(", ");
31827                    }
31828                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
31829                    if let Expression::Partition(p) = part {
31830                        for (j, inner) in p.expressions.iter().enumerate() {
31831                            if j > 0 {
31832                                self.write(", ");
31833                            }
31834                            self.generate_expression(inner)?;
31835                        }
31836                    } else {
31837                        self.generate_expression(part)?;
31838                    }
31839                }
31840            }
31841            Expression::PartitionByRangePropertyDynamic(_) => {
31842                // Dynamic partition - FROM/TO/INTERVAL
31843                self.generate_expression(expr)?;
31844            }
31845            _ => {
31846                self.generate_expression(expr)?;
31847            }
31848        }
31849        Ok(())
31850    }
31851
31852    fn generate_partition_by_range_property_dynamic(
31853        &mut self,
31854        e: &PartitionByRangePropertyDynamic,
31855    ) -> Result<()> {
31856        if e.use_start_end {
31857            // StarRocks: START ('val') END ('val') EVERY (expr)
31858            if let Some(start) = &e.start {
31859                self.write_keyword("START");
31860                self.write(" (");
31861                self.generate_expression(start)?;
31862                self.write(")");
31863            }
31864            if let Some(end) = &e.end {
31865                self.write_space();
31866                self.write_keyword("END");
31867                self.write(" (");
31868                self.generate_expression(end)?;
31869                self.write(")");
31870            }
31871            if let Some(every) = &e.every {
31872                self.write_space();
31873                self.write_keyword("EVERY");
31874                self.write(" (");
31875                // Use unquoted interval format for StarRocks
31876                self.generate_doris_interval(every)?;
31877                self.write(")");
31878            }
31879        } else {
31880            // Doris: FROM (start) TO (end) INTERVAL n UNIT
31881            if let Some(start) = &e.start {
31882                self.write_keyword("FROM");
31883                self.write(" (");
31884                self.generate_expression(start)?;
31885                self.write(")");
31886            }
31887            if let Some(end) = &e.end {
31888                self.write_space();
31889                self.write_keyword("TO");
31890                self.write(" (");
31891                self.generate_expression(end)?;
31892                self.write(")");
31893            }
31894            if let Some(every) = &e.every {
31895                self.write_space();
31896                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
31897                self.generate_doris_interval(every)?;
31898            }
31899        }
31900        Ok(())
31901    }
31902
31903    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
31904    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
31905        if let Expression::Interval(interval) = expr {
31906            self.write_keyword("INTERVAL");
31907            if let Some(ref value) = interval.this {
31908                self.write_space();
31909                // If the value is a string literal that looks like a number,
31910                // output it without quotes (matching Python sqlglot's
31911                // partitionbyrangepropertydynamic_sql which converts back to number)
31912                match value {
31913                    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()) => {
31914                        if let Literal::String(s) = lit.as_ref() {
31915                            self.write(s);
31916                        }
31917                    }
31918                    _ => {
31919                        self.generate_expression(value)?;
31920                    }
31921                }
31922            }
31923            if let Some(ref unit_spec) = interval.unit {
31924                self.write_space();
31925                self.write_interval_unit_spec(unit_spec)?;
31926            }
31927            Ok(())
31928        } else {
31929            self.generate_expression(expr)
31930        }
31931    }
31932
31933    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
31934        // TRUNCATE(expression, this)
31935        self.write_keyword("TRUNCATE");
31936        self.write("(");
31937        self.generate_expression(&e.expression)?;
31938        self.write(", ");
31939        self.generate_expression(&e.this)?;
31940        self.write(")");
31941        Ok(())
31942    }
31943
31944    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
31945        // Doris: PARTITION name VALUES IN (val1, val2)
31946        self.write_keyword("PARTITION");
31947        self.write_space();
31948        self.generate_expression(&e.this)?;
31949        self.write_space();
31950        self.write_keyword("VALUES IN");
31951        self.write(" (");
31952        for (i, expr) in e.expressions.iter().enumerate() {
31953            if i > 0 {
31954                self.write(", ");
31955            }
31956            self.generate_expression(expr)?;
31957        }
31958        self.write(")");
31959        Ok(())
31960    }
31961
31962    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
31963        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
31964        // TSQL ranges have no expressions and just use `this TO expression`
31965        if e.expressions.is_empty() && e.expression.is_some() {
31966            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
31967            self.generate_expression(&e.this)?;
31968            self.write_space();
31969            self.write_keyword("TO");
31970            self.write_space();
31971            self.generate_expression(e.expression.as_ref().unwrap())?;
31972            return Ok(());
31973        }
31974
31975        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
31976        self.write_keyword("PARTITION");
31977        self.write_space();
31978        self.generate_expression(&e.this)?;
31979        self.write_space();
31980
31981        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
31982        if e.expressions.len() == 1 {
31983            // Single value: VALUES LESS THAN (val)
31984            self.write_keyword("VALUES LESS THAN");
31985            self.write(" (");
31986            self.generate_expression(&e.expressions[0])?;
31987            self.write(")");
31988        } else if !e.expressions.is_empty() {
31989            // Multiple values with Tuple: VALUES [(val1), (val2))
31990            self.write_keyword("VALUES");
31991            self.write(" [");
31992            for (i, expr) in e.expressions.iter().enumerate() {
31993                if i > 0 {
31994                    self.write(", ");
31995                }
31996                // If the expr is a Tuple, generate its contents wrapped in parens
31997                if let Expression::Tuple(t) = expr {
31998                    self.write("(");
31999                    for (j, inner) in t.expressions.iter().enumerate() {
32000                        if j > 0 {
32001                            self.write(", ");
32002                        }
32003                        self.generate_expression(inner)?;
32004                    }
32005                    self.write(")");
32006                } else {
32007                    self.write("(");
32008                    self.generate_expression(expr)?;
32009                    self.write(")");
32010                }
32011            }
32012            self.write(")");
32013        }
32014        Ok(())
32015    }
32016
32017    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
32018        // BUCKET(this, expression)
32019        self.write_keyword("BUCKET");
32020        self.write("(");
32021        self.generate_expression(&e.this)?;
32022        self.write(", ");
32023        self.generate_expression(&e.expression)?;
32024        self.write(")");
32025        Ok(())
32026    }
32027
32028    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
32029        // BigQuery table property: PARTITION BY expression [, expression ...]
32030        self.write_keyword("PARTITION BY");
32031        self.write_space();
32032        for (i, expr) in e.expressions.iter().enumerate() {
32033            if i > 0 {
32034                self.write(", ");
32035            }
32036            self.generate_expression(expr)?;
32037        }
32038        Ok(())
32039    }
32040
32041    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
32042        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
32043        if matches!(
32044            self.config.dialect,
32045            Some(crate::dialects::DialectType::Teradata)
32046                | Some(crate::dialects::DialectType::ClickHouse)
32047        ) {
32048            self.write_keyword("PARTITION BY");
32049        } else {
32050            self.write_keyword("PARTITIONED BY");
32051        }
32052        self.write_space();
32053        // In pretty mode, always use multiline tuple format for PARTITIONED BY
32054        if self.config.pretty {
32055            if let Expression::Tuple(ref tuple) = *e.this {
32056                self.write("(");
32057                self.write_newline();
32058                self.indent_level += 1;
32059                for (i, expr) in tuple.expressions.iter().enumerate() {
32060                    if i > 0 {
32061                        self.write(",");
32062                        self.write_newline();
32063                    }
32064                    self.write_indent();
32065                    self.generate_expression(expr)?;
32066                }
32067                self.indent_level -= 1;
32068                self.write_newline();
32069                self.write(")");
32070            } else {
32071                self.generate_expression(&e.this)?;
32072            }
32073        } else {
32074            self.generate_expression(&e.this)?;
32075        }
32076        Ok(())
32077    }
32078
32079    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
32080        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
32081        self.write_keyword("PARTITION OF");
32082        self.write_space();
32083        self.generate_expression(&e.this)?;
32084        // Check if expression is a PartitionBoundSpec
32085        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
32086            self.write_space();
32087            self.write_keyword("FOR VALUES");
32088            self.write_space();
32089            self.generate_expression(&e.expression)?;
32090        } else {
32091            self.write_space();
32092            self.write_keyword("DEFAULT");
32093        }
32094        Ok(())
32095    }
32096
32097    fn generate_period_for_system_time_constraint(
32098        &mut self,
32099        e: &PeriodForSystemTimeConstraint,
32100    ) -> Result<()> {
32101        // PERIOD FOR SYSTEM_TIME (this, expression)
32102        self.write_keyword("PERIOD FOR SYSTEM_TIME");
32103        self.write(" (");
32104        self.generate_expression(&e.this)?;
32105        self.write(", ");
32106        self.generate_expression(&e.expression)?;
32107        self.write(")");
32108        Ok(())
32109    }
32110
32111    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
32112        // value AS alias
32113        // The alias can be an identifier or an expression (e.g., string concatenation)
32114        self.generate_expression(&e.this)?;
32115        self.write_space();
32116        self.write_keyword("AS");
32117        self.write_space();
32118        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
32119        if self.config.unpivot_aliases_are_identifiers {
32120            match &e.alias {
32121                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
32122                    let Literal::String(s) = lit.as_ref() else {
32123                        unreachable!()
32124                    };
32125                    // Convert string literal to identifier
32126                    self.generate_identifier(&Identifier::new(s.clone()))?;
32127                }
32128                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
32129                    let Literal::Number(n) = lit.as_ref() else {
32130                        unreachable!()
32131                    };
32132                    // Convert number literal to quoted identifier
32133                    let mut id = Identifier::new(n.clone());
32134                    id.quoted = true;
32135                    self.generate_identifier(&id)?;
32136                }
32137                other => {
32138                    self.generate_expression(other)?;
32139                }
32140            }
32141        } else {
32142            self.generate_expression(&e.alias)?;
32143        }
32144        Ok(())
32145    }
32146
32147    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
32148        // ANY or ANY [expression]
32149        self.write_keyword("ANY");
32150        if let Some(this) = &e.this {
32151            self.write_space();
32152            self.generate_expression(this)?;
32153        }
32154        Ok(())
32155    }
32156
32157    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
32158        // ML.PREDICT(MODEL this, expression, [params_struct])
32159        self.write_keyword("ML.PREDICT");
32160        self.write("(");
32161        self.write_keyword("MODEL");
32162        self.write_space();
32163        self.generate_expression(&e.this)?;
32164        self.write(", ");
32165        self.generate_expression(&e.expression)?;
32166        if let Some(params) = &e.params_struct {
32167            self.write(", ");
32168            self.generate_expression(params)?;
32169        }
32170        self.write(")");
32171        Ok(())
32172    }
32173
32174    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
32175        // PREVIOUS_DAY(this, expression)
32176        self.write_keyword("PREVIOUS_DAY");
32177        self.write("(");
32178        self.generate_expression(&e.this)?;
32179        self.write(", ");
32180        self.generate_expression(&e.expression)?;
32181        self.write(")");
32182        Ok(())
32183    }
32184
32185    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
32186        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
32187        self.write_keyword("PRIMARY KEY");
32188        if let Some(name) = &e.this {
32189            self.write_space();
32190            self.generate_expression(name)?;
32191        }
32192        if !e.expressions.is_empty() {
32193            self.write(" (");
32194            for (i, expr) in e.expressions.iter().enumerate() {
32195                if i > 0 {
32196                    self.write(", ");
32197                }
32198                self.generate_expression(expr)?;
32199            }
32200            self.write(")");
32201        }
32202        if let Some(include) = &e.include {
32203            self.write_space();
32204            self.generate_expression(include)?;
32205        }
32206        if !e.options.is_empty() {
32207            self.write_space();
32208            for (i, opt) in e.options.iter().enumerate() {
32209                if i > 0 {
32210                    self.write_space();
32211                }
32212                self.generate_expression(opt)?;
32213            }
32214        }
32215        Ok(())
32216    }
32217
32218    fn generate_primary_key_column_constraint(
32219        &mut self,
32220        _e: &PrimaryKeyColumnConstraint,
32221    ) -> Result<()> {
32222        // PRIMARY KEY constraint at column level
32223        self.write_keyword("PRIMARY KEY");
32224        Ok(())
32225    }
32226
32227    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
32228        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
32229        self.write_keyword("PATH");
32230        self.write_space();
32231        self.generate_expression(&e.this)?;
32232        Ok(())
32233    }
32234
32235    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
32236        // PROJECTION this (expression)
32237        self.write_keyword("PROJECTION");
32238        self.write_space();
32239        self.generate_expression(&e.this)?;
32240        self.write(" (");
32241        self.generate_expression(&e.expression)?;
32242        self.write(")");
32243        Ok(())
32244    }
32245
32246    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
32247        // Properties list
32248        for (i, prop) in e.expressions.iter().enumerate() {
32249            if i > 0 {
32250                self.write(", ");
32251            }
32252            self.generate_expression(prop)?;
32253        }
32254        Ok(())
32255    }
32256
32257    fn generate_property(&mut self, e: &Property) -> Result<()> {
32258        // name=value
32259        self.generate_expression(&e.this)?;
32260        if let Some(value) = &e.value {
32261            self.write("=");
32262            self.generate_expression(value)?;
32263        }
32264        Ok(())
32265    }
32266
32267    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
32268        self.write_keyword("OPTIONS");
32269        if e.entries.is_empty() {
32270            self.write(" ()");
32271            return Ok(());
32272        }
32273
32274        if self.config.pretty {
32275            self.write(" (");
32276            self.write_newline();
32277            self.indent_level += 1;
32278            for (i, entry) in e.entries.iter().enumerate() {
32279                if i > 0 {
32280                    self.write(",");
32281                    self.write_newline();
32282                }
32283                self.write_indent();
32284                self.generate_identifier(&entry.key)?;
32285                self.write("=");
32286                self.generate_expression(&entry.value)?;
32287            }
32288            self.indent_level -= 1;
32289            self.write_newline();
32290            self.write(")");
32291        } else {
32292            self.write(" (");
32293            for (i, entry) in e.entries.iter().enumerate() {
32294                if i > 0 {
32295                    self.write(", ");
32296                }
32297                self.generate_identifier(&entry.key)?;
32298                self.write("=");
32299                self.generate_expression(&entry.value)?;
32300            }
32301            self.write(")");
32302        }
32303        Ok(())
32304    }
32305
32306    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
32307    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
32308        self.write_keyword("OPTIONS");
32309        self.write(" (");
32310        for (i, opt) in options.iter().enumerate() {
32311            if i > 0 {
32312                self.write(", ");
32313            }
32314            self.generate_option_expression(opt)?;
32315        }
32316        self.write(")");
32317        Ok(())
32318    }
32319
32320    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
32321    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
32322        self.write_keyword("PROPERTIES");
32323        self.write(" (");
32324        for (i, prop) in properties.iter().enumerate() {
32325            if i > 0 {
32326                self.write(", ");
32327            }
32328            self.generate_option_expression(prop)?;
32329        }
32330        self.write(")");
32331        Ok(())
32332    }
32333
32334    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
32335    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
32336        self.write_keyword("ENVIRONMENT");
32337        self.write(" (");
32338        for (i, env_item) in environment.iter().enumerate() {
32339            if i > 0 {
32340                self.write(", ");
32341            }
32342            self.generate_environment_expression(env_item)?;
32343        }
32344        self.write(")");
32345        Ok(())
32346    }
32347
32348    /// Generate an environment expression with spaces around =
32349    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
32350        match expr {
32351            Expression::Eq(eq) => {
32352                // Generate key = value with spaces (Databricks ENVIRONMENT style)
32353                self.generate_expression(&eq.left)?;
32354                self.write(" = ");
32355                self.generate_expression(&eq.right)?;
32356                Ok(())
32357            }
32358            _ => self.generate_expression(expr),
32359        }
32360    }
32361
32362    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
32363    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
32364        self.write_keyword("TBLPROPERTIES");
32365        if self.config.pretty {
32366            self.write(" (");
32367            self.write_newline();
32368            self.indent_level += 1;
32369            for (i, opt) in options.iter().enumerate() {
32370                if i > 0 {
32371                    self.write(",");
32372                    self.write_newline();
32373                }
32374                self.write_indent();
32375                self.generate_option_expression(opt)?;
32376            }
32377            self.indent_level -= 1;
32378            self.write_newline();
32379            self.write(")");
32380        } else {
32381            self.write(" (");
32382            for (i, opt) in options.iter().enumerate() {
32383                if i > 0 {
32384                    self.write(", ");
32385                }
32386                self.generate_option_expression(opt)?;
32387            }
32388            self.write(")");
32389        }
32390        Ok(())
32391    }
32392
32393    /// Generate an option expression without spaces around =
32394    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
32395        match expr {
32396            Expression::Eq(eq) => {
32397                // Generate key=value without spaces
32398                self.generate_expression(&eq.left)?;
32399                self.write("=");
32400                self.generate_expression(&eq.right)?;
32401                Ok(())
32402            }
32403            _ => self.generate_expression(expr),
32404        }
32405    }
32406
32407    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
32408        // Just output the name
32409        self.generate_expression(&e.this)?;
32410        Ok(())
32411    }
32412
32413    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
32414        // PUT source_file @stage [options]
32415        self.write_keyword("PUT");
32416        self.write_space();
32417
32418        // Source file path - preserve original quoting
32419        if e.source_quoted {
32420            self.write("'");
32421            self.write(&e.source);
32422            self.write("'");
32423        } else {
32424            self.write(&e.source);
32425        }
32426
32427        self.write_space();
32428
32429        // Target stage reference - output the string directly (includes @)
32430        if let Expression::Literal(lit) = &e.target {
32431            if let Literal::String(s) = lit.as_ref() {
32432                self.write(s);
32433            }
32434        } else {
32435            self.generate_expression(&e.target)?;
32436        }
32437
32438        // Optional parameters: KEY=VALUE
32439        for param in &e.params {
32440            self.write_space();
32441            self.write(&param.name);
32442            if let Some(ref value) = param.value {
32443                self.write("=");
32444                self.generate_expression(value)?;
32445            }
32446        }
32447
32448        Ok(())
32449    }
32450
32451    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
32452        // QUANTILE(this, quantile)
32453        self.write_keyword("QUANTILE");
32454        self.write("(");
32455        self.generate_expression(&e.this)?;
32456        if let Some(quantile) = &e.quantile {
32457            self.write(", ");
32458            self.generate_expression(quantile)?;
32459        }
32460        self.write(")");
32461        Ok(())
32462    }
32463
32464    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
32465        // QUERY_BAND = this [UPDATE] [FOR scope]
32466        if matches!(
32467            self.config.dialect,
32468            Some(crate::dialects::DialectType::Teradata)
32469        ) {
32470            self.write_keyword("SET");
32471            self.write_space();
32472        }
32473        self.write_keyword("QUERY_BAND");
32474        self.write(" = ");
32475        self.generate_expression(&e.this)?;
32476        if e.update.is_some() {
32477            self.write_space();
32478            self.write_keyword("UPDATE");
32479        }
32480        if let Some(scope) = &e.scope {
32481            self.write_space();
32482            self.write_keyword("FOR");
32483            self.write_space();
32484            self.generate_expression(scope)?;
32485        }
32486        Ok(())
32487    }
32488
32489    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
32490        // this = expression
32491        self.generate_expression(&e.this)?;
32492        if let Some(expression) = &e.expression {
32493            self.write(" = ");
32494            self.generate_expression(expression)?;
32495        }
32496        Ok(())
32497    }
32498
32499    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
32500        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
32501        self.write_keyword("TRANSFORM");
32502        self.write("(");
32503        for (i, expr) in e.expressions.iter().enumerate() {
32504            if i > 0 {
32505                self.write(", ");
32506            }
32507            self.generate_expression(expr)?;
32508        }
32509        self.write(")");
32510        if let Some(row_format_before) = &e.row_format_before {
32511            self.write_space();
32512            self.generate_expression(row_format_before)?;
32513        }
32514        if let Some(record_writer) = &e.record_writer {
32515            self.write_space();
32516            self.write_keyword("RECORDWRITER");
32517            self.write_space();
32518            self.generate_expression(record_writer)?;
32519        }
32520        if let Some(command_script) = &e.command_script {
32521            self.write_space();
32522            self.write_keyword("USING");
32523            self.write_space();
32524            self.generate_expression(command_script)?;
32525        }
32526        if let Some(schema) = &e.schema {
32527            self.write_space();
32528            self.write_keyword("AS");
32529            self.write_space();
32530            self.generate_expression(schema)?;
32531        }
32532        if let Some(row_format_after) = &e.row_format_after {
32533            self.write_space();
32534            self.generate_expression(row_format_after)?;
32535        }
32536        if let Some(record_reader) = &e.record_reader {
32537            self.write_space();
32538            self.write_keyword("RECORDREADER");
32539            self.write_space();
32540            self.generate_expression(record_reader)?;
32541        }
32542        Ok(())
32543    }
32544
32545    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
32546        // RANDN([seed])
32547        self.write_keyword("RANDN");
32548        self.write("(");
32549        if let Some(this) = &e.this {
32550            self.generate_expression(this)?;
32551        }
32552        self.write(")");
32553        Ok(())
32554    }
32555
32556    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
32557        // RANDSTR(this, [generator])
32558        self.write_keyword("RANDSTR");
32559        self.write("(");
32560        self.generate_expression(&e.this)?;
32561        if let Some(generator) = &e.generator {
32562            self.write(", ");
32563            self.generate_expression(generator)?;
32564        }
32565        self.write(")");
32566        Ok(())
32567    }
32568
32569    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
32570        // RANGE_BUCKET(this, expression)
32571        self.write_keyword("RANGE_BUCKET");
32572        self.write("(");
32573        self.generate_expression(&e.this)?;
32574        self.write(", ");
32575        self.generate_expression(&e.expression)?;
32576        self.write(")");
32577        Ok(())
32578    }
32579
32580    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
32581        // RANGE_N(this BETWEEN expressions [EACH each])
32582        self.write_keyword("RANGE_N");
32583        self.write("(");
32584        self.generate_expression(&e.this)?;
32585        self.write_space();
32586        self.write_keyword("BETWEEN");
32587        self.write_space();
32588        for (i, expr) in e.expressions.iter().enumerate() {
32589            if i > 0 {
32590                self.write(", ");
32591            }
32592            self.generate_expression(expr)?;
32593        }
32594        if let Some(each) = &e.each {
32595            self.write_space();
32596            self.write_keyword("EACH");
32597            self.write_space();
32598            self.generate_expression(each)?;
32599        }
32600        self.write(")");
32601        Ok(())
32602    }
32603
32604    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
32605        // READ_CSV(this, expressions...)
32606        self.write_keyword("READ_CSV");
32607        self.write("(");
32608        self.generate_expression(&e.this)?;
32609        for expr in &e.expressions {
32610            self.write(", ");
32611            self.generate_expression(expr)?;
32612        }
32613        self.write(")");
32614        Ok(())
32615    }
32616
32617    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
32618        // READ_PARQUET(expressions...)
32619        self.write_keyword("READ_PARQUET");
32620        self.write("(");
32621        for (i, expr) in e.expressions.iter().enumerate() {
32622            if i > 0 {
32623                self.write(", ");
32624            }
32625            self.generate_expression(expr)?;
32626        }
32627        self.write(")");
32628        Ok(())
32629    }
32630
32631    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
32632        // SEARCH kind FIRST BY this SET expression [USING using]
32633        // or CYCLE this SET expression [USING using]
32634        if e.kind == "CYCLE" {
32635            self.write_keyword("CYCLE");
32636        } else {
32637            self.write_keyword("SEARCH");
32638            self.write_space();
32639            self.write(&e.kind);
32640            self.write_space();
32641            self.write_keyword("FIRST BY");
32642        }
32643        self.write_space();
32644        self.generate_expression(&e.this)?;
32645        self.write_space();
32646        self.write_keyword("SET");
32647        self.write_space();
32648        self.generate_expression(&e.expression)?;
32649        if let Some(using) = &e.using {
32650            self.write_space();
32651            self.write_keyword("USING");
32652            self.write_space();
32653            self.generate_expression(using)?;
32654        }
32655        Ok(())
32656    }
32657
32658    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
32659        // REDUCE(this, initial, merge, [finish])
32660        self.write_keyword("REDUCE");
32661        self.write("(");
32662        self.generate_expression(&e.this)?;
32663        if let Some(initial) = &e.initial {
32664            self.write(", ");
32665            self.generate_expression(initial)?;
32666        }
32667        if let Some(merge) = &e.merge {
32668            self.write(", ");
32669            self.generate_expression(merge)?;
32670        }
32671        if let Some(finish) = &e.finish {
32672            self.write(", ");
32673            self.generate_expression(finish)?;
32674        }
32675        self.write(")");
32676        Ok(())
32677    }
32678
32679    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
32680        // REFERENCES this (expressions) [options]
32681        self.write_keyword("REFERENCES");
32682        self.write_space();
32683        self.generate_expression(&e.this)?;
32684        if !e.expressions.is_empty() {
32685            self.write(" (");
32686            for (i, expr) in e.expressions.iter().enumerate() {
32687                if i > 0 {
32688                    self.write(", ");
32689                }
32690                self.generate_expression(expr)?;
32691            }
32692            self.write(")");
32693        }
32694        for opt in &e.options {
32695            self.write_space();
32696            self.generate_expression(opt)?;
32697        }
32698        Ok(())
32699    }
32700
32701    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
32702        // REFRESH [kind] this
32703        self.write_keyword("REFRESH");
32704        if !e.kind.is_empty() {
32705            self.write_space();
32706            self.write_keyword(&e.kind);
32707        }
32708        self.write_space();
32709        self.generate_expression(&e.this)?;
32710        Ok(())
32711    }
32712
32713    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
32714        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
32715        self.write_keyword("REFRESH");
32716        self.write_space();
32717        self.write_keyword(&e.method);
32718
32719        if let Some(ref kind) = e.kind {
32720            self.write_space();
32721            self.write_keyword("ON");
32722            self.write_space();
32723            self.write_keyword(kind);
32724
32725            // EVERY n UNIT
32726            if let Some(ref every) = e.every {
32727                self.write_space();
32728                self.write_keyword("EVERY");
32729                self.write_space();
32730                self.generate_expression(every)?;
32731                if let Some(ref unit) = e.unit {
32732                    self.write_space();
32733                    self.write_keyword(unit);
32734                }
32735            }
32736
32737            // STARTS 'datetime'
32738            if let Some(ref starts) = e.starts {
32739                self.write_space();
32740                self.write_keyword("STARTS");
32741                self.write_space();
32742                self.generate_expression(starts)?;
32743            }
32744        }
32745        Ok(())
32746    }
32747
32748    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
32749        // REGEXP_COUNT(this, expression, position, parameters)
32750        self.write_keyword("REGEXP_COUNT");
32751        self.write("(");
32752        self.generate_expression(&e.this)?;
32753        self.write(", ");
32754        self.generate_expression(&e.expression)?;
32755        if let Some(position) = &e.position {
32756            self.write(", ");
32757            self.generate_expression(position)?;
32758        }
32759        if let Some(parameters) = &e.parameters {
32760            self.write(", ");
32761            self.generate_expression(parameters)?;
32762        }
32763        self.write(")");
32764        Ok(())
32765    }
32766
32767    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
32768        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
32769        self.write_keyword("REGEXP_EXTRACT_ALL");
32770        self.write("(");
32771        self.generate_expression(&e.this)?;
32772        self.write(", ");
32773        self.generate_expression(&e.expression)?;
32774        if let Some(group) = &e.group {
32775            self.write(", ");
32776            self.generate_expression(group)?;
32777        }
32778        self.write(")");
32779        Ok(())
32780    }
32781
32782    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
32783        // REGEXP_FULL_MATCH(this, expression)
32784        self.write_keyword("REGEXP_FULL_MATCH");
32785        self.write("(");
32786        self.generate_expression(&e.this)?;
32787        self.write(", ");
32788        self.generate_expression(&e.expression)?;
32789        self.write(")");
32790        Ok(())
32791    }
32792
32793    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
32794        use crate::dialects::DialectType;
32795        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
32796        if matches!(
32797            self.config.dialect,
32798            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
32799        ) && e.flag.is_none()
32800        {
32801            self.generate_expression(&e.this)?;
32802            self.write(" ~* ");
32803            self.generate_expression(&e.expression)?;
32804        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
32805            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
32806            self.write_keyword("REGEXP_LIKE");
32807            self.write("(");
32808            self.generate_expression(&e.this)?;
32809            self.write(", ");
32810            self.generate_expression(&e.expression)?;
32811            self.write(", ");
32812            if let Some(flag) = &e.flag {
32813                self.generate_expression(flag)?;
32814            } else {
32815                self.write("'i'");
32816            }
32817            self.write(")");
32818        } else {
32819            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
32820            self.generate_expression(&e.this)?;
32821            self.write_space();
32822            self.write_keyword("REGEXP_ILIKE");
32823            self.write_space();
32824            self.generate_expression(&e.expression)?;
32825            if let Some(flag) = &e.flag {
32826                self.write(", ");
32827                self.generate_expression(flag)?;
32828            }
32829        }
32830        Ok(())
32831    }
32832
32833    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
32834        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
32835        self.write_keyword("REGEXP_INSTR");
32836        self.write("(");
32837        self.generate_expression(&e.this)?;
32838        self.write(", ");
32839        self.generate_expression(&e.expression)?;
32840        if let Some(position) = &e.position {
32841            self.write(", ");
32842            self.generate_expression(position)?;
32843        }
32844        if let Some(occurrence) = &e.occurrence {
32845            self.write(", ");
32846            self.generate_expression(occurrence)?;
32847        }
32848        if let Some(option) = &e.option {
32849            self.write(", ");
32850            self.generate_expression(option)?;
32851        }
32852        if let Some(parameters) = &e.parameters {
32853            self.write(", ");
32854            self.generate_expression(parameters)?;
32855        }
32856        if let Some(group) = &e.group {
32857            self.write(", ");
32858            self.generate_expression(group)?;
32859        }
32860        self.write(")");
32861        Ok(())
32862    }
32863
32864    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
32865        // REGEXP_SPLIT(this, expression, limit)
32866        self.write_keyword("REGEXP_SPLIT");
32867        self.write("(");
32868        self.generate_expression(&e.this)?;
32869        self.write(", ");
32870        self.generate_expression(&e.expression)?;
32871        if let Some(limit) = &e.limit {
32872            self.write(", ");
32873            self.generate_expression(limit)?;
32874        }
32875        self.write(")");
32876        Ok(())
32877    }
32878
32879    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
32880        // REGR_AVGX(this, expression)
32881        self.write_keyword("REGR_AVGX");
32882        self.write("(");
32883        self.generate_expression(&e.this)?;
32884        self.write(", ");
32885        self.generate_expression(&e.expression)?;
32886        self.write(")");
32887        Ok(())
32888    }
32889
32890    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
32891        // REGR_AVGY(this, expression)
32892        self.write_keyword("REGR_AVGY");
32893        self.write("(");
32894        self.generate_expression(&e.this)?;
32895        self.write(", ");
32896        self.generate_expression(&e.expression)?;
32897        self.write(")");
32898        Ok(())
32899    }
32900
32901    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
32902        // REGR_COUNT(this, expression)
32903        self.write_keyword("REGR_COUNT");
32904        self.write("(");
32905        self.generate_expression(&e.this)?;
32906        self.write(", ");
32907        self.generate_expression(&e.expression)?;
32908        self.write(")");
32909        Ok(())
32910    }
32911
32912    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
32913        // REGR_INTERCEPT(this, expression)
32914        self.write_keyword("REGR_INTERCEPT");
32915        self.write("(");
32916        self.generate_expression(&e.this)?;
32917        self.write(", ");
32918        self.generate_expression(&e.expression)?;
32919        self.write(")");
32920        Ok(())
32921    }
32922
32923    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
32924        // REGR_R2(this, expression)
32925        self.write_keyword("REGR_R2");
32926        self.write("(");
32927        self.generate_expression(&e.this)?;
32928        self.write(", ");
32929        self.generate_expression(&e.expression)?;
32930        self.write(")");
32931        Ok(())
32932    }
32933
32934    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
32935        // REGR_SLOPE(this, expression)
32936        self.write_keyword("REGR_SLOPE");
32937        self.write("(");
32938        self.generate_expression(&e.this)?;
32939        self.write(", ");
32940        self.generate_expression(&e.expression)?;
32941        self.write(")");
32942        Ok(())
32943    }
32944
32945    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
32946        // REGR_SXX(this, expression)
32947        self.write_keyword("REGR_SXX");
32948        self.write("(");
32949        self.generate_expression(&e.this)?;
32950        self.write(", ");
32951        self.generate_expression(&e.expression)?;
32952        self.write(")");
32953        Ok(())
32954    }
32955
32956    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
32957        // REGR_SXY(this, expression)
32958        self.write_keyword("REGR_SXY");
32959        self.write("(");
32960        self.generate_expression(&e.this)?;
32961        self.write(", ");
32962        self.generate_expression(&e.expression)?;
32963        self.write(")");
32964        Ok(())
32965    }
32966
32967    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
32968        // REGR_SYY(this, expression)
32969        self.write_keyword("REGR_SYY");
32970        self.write("(");
32971        self.generate_expression(&e.this)?;
32972        self.write(", ");
32973        self.generate_expression(&e.expression)?;
32974        self.write(")");
32975        Ok(())
32976    }
32977
32978    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
32979        // REGR_VALX(this, expression)
32980        self.write_keyword("REGR_VALX");
32981        self.write("(");
32982        self.generate_expression(&e.this)?;
32983        self.write(", ");
32984        self.generate_expression(&e.expression)?;
32985        self.write(")");
32986        Ok(())
32987    }
32988
32989    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
32990        // REGR_VALY(this, expression)
32991        self.write_keyword("REGR_VALY");
32992        self.write("(");
32993        self.generate_expression(&e.this)?;
32994        self.write(", ");
32995        self.generate_expression(&e.expression)?;
32996        self.write(")");
32997        Ok(())
32998    }
32999
33000    fn generate_remote_with_connection_model_property(
33001        &mut self,
33002        e: &RemoteWithConnectionModelProperty,
33003    ) -> Result<()> {
33004        // REMOTE WITH CONNECTION this
33005        self.write_keyword("REMOTE WITH CONNECTION");
33006        self.write_space();
33007        self.generate_expression(&e.this)?;
33008        Ok(())
33009    }
33010
33011    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
33012        // RENAME COLUMN [IF EXISTS] this TO new_name
33013        self.write_keyword("RENAME COLUMN");
33014        if e.exists {
33015            self.write_space();
33016            self.write_keyword("IF EXISTS");
33017        }
33018        self.write_space();
33019        self.generate_expression(&e.this)?;
33020        if let Some(to) = &e.to {
33021            self.write_space();
33022            self.write_keyword("TO");
33023            self.write_space();
33024            self.generate_expression(to)?;
33025        }
33026        Ok(())
33027    }
33028
33029    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
33030        // REPLACE PARTITION expression [FROM source]
33031        self.write_keyword("REPLACE PARTITION");
33032        self.write_space();
33033        self.generate_expression(&e.expression)?;
33034        if let Some(source) = &e.source {
33035            self.write_space();
33036            self.write_keyword("FROM");
33037            self.write_space();
33038            self.generate_expression(source)?;
33039        }
33040        Ok(())
33041    }
33042
33043    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
33044        // RETURNING expressions [INTO into]
33045        // TSQL and Fabric use OUTPUT instead of RETURNING
33046        let keyword = match self.config.dialect {
33047            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
33048            _ => "RETURNING",
33049        };
33050        self.write_keyword(keyword);
33051        self.write_space();
33052        for (i, expr) in e.expressions.iter().enumerate() {
33053            if i > 0 {
33054                self.write(", ");
33055            }
33056            self.generate_expression(expr)?;
33057        }
33058        if let Some(into) = &e.into {
33059            self.write_space();
33060            self.write_keyword("INTO");
33061            self.write_space();
33062            self.generate_expression(into)?;
33063        }
33064        Ok(())
33065    }
33066
33067    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
33068        // OUTPUT expressions [INTO into_table]
33069        self.write_space();
33070        self.write_keyword("OUTPUT");
33071        self.write_space();
33072        for (i, expr) in output.columns.iter().enumerate() {
33073            if i > 0 {
33074                self.write(", ");
33075            }
33076            self.generate_expression(expr)?;
33077        }
33078        if let Some(into_table) = &output.into_table {
33079            self.write_space();
33080            self.write_keyword("INTO");
33081            self.write_space();
33082            self.generate_expression(into_table)?;
33083        }
33084        Ok(())
33085    }
33086
33087    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
33088        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
33089        self.write_keyword("RETURNS");
33090        if e.is_table.is_some() {
33091            self.write_space();
33092            self.write_keyword("TABLE");
33093        }
33094        if let Some(table) = &e.table {
33095            self.write_space();
33096            self.generate_expression(table)?;
33097        } else if let Some(this) = &e.this {
33098            self.write_space();
33099            self.generate_expression(this)?;
33100        }
33101        if e.null.is_some() {
33102            self.write_space();
33103            self.write_keyword("NULL ON NULL INPUT");
33104        }
33105        Ok(())
33106    }
33107
33108    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
33109        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
33110        self.write_keyword("ROLLBACK");
33111
33112        // TSQL always uses ROLLBACK TRANSACTION
33113        if e.this.is_none()
33114            && matches!(
33115                self.config.dialect,
33116                Some(DialectType::TSQL) | Some(DialectType::Fabric)
33117            )
33118        {
33119            self.write_space();
33120            self.write_keyword("TRANSACTION");
33121        }
33122
33123        // Check if this has TRANSACTION keyword or transaction name
33124        if let Some(this) = &e.this {
33125            // Check if it's just the "TRANSACTION" marker or an actual transaction name
33126            let is_transaction_marker = matches!(
33127                this.as_ref(),
33128                Expression::Identifier(id) if id.name == "TRANSACTION"
33129            );
33130
33131            self.write_space();
33132            self.write_keyword("TRANSACTION");
33133
33134            // If it's a real transaction name, output it
33135            if !is_transaction_marker {
33136                self.write_space();
33137                self.generate_expression(this)?;
33138            }
33139        }
33140
33141        // Output TO savepoint
33142        if let Some(savepoint) = &e.savepoint {
33143            self.write_space();
33144            self.write_keyword("TO");
33145            self.write_space();
33146            self.generate_expression(savepoint)?;
33147        }
33148        Ok(())
33149    }
33150
33151    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
33152        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
33153        if e.expressions.is_empty() {
33154            self.write_keyword("WITH ROLLUP");
33155        } else {
33156            self.write_keyword("ROLLUP");
33157            self.write("(");
33158            for (i, expr) in e.expressions.iter().enumerate() {
33159                if i > 0 {
33160                    self.write(", ");
33161                }
33162                self.generate_expression(expr)?;
33163            }
33164            self.write(")");
33165        }
33166        Ok(())
33167    }
33168
33169    fn generate_row_format_delimited_property(
33170        &mut self,
33171        e: &RowFormatDelimitedProperty,
33172    ) -> Result<()> {
33173        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
33174        self.write_keyword("ROW FORMAT DELIMITED");
33175        if let Some(fields) = &e.fields {
33176            self.write_space();
33177            self.write_keyword("FIELDS TERMINATED BY");
33178            self.write_space();
33179            self.generate_expression(fields)?;
33180        }
33181        if let Some(escaped) = &e.escaped {
33182            self.write_space();
33183            self.write_keyword("ESCAPED BY");
33184            self.write_space();
33185            self.generate_expression(escaped)?;
33186        }
33187        if let Some(items) = &e.collection_items {
33188            self.write_space();
33189            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
33190            self.write_space();
33191            self.generate_expression(items)?;
33192        }
33193        if let Some(keys) = &e.map_keys {
33194            self.write_space();
33195            self.write_keyword("MAP KEYS TERMINATED BY");
33196            self.write_space();
33197            self.generate_expression(keys)?;
33198        }
33199        if let Some(lines) = &e.lines {
33200            self.write_space();
33201            self.write_keyword("LINES TERMINATED BY");
33202            self.write_space();
33203            self.generate_expression(lines)?;
33204        }
33205        if let Some(null) = &e.null {
33206            self.write_space();
33207            self.write_keyword("NULL DEFINED AS");
33208            self.write_space();
33209            self.generate_expression(null)?;
33210        }
33211        if let Some(serde) = &e.serde {
33212            self.write_space();
33213            self.generate_expression(serde)?;
33214        }
33215        Ok(())
33216    }
33217
33218    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
33219        // ROW FORMAT this
33220        self.write_keyword("ROW FORMAT");
33221        self.write_space();
33222        self.generate_expression(&e.this)?;
33223        Ok(())
33224    }
33225
33226    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
33227        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
33228        self.write_keyword("ROW FORMAT SERDE");
33229        self.write_space();
33230        self.generate_expression(&e.this)?;
33231        if let Some(props) = &e.serde_properties {
33232            self.write_space();
33233            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
33234            self.generate_expression(props)?;
33235        }
33236        Ok(())
33237    }
33238
33239    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
33240        // SHA2(this, length)
33241        self.write_keyword("SHA2");
33242        self.write("(");
33243        self.generate_expression(&e.this)?;
33244        if let Some(length) = e.length {
33245            self.write(", ");
33246            self.write(&length.to_string());
33247        }
33248        self.write(")");
33249        Ok(())
33250    }
33251
33252    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
33253        // SHA2_DIGEST(this, length)
33254        self.write_keyword("SHA2_DIGEST");
33255        self.write("(");
33256        self.generate_expression(&e.this)?;
33257        if let Some(length) = e.length {
33258            self.write(", ");
33259            self.write(&length.to_string());
33260        }
33261        self.write(")");
33262        Ok(())
33263    }
33264
33265    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
33266        let name = if matches!(
33267            self.config.dialect,
33268            Some(crate::dialects::DialectType::Spark)
33269                | Some(crate::dialects::DialectType::Databricks)
33270        ) {
33271            "TRY_ADD"
33272        } else {
33273            "SAFE_ADD"
33274        };
33275        self.write_keyword(name);
33276        self.write("(");
33277        self.generate_expression(&e.this)?;
33278        self.write(", ");
33279        self.generate_expression(&e.expression)?;
33280        self.write(")");
33281        Ok(())
33282    }
33283
33284    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
33285        // SAFE_DIVIDE(this, expression)
33286        self.write_keyword("SAFE_DIVIDE");
33287        self.write("(");
33288        self.generate_expression(&e.this)?;
33289        self.write(", ");
33290        self.generate_expression(&e.expression)?;
33291        self.write(")");
33292        Ok(())
33293    }
33294
33295    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
33296        let name = if matches!(
33297            self.config.dialect,
33298            Some(crate::dialects::DialectType::Spark)
33299                | Some(crate::dialects::DialectType::Databricks)
33300        ) {
33301            "TRY_MULTIPLY"
33302        } else {
33303            "SAFE_MULTIPLY"
33304        };
33305        self.write_keyword(name);
33306        self.write("(");
33307        self.generate_expression(&e.this)?;
33308        self.write(", ");
33309        self.generate_expression(&e.expression)?;
33310        self.write(")");
33311        Ok(())
33312    }
33313
33314    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
33315        let name = if matches!(
33316            self.config.dialect,
33317            Some(crate::dialects::DialectType::Spark)
33318                | Some(crate::dialects::DialectType::Databricks)
33319        ) {
33320            "TRY_SUBTRACT"
33321        } else {
33322            "SAFE_SUBTRACT"
33323        };
33324        self.write_keyword(name);
33325        self.write("(");
33326        self.generate_expression(&e.this)?;
33327        self.write(", ");
33328        self.generate_expression(&e.expression)?;
33329        self.write(")");
33330        Ok(())
33331    }
33332
33333    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
33334    /// METHOD (size UNIT) [REPEATABLE (seed)]
33335    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
33336        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
33337        if matches!(sample.method, SampleMethod::Bucket) {
33338            self.write(" (");
33339            self.write_keyword("BUCKET");
33340            self.write_space();
33341            if let Some(ref num) = sample.bucket_numerator {
33342                self.generate_expression(num)?;
33343            }
33344            self.write_space();
33345            self.write_keyword("OUT OF");
33346            self.write_space();
33347            if let Some(ref denom) = sample.bucket_denominator {
33348                self.generate_expression(denom)?;
33349            }
33350            if let Some(ref field) = sample.bucket_field {
33351                self.write_space();
33352                self.write_keyword("ON");
33353                self.write_space();
33354                self.generate_expression(field)?;
33355            }
33356            self.write(")");
33357            return Ok(());
33358        }
33359
33360        // Output method name if explicitly specified, or for dialects that always require it
33361        let is_snowflake = matches!(
33362            self.config.dialect,
33363            Some(crate::dialects::DialectType::Snowflake)
33364        );
33365        let is_postgres = matches!(
33366            self.config.dialect,
33367            Some(crate::dialects::DialectType::PostgreSQL)
33368                | Some(crate::dialects::DialectType::Redshift)
33369        );
33370        // Databricks and Spark don't output method names
33371        let is_databricks = matches!(
33372            self.config.dialect,
33373            Some(crate::dialects::DialectType::Databricks)
33374        );
33375        let is_spark = matches!(
33376            self.config.dialect,
33377            Some(crate::dialects::DialectType::Spark)
33378        );
33379        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
33380        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
33381        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
33382        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
33383            self.write_space();
33384            if !sample.explicit_method && (is_snowflake || force_method) {
33385                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
33386                self.write_keyword("BERNOULLI");
33387            } else {
33388                match sample.method {
33389                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
33390                    SampleMethod::System => self.write_keyword("SYSTEM"),
33391                    SampleMethod::Block => self.write_keyword("BLOCK"),
33392                    SampleMethod::Row => self.write_keyword("ROW"),
33393                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
33394                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
33395                    SampleMethod::Bucket => {} // handled above
33396                }
33397            }
33398        }
33399
33400        // Output size, with or without parentheses depending on dialect
33401        let emit_size_no_parens = !self.config.tablesample_requires_parens;
33402        if emit_size_no_parens {
33403            self.write_space();
33404            match &sample.size {
33405                Expression::Tuple(tuple) => {
33406                    for (i, expr) in tuple.expressions.iter().enumerate() {
33407                        if i > 0 {
33408                            self.write(", ");
33409                        }
33410                        self.generate_expression(expr)?;
33411                    }
33412                }
33413                expr => self.generate_expression(expr)?,
33414            }
33415        } else {
33416            self.write(" (");
33417            self.generate_expression(&sample.size)?;
33418        }
33419
33420        // Determine unit
33421        let is_rows_method = matches!(
33422            sample.method,
33423            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
33424        );
33425        let is_percent = matches!(
33426            sample.method,
33427            SampleMethod::Percent
33428                | SampleMethod::System
33429                | SampleMethod::Bernoulli
33430                | SampleMethod::Block
33431        );
33432
33433        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
33434        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
33435        // For Databricks and Spark, always output PERCENT for percentage samples.
33436        let is_presto = matches!(
33437            self.config.dialect,
33438            Some(crate::dialects::DialectType::Presto)
33439                | Some(crate::dialects::DialectType::Trino)
33440                | Some(crate::dialects::DialectType::Athena)
33441        );
33442        let should_output_unit = if is_databricks || is_spark {
33443            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
33444            is_percent || is_rows_method || sample.unit_after_size
33445        } else if is_snowflake || is_postgres || is_presto {
33446            sample.unit_after_size
33447        } else {
33448            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
33449        };
33450
33451        if should_output_unit {
33452            self.write_space();
33453            if sample.is_percent {
33454                self.write_keyword("PERCENT");
33455            } else if is_rows_method && !sample.unit_after_size {
33456                self.write_keyword("ROWS");
33457            } else if sample.unit_after_size {
33458                match sample.method {
33459                    SampleMethod::Percent
33460                    | SampleMethod::System
33461                    | SampleMethod::Bernoulli
33462                    | SampleMethod::Block => {
33463                        self.write_keyword("PERCENT");
33464                    }
33465                    SampleMethod::Row | SampleMethod::Reservoir => {
33466                        self.write_keyword("ROWS");
33467                    }
33468                    _ => self.write_keyword("ROWS"),
33469                }
33470            } else {
33471                self.write_keyword("PERCENT");
33472            }
33473        }
33474
33475        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
33476            if let Some(ref offset) = sample.offset {
33477                self.write_space();
33478                self.write_keyword("OFFSET");
33479                self.write_space();
33480                self.generate_expression(offset)?;
33481            }
33482        }
33483        if !emit_size_no_parens {
33484            self.write(")");
33485        }
33486
33487        Ok(())
33488    }
33489
33490    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
33491        // SAMPLE this (ClickHouse uses SAMPLE BY)
33492        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
33493            self.write_keyword("SAMPLE BY");
33494        } else {
33495            self.write_keyword("SAMPLE");
33496        }
33497        self.write_space();
33498        self.generate_expression(&e.this)?;
33499        Ok(())
33500    }
33501
33502    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
33503        // this (expressions...)
33504        if let Some(this) = &e.this {
33505            self.generate_expression(this)?;
33506        }
33507        if !e.expressions.is_empty() {
33508            // Add space before column list if there's a preceding expression
33509            if e.this.is_some() {
33510                self.write_space();
33511            }
33512            self.write("(");
33513            for (i, expr) in e.expressions.iter().enumerate() {
33514                if i > 0 {
33515                    self.write(", ");
33516                }
33517                self.generate_expression(expr)?;
33518            }
33519            self.write(")");
33520        }
33521        Ok(())
33522    }
33523
33524    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
33525        // COMMENT this
33526        self.write_keyword("COMMENT");
33527        self.write_space();
33528        self.generate_expression(&e.this)?;
33529        Ok(())
33530    }
33531
33532    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
33533        // [this::]expression
33534        if let Some(this) = &e.this {
33535            self.generate_expression(this)?;
33536            self.write("::");
33537        }
33538        self.generate_expression(&e.expression)?;
33539        Ok(())
33540    }
33541
33542    fn generate_search(&mut self, e: &Search) -> Result<()> {
33543        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
33544        self.write_keyword("SEARCH");
33545        self.write("(");
33546        self.generate_expression(&e.this)?;
33547        self.write(", ");
33548        self.generate_expression(&e.expression)?;
33549        if let Some(json_scope) = &e.json_scope {
33550            self.write(", ");
33551            self.generate_expression(json_scope)?;
33552        }
33553        if let Some(analyzer) = &e.analyzer {
33554            self.write(", ");
33555            self.generate_expression(analyzer)?;
33556        }
33557        if let Some(analyzer_options) = &e.analyzer_options {
33558            self.write(", ");
33559            self.generate_expression(analyzer_options)?;
33560        }
33561        if let Some(search_mode) = &e.search_mode {
33562            self.write(", ");
33563            self.generate_expression(search_mode)?;
33564        }
33565        self.write(")");
33566        Ok(())
33567    }
33568
33569    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
33570        // SEARCH_IP(this, expression)
33571        self.write_keyword("SEARCH_IP");
33572        self.write("(");
33573        self.generate_expression(&e.this)?;
33574        self.write(", ");
33575        self.generate_expression(&e.expression)?;
33576        self.write(")");
33577        Ok(())
33578    }
33579
33580    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
33581        // SECURITY this
33582        self.write_keyword("SECURITY");
33583        self.write_space();
33584        self.generate_expression(&e.this)?;
33585        Ok(())
33586    }
33587
33588    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
33589        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
33590        self.write("SEMANTIC_VIEW(");
33591
33592        if self.config.pretty {
33593            // Pretty print: each clause on its own line
33594            self.write_newline();
33595            self.indent_level += 1;
33596            self.write_indent();
33597            self.generate_expression(&e.this)?;
33598
33599            if let Some(metrics) = &e.metrics {
33600                self.write_newline();
33601                self.write_indent();
33602                self.write_keyword("METRICS");
33603                self.write_space();
33604                self.generate_semantic_view_tuple(metrics)?;
33605            }
33606            if let Some(dimensions) = &e.dimensions {
33607                self.write_newline();
33608                self.write_indent();
33609                self.write_keyword("DIMENSIONS");
33610                self.write_space();
33611                self.generate_semantic_view_tuple(dimensions)?;
33612            }
33613            if let Some(facts) = &e.facts {
33614                self.write_newline();
33615                self.write_indent();
33616                self.write_keyword("FACTS");
33617                self.write_space();
33618                self.generate_semantic_view_tuple(facts)?;
33619            }
33620            if let Some(where_) = &e.where_ {
33621                self.write_newline();
33622                self.write_indent();
33623                self.write_keyword("WHERE");
33624                self.write_space();
33625                self.generate_expression(where_)?;
33626            }
33627            self.write_newline();
33628            self.indent_level -= 1;
33629            self.write_indent();
33630        } else {
33631            // Compact: all on one line
33632            self.generate_expression(&e.this)?;
33633            if let Some(metrics) = &e.metrics {
33634                self.write_space();
33635                self.write_keyword("METRICS");
33636                self.write_space();
33637                self.generate_semantic_view_tuple(metrics)?;
33638            }
33639            if let Some(dimensions) = &e.dimensions {
33640                self.write_space();
33641                self.write_keyword("DIMENSIONS");
33642                self.write_space();
33643                self.generate_semantic_view_tuple(dimensions)?;
33644            }
33645            if let Some(facts) = &e.facts {
33646                self.write_space();
33647                self.write_keyword("FACTS");
33648                self.write_space();
33649                self.generate_semantic_view_tuple(facts)?;
33650            }
33651            if let Some(where_) = &e.where_ {
33652                self.write_space();
33653                self.write_keyword("WHERE");
33654                self.write_space();
33655                self.generate_expression(where_)?;
33656            }
33657        }
33658        self.write(")");
33659        Ok(())
33660    }
33661
33662    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
33663    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
33664        if let Expression::Tuple(t) = expr {
33665            for (i, e) in t.expressions.iter().enumerate() {
33666                if i > 0 {
33667                    self.write(", ");
33668                }
33669                self.generate_expression(e)?;
33670            }
33671        } else {
33672            self.generate_expression(expr)?;
33673        }
33674        Ok(())
33675    }
33676
33677    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
33678        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
33679        if let Some(start) = &e.start {
33680            self.write_keyword("START WITH");
33681            self.write_space();
33682            self.generate_expression(start)?;
33683        }
33684        if let Some(increment) = &e.increment {
33685            self.write_space();
33686            self.write_keyword("INCREMENT BY");
33687            self.write_space();
33688            self.generate_expression(increment)?;
33689        }
33690        if let Some(minvalue) = &e.minvalue {
33691            self.write_space();
33692            self.write_keyword("MINVALUE");
33693            self.write_space();
33694            self.generate_expression(minvalue)?;
33695        }
33696        if let Some(maxvalue) = &e.maxvalue {
33697            self.write_space();
33698            self.write_keyword("MAXVALUE");
33699            self.write_space();
33700            self.generate_expression(maxvalue)?;
33701        }
33702        if let Some(cache) = &e.cache {
33703            self.write_space();
33704            self.write_keyword("CACHE");
33705            self.write_space();
33706            self.generate_expression(cache)?;
33707        }
33708        if let Some(owned) = &e.owned {
33709            self.write_space();
33710            self.write_keyword("OWNED BY");
33711            self.write_space();
33712            self.generate_expression(owned)?;
33713        }
33714        for opt in &e.options {
33715            self.write_space();
33716            self.generate_expression(opt)?;
33717        }
33718        Ok(())
33719    }
33720
33721    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
33722        // [WITH] SERDEPROPERTIES (expressions)
33723        if e.with_.is_some() {
33724            self.write_keyword("WITH");
33725            self.write_space();
33726        }
33727        self.write_keyword("SERDEPROPERTIES");
33728        self.write(" (");
33729        for (i, expr) in e.expressions.iter().enumerate() {
33730            if i > 0 {
33731                self.write(", ");
33732            }
33733            // Generate key=value without spaces around =
33734            match expr {
33735                Expression::Eq(eq) => {
33736                    self.generate_expression(&eq.left)?;
33737                    self.write("=");
33738                    self.generate_expression(&eq.right)?;
33739                }
33740                _ => self.generate_expression(expr)?,
33741            }
33742        }
33743        self.write(")");
33744        Ok(())
33745    }
33746
33747    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
33748        // @@[kind.]this
33749        self.write("@@");
33750        if let Some(kind) = &e.kind {
33751            self.write(kind);
33752            self.write(".");
33753        }
33754        self.generate_expression(&e.this)?;
33755        Ok(())
33756    }
33757
33758    fn generate_set(&mut self, e: &Set) -> Result<()> {
33759        // SET/UNSET [TAG] expressions
33760        if e.unset.is_some() {
33761            self.write_keyword("UNSET");
33762        } else {
33763            self.write_keyword("SET");
33764        }
33765        if e.tag.is_some() {
33766            self.write_space();
33767            self.write_keyword("TAG");
33768        }
33769        if !e.expressions.is_empty() {
33770            self.write_space();
33771            for (i, expr) in e.expressions.iter().enumerate() {
33772                if i > 0 {
33773                    self.write(", ");
33774                }
33775                self.generate_expression(expr)?;
33776            }
33777        }
33778        Ok(())
33779    }
33780
33781    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
33782        // SET this or SETCONFIG this
33783        self.write_keyword("SET");
33784        self.write_space();
33785        self.generate_expression(&e.this)?;
33786        Ok(())
33787    }
33788
33789    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
33790        // [kind] name = value
33791        if let Some(kind) = &e.kind {
33792            self.write_keyword(kind);
33793            self.write_space();
33794        }
33795        self.generate_expression(&e.name)?;
33796        self.write(" = ");
33797        self.generate_expression(&e.value)?;
33798        Ok(())
33799    }
33800
33801    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
33802        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
33803        if let Some(with_) = &e.with_ {
33804            self.generate_expression(with_)?;
33805            self.write_space();
33806        }
33807        self.generate_expression(&e.this)?;
33808        self.write_space();
33809        // kind should be UNION, INTERSECT, EXCEPT, etc.
33810        if let Some(kind) = &e.kind {
33811            self.write_keyword(kind);
33812        }
33813        if e.distinct {
33814            self.write_space();
33815            self.write_keyword("DISTINCT");
33816        } else {
33817            self.write_space();
33818            self.write_keyword("ALL");
33819        }
33820        if e.by_name.is_some() {
33821            self.write_space();
33822            self.write_keyword("BY NAME");
33823        }
33824        self.write_space();
33825        self.generate_expression(&e.expression)?;
33826        Ok(())
33827    }
33828
33829    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
33830        // SET or MULTISET
33831        if e.multi.is_some() {
33832            self.write_keyword("MULTISET");
33833        } else {
33834            self.write_keyword("SET");
33835        }
33836        Ok(())
33837    }
33838
33839    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
33840        // SETTINGS expressions
33841        self.write_keyword("SETTINGS");
33842        if self.config.pretty && e.expressions.len() > 1 {
33843            // Pretty print: each setting on its own line, indented
33844            self.indent_level += 1;
33845            for (i, expr) in e.expressions.iter().enumerate() {
33846                if i > 0 {
33847                    self.write(",");
33848                }
33849                self.write_newline();
33850                self.write_indent();
33851                self.generate_expression(expr)?;
33852            }
33853            self.indent_level -= 1;
33854        } else {
33855            self.write_space();
33856            for (i, expr) in e.expressions.iter().enumerate() {
33857                if i > 0 {
33858                    self.write(", ");
33859                }
33860                self.generate_expression(expr)?;
33861            }
33862        }
33863        Ok(())
33864    }
33865
33866    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
33867        // SHARING = this
33868        self.write_keyword("SHARING");
33869        if let Some(this) = &e.this {
33870            self.write(" = ");
33871            self.generate_expression(this)?;
33872        }
33873        Ok(())
33874    }
33875
33876    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
33877        // Python array slicing: begin:end:step
33878        if let Some(begin) = &e.this {
33879            self.generate_expression(begin)?;
33880        }
33881        self.write(":");
33882        if let Some(end) = &e.expression {
33883            self.generate_expression(end)?;
33884        }
33885        if let Some(step) = &e.step {
33886            self.write(":");
33887            self.generate_expression(step)?;
33888        }
33889        Ok(())
33890    }
33891
33892    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
33893        // SORT_ARRAY(this, asc)
33894        self.write_keyword("SORT_ARRAY");
33895        self.write("(");
33896        self.generate_expression(&e.this)?;
33897        if let Some(asc) = &e.asc {
33898            self.write(", ");
33899            self.generate_expression(asc)?;
33900        }
33901        self.write(")");
33902        Ok(())
33903    }
33904
33905    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
33906        // SORT BY expressions
33907        self.write_keyword("SORT BY");
33908        self.write_space();
33909        for (i, expr) in e.expressions.iter().enumerate() {
33910            if i > 0 {
33911                self.write(", ");
33912            }
33913            self.generate_ordered(expr)?;
33914        }
33915        Ok(())
33916    }
33917
33918    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
33919        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
33920        if e.compound.is_some() {
33921            self.write_keyword("COMPOUND");
33922            self.write_space();
33923        }
33924        self.write_keyword("SORTKEY");
33925        self.write("(");
33926        // If this is a Tuple, unwrap its contents to avoid double parentheses
33927        if let Expression::Tuple(t) = e.this.as_ref() {
33928            for (i, expr) in t.expressions.iter().enumerate() {
33929                if i > 0 {
33930                    self.write(", ");
33931                }
33932                self.generate_expression(expr)?;
33933            }
33934        } else {
33935            self.generate_expression(&e.this)?;
33936        }
33937        self.write(")");
33938        Ok(())
33939    }
33940
33941    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
33942        // SPLIT_PART(this, delimiter, part_index)
33943        self.write_keyword("SPLIT_PART");
33944        self.write("(");
33945        self.generate_expression(&e.this)?;
33946        if let Some(delimiter) = &e.delimiter {
33947            self.write(", ");
33948            self.generate_expression(delimiter)?;
33949        }
33950        if let Some(part_index) = &e.part_index {
33951            self.write(", ");
33952            self.generate_expression(part_index)?;
33953        }
33954        self.write(")");
33955        Ok(())
33956    }
33957
33958    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
33959        // READS SQL DATA or MODIFIES SQL DATA, etc.
33960        self.generate_expression(&e.this)?;
33961        Ok(())
33962    }
33963
33964    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
33965        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
33966        self.write_keyword("SQL SECURITY");
33967        self.write_space();
33968        self.generate_expression(&e.this)?;
33969        Ok(())
33970    }
33971
33972    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
33973        // ST_DISTANCE(this, expression, [use_spheroid])
33974        self.write_keyword("ST_DISTANCE");
33975        self.write("(");
33976        self.generate_expression(&e.this)?;
33977        self.write(", ");
33978        self.generate_expression(&e.expression)?;
33979        if let Some(use_spheroid) = &e.use_spheroid {
33980            self.write(", ");
33981            self.generate_expression(use_spheroid)?;
33982        }
33983        self.write(")");
33984        Ok(())
33985    }
33986
33987    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
33988        // ST_POINT(this, expression)
33989        self.write_keyword("ST_POINT");
33990        self.write("(");
33991        self.generate_expression(&e.this)?;
33992        self.write(", ");
33993        self.generate_expression(&e.expression)?;
33994        self.write(")");
33995        Ok(())
33996    }
33997
33998    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
33999        // IMMUTABLE, STABLE, VOLATILE
34000        self.generate_expression(&e.this)?;
34001        Ok(())
34002    }
34003
34004    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
34005        // STANDARD_HASH(this, [expression])
34006        self.write_keyword("STANDARD_HASH");
34007        self.write("(");
34008        self.generate_expression(&e.this)?;
34009        if let Some(expression) = &e.expression {
34010            self.write(", ");
34011            self.generate_expression(expression)?;
34012        }
34013        self.write(")");
34014        Ok(())
34015    }
34016
34017    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
34018        // STORED BY this
34019        self.write_keyword("STORED BY");
34020        self.write_space();
34021        self.generate_expression(&e.this)?;
34022        Ok(())
34023    }
34024
34025    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
34026        // STRPOS(this, substr) or STRPOS(this, substr, position)
34027        // Different dialects have different function names
34028        use crate::dialects::DialectType;
34029        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
34030            // Snowflake: CHARINDEX(substr, str[, position])
34031            self.write_keyword("CHARINDEX");
34032            self.write("(");
34033            if let Some(substr) = &e.substr {
34034                self.generate_expression(substr)?;
34035                self.write(", ");
34036            }
34037            self.generate_expression(&e.this)?;
34038            if let Some(position) = &e.position {
34039                self.write(", ");
34040                self.generate_expression(position)?;
34041            }
34042            self.write(")");
34043        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34044            self.write_keyword("POSITION");
34045            self.write("(");
34046            self.generate_expression(&e.this)?;
34047            if let Some(substr) = &e.substr {
34048                self.write(", ");
34049                self.generate_expression(substr)?;
34050            }
34051            if let Some(position) = &e.position {
34052                self.write(", ");
34053                self.generate_expression(position)?;
34054            }
34055            if let Some(occurrence) = &e.occurrence {
34056                self.write(", ");
34057                self.generate_expression(occurrence)?;
34058            }
34059            self.write(")");
34060        } else if matches!(
34061            self.config.dialect,
34062            Some(DialectType::SQLite)
34063                | Some(DialectType::Oracle)
34064                | Some(DialectType::BigQuery)
34065                | Some(DialectType::Teradata)
34066        ) {
34067            self.write_keyword("INSTR");
34068            self.write("(");
34069            self.generate_expression(&e.this)?;
34070            if let Some(substr) = &e.substr {
34071                self.write(", ");
34072                self.generate_expression(substr)?;
34073            }
34074            if let Some(position) = &e.position {
34075                self.write(", ");
34076                self.generate_expression(position)?;
34077            } else if e.occurrence.is_some() {
34078                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
34079                // Default start position is 1
34080                self.write(", 1");
34081            }
34082            if let Some(occurrence) = &e.occurrence {
34083                self.write(", ");
34084                self.generate_expression(occurrence)?;
34085            }
34086            self.write(")");
34087        } else if matches!(
34088            self.config.dialect,
34089            Some(DialectType::MySQL)
34090                | Some(DialectType::SingleStore)
34091                | Some(DialectType::Doris)
34092                | Some(DialectType::StarRocks)
34093                | Some(DialectType::Hive)
34094                | Some(DialectType::Spark)
34095                | Some(DialectType::Databricks)
34096        ) {
34097            // LOCATE(substr, str[, position]) - substr first
34098            self.write_keyword("LOCATE");
34099            self.write("(");
34100            if let Some(substr) = &e.substr {
34101                self.generate_expression(substr)?;
34102                self.write(", ");
34103            }
34104            self.generate_expression(&e.this)?;
34105            if let Some(position) = &e.position {
34106                self.write(", ");
34107                self.generate_expression(position)?;
34108            }
34109            self.write(")");
34110        } else if matches!(self.config.dialect, Some(DialectType::TSQL)) {
34111            // CHARINDEX(substr, str[, position])
34112            self.write_keyword("CHARINDEX");
34113            self.write("(");
34114            if let Some(substr) = &e.substr {
34115                self.generate_expression(substr)?;
34116                self.write(", ");
34117            }
34118            self.generate_expression(&e.this)?;
34119            if let Some(position) = &e.position {
34120                self.write(", ");
34121                self.generate_expression(position)?;
34122            }
34123            self.write(")");
34124        } else if matches!(
34125            self.config.dialect,
34126            Some(DialectType::PostgreSQL)
34127                | Some(DialectType::Materialize)
34128                | Some(DialectType::RisingWave)
34129                | Some(DialectType::Redshift)
34130        ) {
34131            // POSITION(substr IN str) syntax
34132            self.write_keyword("POSITION");
34133            self.write("(");
34134            if let Some(substr) = &e.substr {
34135                self.generate_expression(substr)?;
34136                self.write(" IN ");
34137            }
34138            self.generate_expression(&e.this)?;
34139            self.write(")");
34140        } else {
34141            self.write_keyword("STRPOS");
34142            self.write("(");
34143            self.generate_expression(&e.this)?;
34144            if let Some(substr) = &e.substr {
34145                self.write(", ");
34146                self.generate_expression(substr)?;
34147            }
34148            if let Some(position) = &e.position {
34149                self.write(", ");
34150                self.generate_expression(position)?;
34151            }
34152            if let Some(occurrence) = &e.occurrence {
34153                self.write(", ");
34154                self.generate_expression(occurrence)?;
34155            }
34156            self.write(")");
34157        }
34158        Ok(())
34159    }
34160
34161    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
34162        match self.config.dialect {
34163            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
34164                // TO_DATE(this, java_format)
34165                self.write_keyword("TO_DATE");
34166                self.write("(");
34167                self.generate_expression(&e.this)?;
34168                if let Some(format) = &e.format {
34169                    self.write(", '");
34170                    self.write(&Self::strftime_to_java_format(format));
34171                    self.write("'");
34172                }
34173                self.write(")");
34174            }
34175            Some(DialectType::DuckDB) => {
34176                // CAST(STRPTIME(this, format) AS DATE)
34177                self.write_keyword("CAST");
34178                self.write("(");
34179                self.write_keyword("STRPTIME");
34180                self.write("(");
34181                self.generate_expression(&e.this)?;
34182                if let Some(format) = &e.format {
34183                    self.write(", '");
34184                    self.write(format);
34185                    self.write("'");
34186                }
34187                self.write(")");
34188                self.write_keyword(" AS ");
34189                self.write_keyword("DATE");
34190                self.write(")");
34191            }
34192            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
34193                // TO_DATE(this, pg_format)
34194                self.write_keyword("TO_DATE");
34195                self.write("(");
34196                self.generate_expression(&e.this)?;
34197                if let Some(format) = &e.format {
34198                    self.write(", '");
34199                    self.write(&Self::strftime_to_postgres_format(format));
34200                    self.write("'");
34201                }
34202                self.write(")");
34203            }
34204            Some(DialectType::BigQuery) => {
34205                // PARSE_DATE(format, this) - note: format comes first for BigQuery
34206                self.write_keyword("PARSE_DATE");
34207                self.write("(");
34208                if let Some(format) = &e.format {
34209                    self.write("'");
34210                    self.write(format);
34211                    self.write("'");
34212                    self.write(", ");
34213                }
34214                self.generate_expression(&e.this)?;
34215                self.write(")");
34216            }
34217            Some(DialectType::Teradata) => {
34218                // CAST(this AS DATE FORMAT 'teradata_fmt')
34219                self.write_keyword("CAST");
34220                self.write("(");
34221                self.generate_expression(&e.this)?;
34222                self.write_keyword(" AS ");
34223                self.write_keyword("DATE");
34224                if let Some(format) = &e.format {
34225                    self.write_keyword(" FORMAT ");
34226                    self.write("'");
34227                    self.write(&Self::strftime_to_teradata_format(format));
34228                    self.write("'");
34229                }
34230                self.write(")");
34231            }
34232            _ => {
34233                // STR_TO_DATE(this, format) - MySQL default
34234                self.write_keyword("STR_TO_DATE");
34235                self.write("(");
34236                self.generate_expression(&e.this)?;
34237                if let Some(format) = &e.format {
34238                    self.write(", '");
34239                    self.write(format);
34240                    self.write("'");
34241                }
34242                self.write(")");
34243            }
34244        }
34245        Ok(())
34246    }
34247
34248    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
34249    fn strftime_to_teradata_format(fmt: &str) -> String {
34250        let mut result = String::with_capacity(fmt.len() * 2);
34251        let bytes = fmt.as_bytes();
34252        let len = bytes.len();
34253        let mut i = 0;
34254        while i < len {
34255            if bytes[i] == b'%' && i + 1 < len {
34256                let replacement = match bytes[i + 1] {
34257                    b'Y' => "YYYY",
34258                    b'y' => "YY",
34259                    b'm' => "MM",
34260                    b'B' => "MMMM",
34261                    b'b' => "MMM",
34262                    b'd' => "DD",
34263                    b'j' => "DDD",
34264                    b'H' => "HH",
34265                    b'M' => "MI",
34266                    b'S' => "SS",
34267                    b'f' => "SSSSSS",
34268                    b'A' => "EEEE",
34269                    b'a' => "EEE",
34270                    _ => {
34271                        result.push('%');
34272                        i += 1;
34273                        continue;
34274                    }
34275                };
34276                result.push_str(replacement);
34277                i += 2;
34278            } else {
34279                result.push(bytes[i] as char);
34280                i += 1;
34281            }
34282        }
34283        result
34284    }
34285
34286    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
34287    /// Public static version for use by other modules
34288    pub fn strftime_to_java_format_static(fmt: &str) -> String {
34289        Self::strftime_to_java_format(fmt)
34290    }
34291
34292    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
34293    fn strftime_to_java_format(fmt: &str) -> String {
34294        let mut result = String::with_capacity(fmt.len() * 2);
34295        let bytes = fmt.as_bytes();
34296        let len = bytes.len();
34297        let mut i = 0;
34298        while i < len {
34299            if bytes[i] == b'%' && i + 1 < len {
34300                // Check for non-padded variants (%-X)
34301                if bytes[i + 1] == b'-' && i + 2 < len {
34302                    let replacement = match bytes[i + 2] {
34303                        b'd' => "d",
34304                        b'm' => "M",
34305                        b'H' => "H",
34306                        b'M' => "m",
34307                        b'S' => "s",
34308                        _ => {
34309                            result.push('%');
34310                            i += 1;
34311                            continue;
34312                        }
34313                    };
34314                    result.push_str(replacement);
34315                    i += 3;
34316                } else {
34317                    let replacement = match bytes[i + 1] {
34318                        b'Y' => "yyyy",
34319                        b'y' => "yy",
34320                        b'm' => "MM",
34321                        b'B' => "MMMM",
34322                        b'b' => "MMM",
34323                        b'd' => "dd",
34324                        b'j' => "DDD",
34325                        b'H' => "HH",
34326                        b'M' => "mm",
34327                        b'S' => "ss",
34328                        b'f' => "SSSSSS",
34329                        b'A' => "EEEE",
34330                        b'a' => "EEE",
34331                        _ => {
34332                            result.push('%');
34333                            i += 1;
34334                            continue;
34335                        }
34336                    };
34337                    result.push_str(replacement);
34338                    i += 2;
34339                }
34340            } else {
34341                result.push(bytes[i] as char);
34342                i += 1;
34343            }
34344        }
34345        result
34346    }
34347
34348    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
34349    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
34350    fn strftime_to_tsql_format(fmt: &str) -> String {
34351        let mut result = String::with_capacity(fmt.len() * 2);
34352        let bytes = fmt.as_bytes();
34353        let len = bytes.len();
34354        let mut i = 0;
34355        while i < len {
34356            if bytes[i] == b'%' && i + 1 < len {
34357                // Check for non-padded variants (%-X)
34358                if bytes[i + 1] == b'-' && i + 2 < len {
34359                    let replacement = match bytes[i + 2] {
34360                        b'd' => "d",
34361                        b'm' => "M",
34362                        b'H' => "H",
34363                        b'M' => "m",
34364                        b'S' => "s",
34365                        _ => {
34366                            result.push('%');
34367                            i += 1;
34368                            continue;
34369                        }
34370                    };
34371                    result.push_str(replacement);
34372                    i += 3;
34373                } else {
34374                    let replacement = match bytes[i + 1] {
34375                        b'Y' => "yyyy",
34376                        b'y' => "yy",
34377                        b'm' => "MM",
34378                        b'B' => "MMMM",
34379                        b'b' => "MMM",
34380                        b'd' => "dd",
34381                        b'j' => "DDD",
34382                        b'H' => "HH",
34383                        b'M' => "mm",
34384                        b'S' => "ss",
34385                        b'f' => "ffffff",
34386                        b'A' => "dddd",
34387                        b'a' => "ddd",
34388                        _ => {
34389                            result.push('%');
34390                            i += 1;
34391                            continue;
34392                        }
34393                    };
34394                    result.push_str(replacement);
34395                    i += 2;
34396                }
34397            } else {
34398                result.push(bytes[i] as char);
34399                i += 1;
34400            }
34401        }
34402        result
34403    }
34404
34405    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
34406    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
34407    fn decompose_json_path(path: &str) -> Vec<String> {
34408        let mut parts = Vec::new();
34409        // Strip leading $ and optional .
34410        let path = if path.starts_with("$.") {
34411            &path[2..]
34412        } else if path.starts_with('$') {
34413            &path[1..]
34414        } else {
34415            path
34416        };
34417        if path.is_empty() {
34418            return parts;
34419        }
34420        let mut current = String::new();
34421        let chars: Vec<char> = path.chars().collect();
34422        let mut i = 0;
34423        while i < chars.len() {
34424            match chars[i] {
34425                '.' => {
34426                    if !current.is_empty() {
34427                        parts.push(current.clone());
34428                        current.clear();
34429                    }
34430                    i += 1;
34431                }
34432                '[' => {
34433                    if !current.is_empty() {
34434                        parts.push(current.clone());
34435                        current.clear();
34436                    }
34437                    i += 1;
34438                    // Read the content inside brackets
34439                    let mut bracket_content = String::new();
34440                    while i < chars.len() && chars[i] != ']' {
34441                        // Skip quotes inside brackets
34442                        if chars[i] == '"' || chars[i] == '\'' {
34443                            let quote = chars[i];
34444                            i += 1;
34445                            while i < chars.len() && chars[i] != quote {
34446                                bracket_content.push(chars[i]);
34447                                i += 1;
34448                            }
34449                            if i < chars.len() {
34450                                i += 1;
34451                            } // skip closing quote
34452                        } else {
34453                            bracket_content.push(chars[i]);
34454                            i += 1;
34455                        }
34456                    }
34457                    if i < chars.len() {
34458                        i += 1;
34459                    } // skip ]
34460                      // Skip wildcard [*] - don't add as a part
34461                    if bracket_content != "*" {
34462                        parts.push(bracket_content);
34463                    }
34464                }
34465                _ => {
34466                    current.push(chars[i]);
34467                    i += 1;
34468                }
34469            }
34470        }
34471        if !current.is_empty() {
34472            parts.push(current);
34473        }
34474        parts
34475    }
34476
34477    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
34478    fn strftime_to_postgres_format(fmt: &str) -> String {
34479        let mut result = String::with_capacity(fmt.len() * 2);
34480        let bytes = fmt.as_bytes();
34481        let len = bytes.len();
34482        let mut i = 0;
34483        while i < len {
34484            if bytes[i] == b'%' && i + 1 < len {
34485                // Check for non-padded variants (%-X)
34486                if bytes[i + 1] == b'-' && i + 2 < len {
34487                    let replacement = match bytes[i + 2] {
34488                        b'd' => "FMDD",
34489                        b'm' => "FMMM",
34490                        b'H' => "FMHH24",
34491                        b'M' => "FMMI",
34492                        b'S' => "FMSS",
34493                        _ => {
34494                            result.push('%');
34495                            i += 1;
34496                            continue;
34497                        }
34498                    };
34499                    result.push_str(replacement);
34500                    i += 3;
34501                } else {
34502                    let replacement = match bytes[i + 1] {
34503                        b'Y' => "YYYY",
34504                        b'y' => "YY",
34505                        b'm' => "MM",
34506                        b'B' => "Month",
34507                        b'b' => "Mon",
34508                        b'd' => "DD",
34509                        b'j' => "DDD",
34510                        b'H' => "HH24",
34511                        b'M' => "MI",
34512                        b'S' => "SS",
34513                        b'f' => "US",
34514                        b'A' => "Day",
34515                        b'a' => "Dy",
34516                        _ => {
34517                            result.push('%');
34518                            i += 1;
34519                            continue;
34520                        }
34521                    };
34522                    result.push_str(replacement);
34523                    i += 2;
34524                }
34525            } else {
34526                result.push(bytes[i] as char);
34527                i += 1;
34528            }
34529        }
34530        result
34531    }
34532
34533    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
34534    fn strftime_to_snowflake_format(fmt: &str) -> String {
34535        let mut result = String::with_capacity(fmt.len() * 2);
34536        let bytes = fmt.as_bytes();
34537        let len = bytes.len();
34538        let mut i = 0;
34539        while i < len {
34540            if bytes[i] == b'%' && i + 1 < len {
34541                // Check for non-padded variants (%-X)
34542                if bytes[i + 1] == b'-' && i + 2 < len {
34543                    let replacement = match bytes[i + 2] {
34544                        b'd' => "dd",
34545                        b'm' => "mm",
34546                        _ => {
34547                            result.push('%');
34548                            i += 1;
34549                            continue;
34550                        }
34551                    };
34552                    result.push_str(replacement);
34553                    i += 3;
34554                } else {
34555                    let replacement = match bytes[i + 1] {
34556                        b'Y' => "yyyy",
34557                        b'y' => "yy",
34558                        b'm' => "mm",
34559                        b'd' => "DD",
34560                        b'H' => "hh24",
34561                        b'M' => "mi",
34562                        b'S' => "ss",
34563                        b'f' => "ff",
34564                        _ => {
34565                            result.push('%');
34566                            i += 1;
34567                            continue;
34568                        }
34569                    };
34570                    result.push_str(replacement);
34571                    i += 2;
34572                }
34573            } else {
34574                result.push(bytes[i] as char);
34575                i += 1;
34576            }
34577        }
34578        result
34579    }
34580
34581    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
34582        // STR_TO_MAP(this, pair_delim, key_value_delim)
34583        self.write_keyword("STR_TO_MAP");
34584        self.write("(");
34585        self.generate_expression(&e.this)?;
34586        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
34587        let needs_defaults = matches!(
34588            self.config.dialect,
34589            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
34590        );
34591        if let Some(pair_delim) = &e.pair_delim {
34592            self.write(", ");
34593            self.generate_expression(pair_delim)?;
34594        } else if needs_defaults {
34595            self.write(", ','");
34596        }
34597        if let Some(key_value_delim) = &e.key_value_delim {
34598            self.write(", ");
34599            self.generate_expression(key_value_delim)?;
34600        } else if needs_defaults {
34601            self.write(", ':'");
34602        }
34603        self.write(")");
34604        Ok(())
34605    }
34606
34607    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
34608        // Detect format style: strftime (starts with %) vs Snowflake/Java
34609        let is_strftime = e.format.contains('%');
34610        // Helper: get strftime format from whatever style is stored
34611        let to_strftime = |f: &str| -> String {
34612            if is_strftime {
34613                f.to_string()
34614            } else {
34615                Self::snowflake_format_to_strftime(f)
34616            }
34617        };
34618        // Helper: get Java format
34619        let to_java = |f: &str| -> String {
34620            if is_strftime {
34621                Self::strftime_to_java_format(f)
34622            } else {
34623                Self::snowflake_format_to_spark(f)
34624            }
34625        };
34626        // Helper: get PG format
34627        let to_pg = |f: &str| -> String {
34628            if is_strftime {
34629                Self::strftime_to_postgres_format(f)
34630            } else {
34631                Self::convert_strptime_to_postgres_format(f)
34632            }
34633        };
34634
34635        match self.config.dialect {
34636            Some(DialectType::Exasol) => {
34637                self.write_keyword("TO_DATE");
34638                self.write("(");
34639                self.generate_expression(&e.this)?;
34640                self.write(", '");
34641                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
34642                self.write("'");
34643                self.write(")");
34644            }
34645            Some(DialectType::BigQuery) => {
34646                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
34647                let fmt = to_strftime(&e.format);
34648                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
34649                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
34650                self.write_keyword("PARSE_TIMESTAMP");
34651                self.write("('");
34652                self.write(&fmt);
34653                self.write("', ");
34654                self.generate_expression(&e.this)?;
34655                self.write(")");
34656            }
34657            Some(DialectType::Hive) => {
34658                // Hive: CAST(x AS TIMESTAMP) for simple date formats
34659                // Check both the raw format and the converted format (in case it's already Java)
34660                let java_fmt = to_java(&e.format);
34661                if java_fmt == "yyyy-MM-dd HH:mm:ss"
34662                    || java_fmt == "yyyy-MM-dd"
34663                    || e.format == "yyyy-MM-dd HH:mm:ss"
34664                    || e.format == "yyyy-MM-dd"
34665                {
34666                    self.write_keyword("CAST");
34667                    self.write("(");
34668                    self.generate_expression(&e.this)?;
34669                    self.write(" ");
34670                    self.write_keyword("AS TIMESTAMP");
34671                    self.write(")");
34672                } else {
34673                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
34674                    self.write_keyword("CAST");
34675                    self.write("(");
34676                    self.write_keyword("FROM_UNIXTIME");
34677                    self.write("(");
34678                    self.write_keyword("UNIX_TIMESTAMP");
34679                    self.write("(");
34680                    self.generate_expression(&e.this)?;
34681                    self.write(", '");
34682                    self.write(&java_fmt);
34683                    self.write("')");
34684                    self.write(") ");
34685                    self.write_keyword("AS TIMESTAMP");
34686                    self.write(")");
34687                }
34688            }
34689            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
34690                // Spark: TO_TIMESTAMP(value, java_format)
34691                let java_fmt = to_java(&e.format);
34692                self.write_keyword("TO_TIMESTAMP");
34693                self.write("(");
34694                self.generate_expression(&e.this)?;
34695                self.write(", '");
34696                self.write(&java_fmt);
34697                self.write("')");
34698            }
34699            Some(DialectType::MySQL) => {
34700                // MySQL: STR_TO_DATE(value, format)
34701                let mut fmt = to_strftime(&e.format);
34702                // MySQL uses %e for non-padded day, %T for %H:%M:%S
34703                fmt = fmt.replace("%-d", "%e");
34704                fmt = fmt.replace("%-m", "%c");
34705                fmt = fmt.replace("%H:%M:%S", "%T");
34706                self.write_keyword("STR_TO_DATE");
34707                self.write("(");
34708                self.generate_expression(&e.this)?;
34709                self.write(", '");
34710                self.write(&fmt);
34711                self.write("')");
34712            }
34713            Some(DialectType::Drill) => {
34714                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
34715                let java_fmt = to_java(&e.format);
34716                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
34717                let java_fmt = java_fmt.replace('T', "''T''");
34718                self.write_keyword("TO_TIMESTAMP");
34719                self.write("(");
34720                self.generate_expression(&e.this)?;
34721                self.write(", '");
34722                self.write(&java_fmt);
34723                self.write("')");
34724            }
34725            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
34726                // Presto: DATE_PARSE(value, strftime_format)
34727                let mut fmt = to_strftime(&e.format);
34728                // Presto uses %e for non-padded day, %T for %H:%M:%S
34729                fmt = fmt.replace("%-d", "%e");
34730                fmt = fmt.replace("%-m", "%c");
34731                fmt = fmt.replace("%H:%M:%S", "%T");
34732                self.write_keyword("DATE_PARSE");
34733                self.write("(");
34734                self.generate_expression(&e.this)?;
34735                self.write(", '");
34736                self.write(&fmt);
34737                self.write("')");
34738            }
34739            Some(DialectType::DuckDB) => {
34740                // DuckDB: STRPTIME(value, strftime_format)
34741                let fmt = to_strftime(&e.format);
34742                self.write_keyword("STRPTIME");
34743                self.write("(");
34744                self.generate_expression(&e.this)?;
34745                self.write(", '");
34746                self.write(&fmt);
34747                self.write("')");
34748            }
34749            Some(DialectType::PostgreSQL)
34750            | Some(DialectType::Redshift)
34751            | Some(DialectType::Materialize) => {
34752                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
34753                let pg_fmt = to_pg(&e.format);
34754                self.write_keyword("TO_TIMESTAMP");
34755                self.write("(");
34756                self.generate_expression(&e.this)?;
34757                self.write(", '");
34758                self.write(&pg_fmt);
34759                self.write("')");
34760            }
34761            Some(DialectType::Oracle) => {
34762                // Oracle: TO_TIMESTAMP(value, pg_format)
34763                let pg_fmt = to_pg(&e.format);
34764                self.write_keyword("TO_TIMESTAMP");
34765                self.write("(");
34766                self.generate_expression(&e.this)?;
34767                self.write(", '");
34768                self.write(&pg_fmt);
34769                self.write("')");
34770            }
34771            Some(DialectType::Snowflake) => {
34772                // Snowflake: TO_TIMESTAMP(value, format) - native format
34773                self.write_keyword("TO_TIMESTAMP");
34774                self.write("(");
34775                self.generate_expression(&e.this)?;
34776                self.write(", '");
34777                self.write(&e.format);
34778                self.write("')");
34779            }
34780            _ => {
34781                // Default: STR_TO_TIME(this, format)
34782                self.write_keyword("STR_TO_TIME");
34783                self.write("(");
34784                self.generate_expression(&e.this)?;
34785                self.write(", '");
34786                self.write(&e.format);
34787                self.write("'");
34788                self.write(")");
34789            }
34790        }
34791        Ok(())
34792    }
34793
34794    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
34795    fn snowflake_format_to_strftime(format: &str) -> String {
34796        let mut result = String::new();
34797        let chars: Vec<char> = format.chars().collect();
34798        let mut i = 0;
34799        while i < chars.len() {
34800            let remaining = &format[i..];
34801            if remaining.starts_with("yyyy") {
34802                result.push_str("%Y");
34803                i += 4;
34804            } else if remaining.starts_with("yy") {
34805                result.push_str("%y");
34806                i += 2;
34807            } else if remaining.starts_with("mmmm") {
34808                result.push_str("%B"); // full month name
34809                i += 4;
34810            } else if remaining.starts_with("mon") {
34811                result.push_str("%b"); // abbreviated month
34812                i += 3;
34813            } else if remaining.starts_with("mm") {
34814                result.push_str("%m");
34815                i += 2;
34816            } else if remaining.starts_with("DD") {
34817                result.push_str("%d");
34818                i += 2;
34819            } else if remaining.starts_with("dy") {
34820                result.push_str("%a"); // abbreviated day name
34821                i += 2;
34822            } else if remaining.starts_with("hh24") {
34823                result.push_str("%H");
34824                i += 4;
34825            } else if remaining.starts_with("hh12") {
34826                result.push_str("%I");
34827                i += 4;
34828            } else if remaining.starts_with("hh") {
34829                result.push_str("%H");
34830                i += 2;
34831            } else if remaining.starts_with("mi") {
34832                result.push_str("%M");
34833                i += 2;
34834            } else if remaining.starts_with("ss") {
34835                result.push_str("%S");
34836                i += 2;
34837            } else if remaining.starts_with("ff") {
34838                // Fractional seconds
34839                result.push_str("%f");
34840                i += 2;
34841                // Skip digits after ff (ff3, ff6, ff9)
34842                while i < chars.len() && chars[i].is_ascii_digit() {
34843                    i += 1;
34844                }
34845            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
34846                result.push_str("%p");
34847                i += 2;
34848            } else if remaining.starts_with("tz") {
34849                result.push_str("%Z");
34850                i += 2;
34851            } else {
34852                result.push(chars[i]);
34853                i += 1;
34854            }
34855        }
34856        result
34857    }
34858
34859    /// Convert Snowflake normalized format to Spark format (Java-style)
34860    fn snowflake_format_to_spark(format: &str) -> String {
34861        let mut result = String::new();
34862        let chars: Vec<char> = format.chars().collect();
34863        let mut i = 0;
34864        while i < chars.len() {
34865            let remaining = &format[i..];
34866            if remaining.starts_with("yyyy") {
34867                result.push_str("yyyy");
34868                i += 4;
34869            } else if remaining.starts_with("yy") {
34870                result.push_str("yy");
34871                i += 2;
34872            } else if remaining.starts_with("mmmm") {
34873                result.push_str("MMMM"); // full month name
34874                i += 4;
34875            } else if remaining.starts_with("mon") {
34876                result.push_str("MMM"); // abbreviated month
34877                i += 3;
34878            } else if remaining.starts_with("mm") {
34879                result.push_str("MM");
34880                i += 2;
34881            } else if remaining.starts_with("DD") {
34882                result.push_str("dd");
34883                i += 2;
34884            } else if remaining.starts_with("dy") {
34885                result.push_str("EEE"); // abbreviated day name
34886                i += 2;
34887            } else if remaining.starts_with("hh24") {
34888                result.push_str("HH");
34889                i += 4;
34890            } else if remaining.starts_with("hh12") {
34891                result.push_str("hh");
34892                i += 4;
34893            } else if remaining.starts_with("hh") {
34894                result.push_str("HH");
34895                i += 2;
34896            } else if remaining.starts_with("mi") {
34897                result.push_str("mm");
34898                i += 2;
34899            } else if remaining.starts_with("ss") {
34900                result.push_str("ss");
34901                i += 2;
34902            } else if remaining.starts_with("ff") {
34903                result.push_str("SSS"); // milliseconds
34904                i += 2;
34905                // Skip digits after ff
34906                while i < chars.len() && chars[i].is_ascii_digit() {
34907                    i += 1;
34908                }
34909            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
34910                result.push_str("a");
34911                i += 2;
34912            } else if remaining.starts_with("tz") {
34913                result.push_str("z");
34914                i += 2;
34915            } else {
34916                result.push(chars[i]);
34917                i += 1;
34918            }
34919        }
34920        result
34921    }
34922
34923    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
34924        match self.config.dialect {
34925            Some(DialectType::DuckDB) => {
34926                // DuckDB: EPOCH(STRPTIME(value, format))
34927                self.write_keyword("EPOCH");
34928                self.write("(");
34929                self.write_keyword("STRPTIME");
34930                self.write("(");
34931                if let Some(this) = &e.this {
34932                    self.generate_expression(this)?;
34933                }
34934                if let Some(format) = &e.format {
34935                    self.write(", '");
34936                    self.write(format);
34937                    self.write("'");
34938                }
34939                self.write("))");
34940            }
34941            Some(DialectType::Hive) => {
34942                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
34943                self.write_keyword("UNIX_TIMESTAMP");
34944                self.write("(");
34945                if let Some(this) = &e.this {
34946                    self.generate_expression(this)?;
34947                }
34948                if let Some(format) = &e.format {
34949                    let java_fmt = Self::strftime_to_java_format(format);
34950                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
34951                        self.write(", '");
34952                        self.write(&java_fmt);
34953                        self.write("'");
34954                    }
34955                }
34956                self.write(")");
34957            }
34958            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
34959                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
34960                self.write_keyword("UNIX_TIMESTAMP");
34961                self.write("(");
34962                if let Some(this) = &e.this {
34963                    self.generate_expression(this)?;
34964                }
34965                if let Some(format) = &e.format {
34966                    self.write(", '");
34967                    self.write(format);
34968                    self.write("'");
34969                }
34970                self.write(")");
34971            }
34972            Some(DialectType::Presto) | Some(DialectType::Trino) => {
34973                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
34974                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
34975                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
34976                let java_fmt = Self::strftime_to_java_format(c_fmt);
34977                self.write_keyword("TO_UNIXTIME");
34978                self.write("(");
34979                self.write_keyword("COALESCE");
34980                self.write("(");
34981                self.write_keyword("TRY");
34982                self.write("(");
34983                self.write_keyword("DATE_PARSE");
34984                self.write("(");
34985                self.write_keyword("CAST");
34986                self.write("(");
34987                if let Some(this) = &e.this {
34988                    self.generate_expression(this)?;
34989                }
34990                self.write(" ");
34991                self.write_keyword("AS VARCHAR");
34992                self.write("), '");
34993                self.write(c_fmt);
34994                self.write("')), ");
34995                self.write_keyword("PARSE_DATETIME");
34996                self.write("(");
34997                self.write_keyword("DATE_FORMAT");
34998                self.write("(");
34999                self.write_keyword("CAST");
35000                self.write("(");
35001                if let Some(this) = &e.this {
35002                    self.generate_expression(this)?;
35003                }
35004                self.write(" ");
35005                self.write_keyword("AS TIMESTAMP");
35006                self.write("), '");
35007                self.write(c_fmt);
35008                self.write("'), '");
35009                self.write(&java_fmt);
35010                self.write("')))");
35011            }
35012            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35013                // Spark: UNIX_TIMESTAMP(value, java_format)
35014                self.write_keyword("UNIX_TIMESTAMP");
35015                self.write("(");
35016                if let Some(this) = &e.this {
35017                    self.generate_expression(this)?;
35018                }
35019                if let Some(format) = &e.format {
35020                    let java_fmt = Self::strftime_to_java_format(format);
35021                    self.write(", '");
35022                    self.write(&java_fmt);
35023                    self.write("'");
35024                }
35025                self.write(")");
35026            }
35027            _ => {
35028                // Default: STR_TO_UNIX(this, format)
35029                self.write_keyword("STR_TO_UNIX");
35030                self.write("(");
35031                if let Some(this) = &e.this {
35032                    self.generate_expression(this)?;
35033                }
35034                if let Some(format) = &e.format {
35035                    self.write(", '");
35036                    self.write(format);
35037                    self.write("'");
35038                }
35039                self.write(")");
35040            }
35041        }
35042        Ok(())
35043    }
35044
35045    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
35046        // STRING_TO_ARRAY(this, delimiter, null_string)
35047        self.write_keyword("STRING_TO_ARRAY");
35048        self.write("(");
35049        self.generate_expression(&e.this)?;
35050        if let Some(expression) = &e.expression {
35051            self.write(", ");
35052            self.generate_expression(expression)?;
35053        }
35054        if let Some(null_val) = &e.null {
35055            self.write(", ");
35056            self.generate_expression(null_val)?;
35057        }
35058        self.write(")");
35059        Ok(())
35060    }
35061
35062    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
35063        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
35064            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
35065            self.write_keyword("OBJECT_CONSTRUCT");
35066            self.write("(");
35067            for (i, (name, expr)) in e.fields.iter().enumerate() {
35068                if i > 0 {
35069                    self.write(", ");
35070                }
35071                if let Some(name) = name {
35072                    self.write("'");
35073                    self.write(name);
35074                    self.write("'");
35075                    self.write(", ");
35076                } else {
35077                    self.write("'_");
35078                    self.write(&i.to_string());
35079                    self.write("'");
35080                    self.write(", ");
35081                }
35082                self.generate_expression(expr)?;
35083            }
35084            self.write(")");
35085        } else if self.config.struct_curly_brace_notation {
35086            // DuckDB-style: {'key': value, ...}
35087            self.write("{");
35088            for (i, (name, expr)) in e.fields.iter().enumerate() {
35089                if i > 0 {
35090                    self.write(", ");
35091                }
35092                if let Some(name) = name {
35093                    // Quote the key as a string literal
35094                    self.write("'");
35095                    self.write(name);
35096                    self.write("'");
35097                    self.write(": ");
35098                } else {
35099                    // Unnamed field: use positional key
35100                    self.write("'_");
35101                    self.write(&i.to_string());
35102                    self.write("'");
35103                    self.write(": ");
35104                }
35105                self.generate_expression(expr)?;
35106            }
35107            self.write("}");
35108        } else {
35109            // Standard SQL struct notation
35110            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
35111            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
35112            let value_as_name = matches!(
35113                self.config.dialect,
35114                Some(DialectType::BigQuery)
35115                    | Some(DialectType::Spark)
35116                    | Some(DialectType::Databricks)
35117                    | Some(DialectType::Hive)
35118            );
35119            self.write_keyword("STRUCT");
35120            self.write("(");
35121            for (i, (name, expr)) in e.fields.iter().enumerate() {
35122                if i > 0 {
35123                    self.write(", ");
35124                }
35125                if let Some(name) = name {
35126                    if value_as_name {
35127                        // STRUCT(value AS name)
35128                        self.generate_expression(expr)?;
35129                        self.write_space();
35130                        self.write_keyword("AS");
35131                        self.write_space();
35132                        // Quote name if it contains spaces or special chars
35133                        let needs_quoting = name.contains(' ') || name.contains('-');
35134                        if needs_quoting {
35135                            if matches!(
35136                                self.config.dialect,
35137                                Some(DialectType::Spark)
35138                                    | Some(DialectType::Databricks)
35139                                    | Some(DialectType::Hive)
35140                            ) {
35141                                self.write("`");
35142                                self.write(name);
35143                                self.write("`");
35144                            } else {
35145                                self.write(name);
35146                            }
35147                        } else {
35148                            self.write(name);
35149                        }
35150                    } else {
35151                        // STRUCT(name AS value)
35152                        self.write(name);
35153                        self.write_space();
35154                        self.write_keyword("AS");
35155                        self.write_space();
35156                        self.generate_expression(expr)?;
35157                    }
35158                } else {
35159                    self.generate_expression(expr)?;
35160                }
35161            }
35162            self.write(")");
35163        }
35164        Ok(())
35165    }
35166
35167    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
35168        // STUFF(this, start, length, expression)
35169        self.write_keyword("STUFF");
35170        self.write("(");
35171        self.generate_expression(&e.this)?;
35172        if let Some(start) = &e.start {
35173            self.write(", ");
35174            self.generate_expression(start)?;
35175        }
35176        if let Some(length) = e.length {
35177            self.write(", ");
35178            self.write(&length.to_string());
35179        }
35180        self.write(", ");
35181        self.generate_expression(&e.expression)?;
35182        self.write(")");
35183        Ok(())
35184    }
35185
35186    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
35187        // SUBSTRING_INDEX(this, delimiter, count)
35188        self.write_keyword("SUBSTRING_INDEX");
35189        self.write("(");
35190        self.generate_expression(&e.this)?;
35191        if let Some(delimiter) = &e.delimiter {
35192            self.write(", ");
35193            self.generate_expression(delimiter)?;
35194        }
35195        if let Some(count) = &e.count {
35196            self.write(", ");
35197            self.generate_expression(count)?;
35198        }
35199        self.write(")");
35200        Ok(())
35201    }
35202
35203    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
35204        // SUMMARIZE [TABLE] this
35205        self.write_keyword("SUMMARIZE");
35206        if e.table.is_some() {
35207            self.write_space();
35208            self.write_keyword("TABLE");
35209        }
35210        self.write_space();
35211        self.generate_expression(&e.this)?;
35212        Ok(())
35213    }
35214
35215    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
35216        // SYSTIMESTAMP
35217        self.write_keyword("SYSTIMESTAMP");
35218        Ok(())
35219    }
35220
35221    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
35222        // alias (columns...)
35223        if let Some(this) = &e.this {
35224            self.generate_expression(this)?;
35225        }
35226        if !e.columns.is_empty() {
35227            self.write("(");
35228            for (i, col) in e.columns.iter().enumerate() {
35229                if i > 0 {
35230                    self.write(", ");
35231                }
35232                self.generate_expression(col)?;
35233            }
35234            self.write(")");
35235        }
35236        Ok(())
35237    }
35238
35239    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
35240        // TABLE(this) [AS alias]
35241        self.write_keyword("TABLE");
35242        self.write("(");
35243        self.generate_expression(&e.this)?;
35244        self.write(")");
35245        if let Some(alias) = &e.alias {
35246            self.write_space();
35247            self.write_keyword("AS");
35248            self.write_space();
35249            self.write(alias);
35250        }
35251        Ok(())
35252    }
35253
35254    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
35255        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
35256        self.write_keyword("ROWS FROM");
35257        self.write(" (");
35258        for (i, expr) in e.expressions.iter().enumerate() {
35259            if i > 0 {
35260                self.write(", ");
35261            }
35262            // Each expression is either:
35263            // - A plain function (no alias)
35264            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
35265            match expr {
35266                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
35267                    // First element is the function, second is the TableAlias
35268                    self.generate_expression(&tuple.expressions[0])?;
35269                    self.write_space();
35270                    self.write_keyword("AS");
35271                    self.write_space();
35272                    self.generate_expression(&tuple.expressions[1])?;
35273                }
35274                _ => {
35275                    self.generate_expression(expr)?;
35276                }
35277            }
35278        }
35279        self.write(")");
35280        if e.ordinality {
35281            self.write_space();
35282            self.write_keyword("WITH ORDINALITY");
35283        }
35284        if let Some(alias) = &e.alias {
35285            self.write_space();
35286            self.write_keyword("AS");
35287            self.write_space();
35288            self.generate_expression(alias)?;
35289        }
35290        Ok(())
35291    }
35292
35293    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
35294        use crate::dialects::DialectType;
35295
35296        // New wrapper pattern: expression + Sample struct
35297        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
35298            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
35299            if self.config.alias_post_tablesample {
35300                // Handle Subquery with alias and Alias wrapper
35301                if let Expression::Subquery(ref s) = **this {
35302                    if let Some(ref alias) = s.alias {
35303                        // Create a clone without alias for output
35304                        let mut subquery_no_alias = (**s).clone();
35305                        subquery_no_alias.alias = None;
35306                        subquery_no_alias.column_aliases = Vec::new();
35307                        self.generate_expression(&Expression::Subquery(Box::new(
35308                            subquery_no_alias,
35309                        )))?;
35310                        self.write_space();
35311                        self.write_keyword("TABLESAMPLE");
35312                        self.generate_sample_body(sample)?;
35313                        if let Some(ref seed) = sample.seed {
35314                            self.write_space();
35315                            let use_seed = sample.use_seed_keyword
35316                                && !matches!(
35317                                    self.config.dialect,
35318                                    Some(crate::dialects::DialectType::Databricks)
35319                                        | Some(crate::dialects::DialectType::Spark)
35320                                );
35321                            if use_seed {
35322                                self.write_keyword("SEED");
35323                            } else {
35324                                self.write_keyword("REPEATABLE");
35325                            }
35326                            self.write(" (");
35327                            self.generate_expression(seed)?;
35328                            self.write(")");
35329                        }
35330                        self.write_space();
35331                        self.write_keyword("AS");
35332                        self.write_space();
35333                        self.generate_identifier(alias)?;
35334                        return Ok(());
35335                    }
35336                } else if let Expression::Alias(ref a) = **this {
35337                    // Output the base expression without alias
35338                    self.generate_expression(&a.this)?;
35339                    self.write_space();
35340                    self.write_keyword("TABLESAMPLE");
35341                    self.generate_sample_body(sample)?;
35342                    if let Some(ref seed) = sample.seed {
35343                        self.write_space();
35344                        let use_seed = sample.use_seed_keyword
35345                            && !matches!(
35346                                self.config.dialect,
35347                                Some(crate::dialects::DialectType::Databricks)
35348                                    | Some(crate::dialects::DialectType::Spark)
35349                            );
35350                        if use_seed {
35351                            self.write_keyword("SEED");
35352                        } else {
35353                            self.write_keyword("REPEATABLE");
35354                        }
35355                        self.write(" (");
35356                        self.generate_expression(seed)?;
35357                        self.write(")");
35358                    }
35359                    // Output alias after TABLESAMPLE
35360                    self.write_space();
35361                    self.write_keyword("AS");
35362                    self.write_space();
35363                    self.generate_identifier(&a.alias)?;
35364                    return Ok(());
35365                }
35366            }
35367            // Default: generate wrapped expression first, then TABLESAMPLE
35368            self.generate_expression(this)?;
35369            self.write_space();
35370            self.write_keyword("TABLESAMPLE");
35371            self.generate_sample_body(sample)?;
35372            // Seed for table-level sample
35373            if let Some(ref seed) = sample.seed {
35374                self.write_space();
35375                // Databricks uses REPEATABLE, not SEED
35376                let use_seed = sample.use_seed_keyword
35377                    && !matches!(
35378                        self.config.dialect,
35379                        Some(crate::dialects::DialectType::Databricks)
35380                            | Some(crate::dialects::DialectType::Spark)
35381                    );
35382                if use_seed {
35383                    self.write_keyword("SEED");
35384                } else {
35385                    self.write_keyword("REPEATABLE");
35386                }
35387                self.write(" (");
35388                self.generate_expression(seed)?;
35389                self.write(")");
35390            }
35391            return Ok(());
35392        }
35393
35394        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
35395        self.write_keyword("TABLESAMPLE");
35396        if let Some(method) = &e.method {
35397            self.write_space();
35398            self.write_keyword(method);
35399        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
35400            // Snowflake defaults to BERNOULLI when no method is specified
35401            self.write_space();
35402            self.write_keyword("BERNOULLI");
35403        }
35404        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
35405            self.write_space();
35406            self.write_keyword("BUCKET");
35407            self.write_space();
35408            self.generate_expression(numerator)?;
35409            self.write_space();
35410            self.write_keyword("OUT OF");
35411            self.write_space();
35412            self.generate_expression(denominator)?;
35413            if let Some(field) = &e.bucket_field {
35414                self.write_space();
35415                self.write_keyword("ON");
35416                self.write_space();
35417                self.generate_expression(field)?;
35418            }
35419        } else if !e.expressions.is_empty() {
35420            self.write(" (");
35421            for (i, expr) in e.expressions.iter().enumerate() {
35422                if i > 0 {
35423                    self.write(", ");
35424                }
35425                self.generate_expression(expr)?;
35426            }
35427            self.write(")");
35428        } else if let Some(percent) = &e.percent {
35429            self.write(" (");
35430            self.generate_expression(percent)?;
35431            self.write_space();
35432            self.write_keyword("PERCENT");
35433            self.write(")");
35434        }
35435        Ok(())
35436    }
35437
35438    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
35439        // [prefix]this[postfix]
35440        if let Some(prefix) = &e.prefix {
35441            self.generate_expression(prefix)?;
35442        }
35443        if let Some(this) = &e.this {
35444            self.generate_expression(this)?;
35445        }
35446        if let Some(postfix) = &e.postfix {
35447            self.generate_expression(postfix)?;
35448        }
35449        Ok(())
35450    }
35451
35452    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
35453        // TAG (expressions)
35454        self.write_keyword("TAG");
35455        self.write(" (");
35456        for (i, expr) in e.expressions.iter().enumerate() {
35457            if i > 0 {
35458                self.write(", ");
35459            }
35460            self.generate_expression(expr)?;
35461        }
35462        self.write(")");
35463        Ok(())
35464    }
35465
35466    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
35467        // TEMPORARY or TEMP or [this] TEMPORARY
35468        if let Some(this) = &e.this {
35469            self.generate_expression(this)?;
35470            self.write_space();
35471        }
35472        self.write_keyword("TEMPORARY");
35473        Ok(())
35474    }
35475
35476    /// Generate a Time function expression
35477    /// For most dialects: TIME('value')
35478    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
35479        // Standard: TIME(value)
35480        self.write_keyword("TIME");
35481        self.write("(");
35482        self.generate_expression(&e.this)?;
35483        self.write(")");
35484        Ok(())
35485    }
35486
35487    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
35488        // TIME_ADD(this, expression, unit)
35489        self.write_keyword("TIME_ADD");
35490        self.write("(");
35491        self.generate_expression(&e.this)?;
35492        self.write(", ");
35493        self.generate_expression(&e.expression)?;
35494        if let Some(unit) = &e.unit {
35495            self.write(", ");
35496            self.write_keyword(unit);
35497        }
35498        self.write(")");
35499        Ok(())
35500    }
35501
35502    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
35503        // TIME_DIFF(this, expression, unit)
35504        self.write_keyword("TIME_DIFF");
35505        self.write("(");
35506        self.generate_expression(&e.this)?;
35507        self.write(", ");
35508        self.generate_expression(&e.expression)?;
35509        if let Some(unit) = &e.unit {
35510            self.write(", ");
35511            self.write_keyword(unit);
35512        }
35513        self.write(")");
35514        Ok(())
35515    }
35516
35517    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
35518        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
35519        self.write_keyword("TIME_FROM_PARTS");
35520        self.write("(");
35521        let mut first = true;
35522        if let Some(hour) = &e.hour {
35523            self.generate_expression(hour)?;
35524            first = false;
35525        }
35526        if let Some(minute) = &e.min {
35527            if !first {
35528                self.write(", ");
35529            }
35530            self.generate_expression(minute)?;
35531            first = false;
35532        }
35533        if let Some(second) = &e.sec {
35534            if !first {
35535                self.write(", ");
35536            }
35537            self.generate_expression(second)?;
35538            first = false;
35539        }
35540        if let Some(ns) = &e.nano {
35541            if !first {
35542                self.write(", ");
35543            }
35544            self.generate_expression(ns)?;
35545        }
35546        self.write(")");
35547        Ok(())
35548    }
35549
35550    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
35551        // TIME_SLICE(this, expression, unit)
35552        self.write_keyword("TIME_SLICE");
35553        self.write("(");
35554        self.generate_expression(&e.this)?;
35555        self.write(", ");
35556        self.generate_expression(&e.expression)?;
35557        self.write(", ");
35558        self.write_keyword(&e.unit);
35559        self.write(")");
35560        Ok(())
35561    }
35562
35563    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
35564        // TIME_STR_TO_TIME(this)
35565        self.write_keyword("TIME_STR_TO_TIME");
35566        self.write("(");
35567        self.generate_expression(&e.this)?;
35568        self.write(")");
35569        Ok(())
35570    }
35571
35572    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
35573        // TIME_SUB(this, expression, unit)
35574        self.write_keyword("TIME_SUB");
35575        self.write("(");
35576        self.generate_expression(&e.this)?;
35577        self.write(", ");
35578        self.generate_expression(&e.expression)?;
35579        if let Some(unit) = &e.unit {
35580            self.write(", ");
35581            self.write_keyword(unit);
35582        }
35583        self.write(")");
35584        Ok(())
35585    }
35586
35587    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
35588        match self.config.dialect {
35589            Some(DialectType::Exasol) => {
35590                // Exasol uses TO_CHAR with Exasol-specific format
35591                self.write_keyword("TO_CHAR");
35592                self.write("(");
35593                self.generate_expression(&e.this)?;
35594                self.write(", '");
35595                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
35596                self.write("'");
35597                self.write(")");
35598            }
35599            Some(DialectType::PostgreSQL)
35600            | Some(DialectType::Redshift)
35601            | Some(DialectType::Materialize) => {
35602                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
35603                self.write_keyword("TO_CHAR");
35604                self.write("(");
35605                self.generate_expression(&e.this)?;
35606                self.write(", '");
35607                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
35608                self.write("'");
35609                self.write(")");
35610            }
35611            Some(DialectType::Oracle) => {
35612                // Oracle uses TO_CHAR with PG-like format
35613                self.write_keyword("TO_CHAR");
35614                self.write("(");
35615                self.generate_expression(&e.this)?;
35616                self.write(", '");
35617                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
35618                self.write("'");
35619                self.write(")");
35620            }
35621            Some(DialectType::Drill) => {
35622                // Drill: TO_CHAR with Java format
35623                self.write_keyword("TO_CHAR");
35624                self.write("(");
35625                self.generate_expression(&e.this)?;
35626                self.write(", '");
35627                self.write(&Self::strftime_to_java_format(&e.format));
35628                self.write("'");
35629                self.write(")");
35630            }
35631            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
35632                // TSQL: FORMAT(value, format) with .NET-style format
35633                self.write_keyword("FORMAT");
35634                self.write("(");
35635                self.generate_expression(&e.this)?;
35636                self.write(", '");
35637                self.write(&Self::strftime_to_tsql_format(&e.format));
35638                self.write("'");
35639                self.write(")");
35640            }
35641            Some(DialectType::DuckDB) => {
35642                // DuckDB: STRFTIME(value, format) - keeps C format
35643                self.write_keyword("STRFTIME");
35644                self.write("(");
35645                self.generate_expression(&e.this)?;
35646                self.write(", '");
35647                self.write(&e.format);
35648                self.write("'");
35649                self.write(")");
35650            }
35651            Some(DialectType::BigQuery) => {
35652                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
35653                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
35654                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
35655                self.write_keyword("FORMAT_DATE");
35656                self.write("('");
35657                self.write(&fmt);
35658                self.write("', ");
35659                self.generate_expression(&e.this)?;
35660                self.write(")");
35661            }
35662            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35663                // Hive/Spark: DATE_FORMAT(value, java_format)
35664                self.write_keyword("DATE_FORMAT");
35665                self.write("(");
35666                self.generate_expression(&e.this)?;
35667                self.write(", '");
35668                self.write(&Self::strftime_to_java_format(&e.format));
35669                self.write("'");
35670                self.write(")");
35671            }
35672            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
35673                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
35674                self.write_keyword("DATE_FORMAT");
35675                self.write("(");
35676                self.generate_expression(&e.this)?;
35677                self.write(", '");
35678                self.write(&e.format);
35679                self.write("'");
35680                self.write(")");
35681            }
35682            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
35683                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
35684                self.write_keyword("DATE_FORMAT");
35685                self.write("(");
35686                self.generate_expression(&e.this)?;
35687                self.write(", '");
35688                self.write(&e.format);
35689                self.write("'");
35690                self.write(")");
35691            }
35692            _ => {
35693                // Default: TIME_TO_STR(this, format)
35694                self.write_keyword("TIME_TO_STR");
35695                self.write("(");
35696                self.generate_expression(&e.this)?;
35697                self.write(", '");
35698                self.write(&e.format);
35699                self.write("'");
35700                self.write(")");
35701            }
35702        }
35703        Ok(())
35704    }
35705
35706    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
35707        match self.config.dialect {
35708            Some(DialectType::DuckDB) => {
35709                // DuckDB: EPOCH(x)
35710                self.write_keyword("EPOCH");
35711                self.write("(");
35712                self.generate_expression(&e.this)?;
35713                self.write(")");
35714            }
35715            Some(DialectType::Hive)
35716            | Some(DialectType::Spark)
35717            | Some(DialectType::Databricks)
35718            | Some(DialectType::Doris)
35719            | Some(DialectType::StarRocks)
35720            | Some(DialectType::Drill) => {
35721                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
35722                self.write_keyword("UNIX_TIMESTAMP");
35723                self.write("(");
35724                self.generate_expression(&e.this)?;
35725                self.write(")");
35726            }
35727            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35728                // Presto: TO_UNIXTIME(x)
35729                self.write_keyword("TO_UNIXTIME");
35730                self.write("(");
35731                self.generate_expression(&e.this)?;
35732                self.write(")");
35733            }
35734            _ => {
35735                // Default: TIME_TO_UNIX(x)
35736                self.write_keyword("TIME_TO_UNIX");
35737                self.write("(");
35738                self.generate_expression(&e.this)?;
35739                self.write(")");
35740            }
35741        }
35742        Ok(())
35743    }
35744
35745    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
35746        match self.config.dialect {
35747            Some(DialectType::Hive) => {
35748                // Hive: TO_DATE(x)
35749                self.write_keyword("TO_DATE");
35750                self.write("(");
35751                self.generate_expression(&e.this)?;
35752                self.write(")");
35753            }
35754            _ => {
35755                // Default: TIME_STR_TO_DATE(x)
35756                self.write_keyword("TIME_STR_TO_DATE");
35757                self.write("(");
35758                self.generate_expression(&e.this)?;
35759                self.write(")");
35760            }
35761        }
35762        Ok(())
35763    }
35764
35765    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
35766        // TIME_TRUNC(this, unit)
35767        self.write_keyword("TIME_TRUNC");
35768        self.write("(");
35769        self.generate_expression(&e.this)?;
35770        self.write(", ");
35771        self.write_keyword(&e.unit);
35772        self.write(")");
35773        Ok(())
35774    }
35775
35776    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
35777        // Just output the unit name
35778        if let Some(unit) = &e.unit {
35779            self.write_keyword(unit);
35780        }
35781        Ok(())
35782    }
35783
35784    /// Generate a Timestamp function expression
35785    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
35786    /// For other dialects: TIMESTAMP('value')
35787    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
35788        use crate::dialects::DialectType;
35789        use crate::expressions::Literal;
35790
35791        match self.config.dialect {
35792            // Exasol uses TO_TIMESTAMP for Timestamp expressions
35793            Some(DialectType::Exasol) => {
35794                self.write_keyword("TO_TIMESTAMP");
35795                self.write("(");
35796                // Extract the string value from the expression if it's a string literal
35797                if let Some(this) = &e.this {
35798                    match this.as_ref() {
35799                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
35800                            let Literal::String(s) = lit.as_ref() else {
35801                                unreachable!()
35802                            };
35803                            self.write("'");
35804                            self.write(s);
35805                            self.write("'");
35806                        }
35807                        _ => {
35808                            self.generate_expression(this)?;
35809                        }
35810                    }
35811                }
35812                self.write(")");
35813            }
35814            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
35815            _ => {
35816                self.write_keyword("TIMESTAMP");
35817                self.write("(");
35818                if let Some(this) = &e.this {
35819                    self.generate_expression(this)?;
35820                }
35821                if let Some(zone) = &e.zone {
35822                    self.write(", ");
35823                    self.generate_expression(zone)?;
35824                }
35825                self.write(")");
35826            }
35827        }
35828        Ok(())
35829    }
35830
35831    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
35832        // TIMESTAMP_ADD(this, expression, unit)
35833        self.write_keyword("TIMESTAMP_ADD");
35834        self.write("(");
35835        self.generate_expression(&e.this)?;
35836        self.write(", ");
35837        self.generate_expression(&e.expression)?;
35838        if let Some(unit) = &e.unit {
35839            self.write(", ");
35840            self.write_keyword(unit);
35841        }
35842        self.write(")");
35843        Ok(())
35844    }
35845
35846    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
35847        // TIMESTAMP_DIFF(this, expression, unit)
35848        self.write_keyword("TIMESTAMP_DIFF");
35849        self.write("(");
35850        self.generate_expression(&e.this)?;
35851        self.write(", ");
35852        self.generate_expression(&e.expression)?;
35853        if let Some(unit) = &e.unit {
35854            self.write(", ");
35855            self.write_keyword(unit);
35856        }
35857        self.write(")");
35858        Ok(())
35859    }
35860
35861    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
35862        // TIMESTAMP_FROM_PARTS(this, expression)
35863        self.write_keyword("TIMESTAMP_FROM_PARTS");
35864        self.write("(");
35865        if let Some(this) = &e.this {
35866            self.generate_expression(this)?;
35867        }
35868        if let Some(expression) = &e.expression {
35869            self.write(", ");
35870            self.generate_expression(expression)?;
35871        }
35872        if let Some(zone) = &e.zone {
35873            self.write(", ");
35874            self.generate_expression(zone)?;
35875        }
35876        if let Some(milli) = &e.milli {
35877            self.write(", ");
35878            self.generate_expression(milli)?;
35879        }
35880        self.write(")");
35881        Ok(())
35882    }
35883
35884    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
35885        // TIMESTAMP_SUB(this, INTERVAL expression unit)
35886        self.write_keyword("TIMESTAMP_SUB");
35887        self.write("(");
35888        self.generate_expression(&e.this)?;
35889        self.write(", ");
35890        self.write_keyword("INTERVAL");
35891        self.write_space();
35892        self.generate_expression(&e.expression)?;
35893        if let Some(unit) = &e.unit {
35894            self.write_space();
35895            self.write_keyword(unit);
35896        }
35897        self.write(")");
35898        Ok(())
35899    }
35900
35901    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
35902        // TIMESTAMP_TZ_FROM_PARTS(...)
35903        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
35904        self.write("(");
35905        if let Some(zone) = &e.zone {
35906            self.generate_expression(zone)?;
35907        }
35908        self.write(")");
35909        Ok(())
35910    }
35911
35912    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
35913        // TO_BINARY(this, [format])
35914        self.write_keyword("TO_BINARY");
35915        self.write("(");
35916        self.generate_expression(&e.this)?;
35917        if let Some(format) = &e.format {
35918            self.write(", '");
35919            self.write(format);
35920            self.write("'");
35921        }
35922        self.write(")");
35923        Ok(())
35924    }
35925
35926    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
35927        // TO_BOOLEAN(this)
35928        self.write_keyword("TO_BOOLEAN");
35929        self.write("(");
35930        self.generate_expression(&e.this)?;
35931        self.write(")");
35932        Ok(())
35933    }
35934
35935    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
35936        // TO_CHAR(this, [format], [nlsparam])
35937        self.write_keyword("TO_CHAR");
35938        self.write("(");
35939        self.generate_expression(&e.this)?;
35940        if let Some(format) = &e.format {
35941            self.write(", '");
35942            self.write(format);
35943            self.write("'");
35944        }
35945        if let Some(nlsparam) = &e.nlsparam {
35946            self.write(", ");
35947            self.generate_expression(nlsparam)?;
35948        }
35949        self.write(")");
35950        Ok(())
35951    }
35952
35953    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
35954        // TO_DECFLOAT(this, [format])
35955        self.write_keyword("TO_DECFLOAT");
35956        self.write("(");
35957        self.generate_expression(&e.this)?;
35958        if let Some(format) = &e.format {
35959            self.write(", '");
35960            self.write(format);
35961            self.write("'");
35962        }
35963        self.write(")");
35964        Ok(())
35965    }
35966
35967    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
35968        // TO_DOUBLE(this, [format])
35969        self.write_keyword("TO_DOUBLE");
35970        self.write("(");
35971        self.generate_expression(&e.this)?;
35972        if let Some(format) = &e.format {
35973            self.write(", '");
35974            self.write(format);
35975            self.write("'");
35976        }
35977        self.write(")");
35978        Ok(())
35979    }
35980
35981    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
35982        // TO_FILE(this, path)
35983        self.write_keyword("TO_FILE");
35984        self.write("(");
35985        self.generate_expression(&e.this)?;
35986        if let Some(path) = &e.path {
35987            self.write(", ");
35988            self.generate_expression(path)?;
35989        }
35990        self.write(")");
35991        Ok(())
35992    }
35993
35994    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
35995        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
35996        // If safe flag is set, output TRY_TO_NUMBER
35997        let is_safe = e.safe.is_some();
35998        if is_safe {
35999            self.write_keyword("TRY_TO_NUMBER");
36000        } else {
36001            self.write_keyword("TO_NUMBER");
36002        }
36003        self.write("(");
36004        self.generate_expression(&e.this)?;
36005        if let Some(format) = &e.format {
36006            self.write(", ");
36007            self.generate_expression(format)?;
36008        }
36009        if let Some(nlsparam) = &e.nlsparam {
36010            self.write(", ");
36011            self.generate_expression(nlsparam)?;
36012        }
36013        if let Some(precision) = &e.precision {
36014            self.write(", ");
36015            self.generate_expression(precision)?;
36016        }
36017        if let Some(scale) = &e.scale {
36018            self.write(", ");
36019            self.generate_expression(scale)?;
36020        }
36021        self.write(")");
36022        Ok(())
36023    }
36024
36025    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
36026        // TO_TABLE this
36027        self.write_keyword("TO_TABLE");
36028        self.write_space();
36029        self.generate_expression(&e.this)?;
36030        Ok(())
36031    }
36032
36033    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
36034        // Check mark to determine the format
36035        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
36036            Expression::Identifier(id) => id.name.clone(),
36037            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
36038                let Literal::String(s) = lit.as_ref() else {
36039                    unreachable!()
36040                };
36041                s.clone()
36042            }
36043            _ => String::new(),
36044        });
36045
36046        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
36047        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
36048        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
36049            matches!(m.as_ref(), Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
36050        });
36051
36052        // For Presto/Trino: always use START TRANSACTION
36053        let use_start_transaction = matches!(
36054            self.config.dialect,
36055            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
36056        );
36057        // For most dialects: strip TRANSACTION keyword
36058        let strip_transaction = matches!(
36059            self.config.dialect,
36060            Some(DialectType::Snowflake)
36061                | Some(DialectType::PostgreSQL)
36062                | Some(DialectType::Redshift)
36063                | Some(DialectType::MySQL)
36064                | Some(DialectType::Hive)
36065                | Some(DialectType::Spark)
36066                | Some(DialectType::Databricks)
36067                | Some(DialectType::DuckDB)
36068                | Some(DialectType::Oracle)
36069                | Some(DialectType::Doris)
36070                | Some(DialectType::StarRocks)
36071                | Some(DialectType::Materialize)
36072                | Some(DialectType::ClickHouse)
36073        );
36074
36075        if is_start || use_start_transaction {
36076            // START TRANSACTION [modes]
36077            self.write_keyword("START TRANSACTION");
36078            if let Some(modes) = &e.modes {
36079                self.write_space();
36080                self.generate_expression(modes)?;
36081            }
36082        } else {
36083            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
36084            self.write_keyword("BEGIN");
36085
36086            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
36087            let is_kind = e.this.as_ref().map_or(false, |t| {
36088                if let Expression::Identifier(id) = t.as_ref() {
36089                    id.name.eq_ignore_ascii_case("DEFERRED")
36090                        || id.name.eq_ignore_ascii_case("IMMEDIATE")
36091                        || id.name.eq_ignore_ascii_case("EXCLUSIVE")
36092                } else {
36093                    false
36094                }
36095            });
36096
36097            // Output kind before TRANSACTION keyword
36098            if is_kind {
36099                if let Some(this) = &e.this {
36100                    self.write_space();
36101                    if let Expression::Identifier(id) = this.as_ref() {
36102                        self.write_keyword(&id.name);
36103                    }
36104                }
36105            }
36106
36107            // Output TRANSACTION keyword if it was present and target supports it
36108            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
36109                self.write_space();
36110                self.write_keyword("TRANSACTION");
36111            }
36112
36113            // Output transaction name (not kind)
36114            if !is_kind {
36115                if let Some(this) = &e.this {
36116                    self.write_space();
36117                    self.generate_expression(this)?;
36118                }
36119            }
36120
36121            // Output WITH MARK 'description' for TSQL
36122            if has_with_mark {
36123                self.write_space();
36124                self.write_keyword("WITH MARK");
36125                if let Some(Expression::Literal(lit)) = e.mark.as_deref() {
36126                    if let Literal::String(desc) = lit.as_ref() {
36127                        if !desc.is_empty() {
36128                            self.write_space();
36129                            self.write(&format!("'{}'", desc));
36130                        }
36131                    }
36132                }
36133            }
36134
36135            // Output modes (isolation levels, etc.)
36136            if let Some(modes) = &e.modes {
36137                self.write_space();
36138                self.generate_expression(modes)?;
36139            }
36140        }
36141        Ok(())
36142    }
36143
36144    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
36145        // TRANSFORM(this, expression)
36146        self.write_keyword("TRANSFORM");
36147        self.write("(");
36148        self.generate_expression(&e.this)?;
36149        self.write(", ");
36150        self.generate_expression(&e.expression)?;
36151        self.write(")");
36152        Ok(())
36153    }
36154
36155    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
36156        // TRANSFORM(expressions)
36157        self.write_keyword("TRANSFORM");
36158        self.write("(");
36159        if self.config.pretty && !e.expressions.is_empty() {
36160            self.indent_level += 1;
36161            for (i, expr) in e.expressions.iter().enumerate() {
36162                if i > 0 {
36163                    self.write(",");
36164                }
36165                self.write_newline();
36166                self.write_indent();
36167                self.generate_expression(expr)?;
36168            }
36169            self.indent_level -= 1;
36170            self.write_newline();
36171            self.write(")");
36172        } else {
36173            for (i, expr) in e.expressions.iter().enumerate() {
36174                if i > 0 {
36175                    self.write(", ");
36176                }
36177                self.generate_expression(expr)?;
36178            }
36179            self.write(")");
36180        }
36181        Ok(())
36182    }
36183
36184    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
36185        use crate::dialects::DialectType;
36186        // TRANSIENT is Snowflake-specific; skip for other dialects
36187        if let Some(this) = &e.this {
36188            self.generate_expression(this)?;
36189            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
36190                self.write_space();
36191            }
36192        }
36193        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
36194            self.write_keyword("TRANSIENT");
36195        }
36196        Ok(())
36197    }
36198
36199    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
36200        // TRANSLATE(this, from_, to)
36201        self.write_keyword("TRANSLATE");
36202        self.write("(");
36203        self.generate_expression(&e.this)?;
36204        if let Some(from) = &e.from_ {
36205            self.write(", ");
36206            self.generate_expression(from)?;
36207        }
36208        if let Some(to) = &e.to {
36209            self.write(", ");
36210            self.generate_expression(to)?;
36211        }
36212        self.write(")");
36213        Ok(())
36214    }
36215
36216    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
36217        // TRANSLATE(this USING expression)
36218        self.write_keyword("TRANSLATE");
36219        self.write("(");
36220        self.generate_expression(&e.this)?;
36221        self.write_space();
36222        self.write_keyword("USING");
36223        self.write_space();
36224        self.generate_expression(&e.expression)?;
36225        if e.with_error.is_some() {
36226            self.write_space();
36227            self.write_keyword("WITH ERROR");
36228        }
36229        self.write(")");
36230        Ok(())
36231    }
36232
36233    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
36234        // TRUNCATE TABLE table1, table2, ...
36235        self.write_keyword("TRUNCATE TABLE");
36236        self.write_space();
36237        for (i, expr) in e.expressions.iter().enumerate() {
36238            if i > 0 {
36239                self.write(", ");
36240            }
36241            self.generate_expression(expr)?;
36242        }
36243        Ok(())
36244    }
36245
36246    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
36247        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
36248        self.write_keyword("TRY_BASE64_DECODE_BINARY");
36249        self.write("(");
36250        self.generate_expression(&e.this)?;
36251        if let Some(alphabet) = &e.alphabet {
36252            self.write(", ");
36253            self.generate_expression(alphabet)?;
36254        }
36255        self.write(")");
36256        Ok(())
36257    }
36258
36259    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
36260        // TRY_BASE64_DECODE_STRING(this, [alphabet])
36261        self.write_keyword("TRY_BASE64_DECODE_STRING");
36262        self.write("(");
36263        self.generate_expression(&e.this)?;
36264        if let Some(alphabet) = &e.alphabet {
36265            self.write(", ");
36266            self.generate_expression(alphabet)?;
36267        }
36268        self.write(")");
36269        Ok(())
36270    }
36271
36272    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
36273        // TRY_TO_DECFLOAT(this, [format])
36274        self.write_keyword("TRY_TO_DECFLOAT");
36275        self.write("(");
36276        self.generate_expression(&e.this)?;
36277        if let Some(format) = &e.format {
36278            self.write(", '");
36279            self.write(format);
36280            self.write("'");
36281        }
36282        self.write(")");
36283        Ok(())
36284    }
36285
36286    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
36287        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
36288        self.write_keyword("TS_OR_DS_ADD");
36289        self.write("(");
36290        self.generate_expression(&e.this)?;
36291        self.write(", ");
36292        self.generate_expression(&e.expression)?;
36293        if let Some(unit) = &e.unit {
36294            self.write(", ");
36295            self.write_keyword(unit);
36296        }
36297        if let Some(return_type) = &e.return_type {
36298            self.write(", ");
36299            self.generate_expression(return_type)?;
36300        }
36301        self.write(")");
36302        Ok(())
36303    }
36304
36305    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
36306        // TS_OR_DS_DIFF(this, expression, [unit])
36307        self.write_keyword("TS_OR_DS_DIFF");
36308        self.write("(");
36309        self.generate_expression(&e.this)?;
36310        self.write(", ");
36311        self.generate_expression(&e.expression)?;
36312        if let Some(unit) = &e.unit {
36313            self.write(", ");
36314            self.write_keyword(unit);
36315        }
36316        self.write(")");
36317        Ok(())
36318    }
36319
36320    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
36321        let default_time_format = "%Y-%m-%d %H:%M:%S";
36322        let default_date_format = "%Y-%m-%d";
36323        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
36324            f != default_time_format && f != default_date_format
36325        });
36326
36327        if has_non_default_format {
36328            // With non-default format: dialect-specific handling
36329            let fmt = e.format.as_ref().unwrap();
36330            match self.config.dialect {
36331                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
36332                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
36333                    // STR_TO_DATE is the MySQL-native form of StrToTime
36334                    let str_to_time = crate::expressions::StrToTime {
36335                        this: Box::new((*e.this).clone()),
36336                        format: fmt.clone(),
36337                        zone: None,
36338                        safe: None,
36339                        target_type: None,
36340                    };
36341                    self.generate_str_to_time(&str_to_time)?;
36342                }
36343                Some(DialectType::Hive)
36344                | Some(DialectType::Spark)
36345                | Some(DialectType::Databricks) => {
36346                    // Hive/Spark: TO_DATE(x, java_fmt)
36347                    self.write_keyword("TO_DATE");
36348                    self.write("(");
36349                    self.generate_expression(&e.this)?;
36350                    self.write(", '");
36351                    self.write(&Self::strftime_to_java_format(fmt));
36352                    self.write("')");
36353                }
36354                Some(DialectType::Snowflake) => {
36355                    // Snowflake: TO_DATE(x, snowflake_fmt)
36356                    self.write_keyword("TO_DATE");
36357                    self.write("(");
36358                    self.generate_expression(&e.this)?;
36359                    self.write(", '");
36360                    self.write(&Self::strftime_to_snowflake_format(fmt));
36361                    self.write("')");
36362                }
36363                Some(DialectType::Doris) => {
36364                    // Doris: TO_DATE(x) - ignores format
36365                    self.write_keyword("TO_DATE");
36366                    self.write("(");
36367                    self.generate_expression(&e.this)?;
36368                    self.write(")");
36369                }
36370                _ => {
36371                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
36372                    self.write_keyword("CAST");
36373                    self.write("(");
36374                    let str_to_time = crate::expressions::StrToTime {
36375                        this: Box::new((*e.this).clone()),
36376                        format: fmt.clone(),
36377                        zone: None,
36378                        safe: None,
36379                        target_type: None,
36380                    };
36381                    self.generate_str_to_time(&str_to_time)?;
36382                    self.write_keyword(" AS ");
36383                    self.write_keyword("DATE");
36384                    self.write(")");
36385                }
36386            }
36387        } else {
36388            // Without format (or default format): simple date conversion
36389            match self.config.dialect {
36390                Some(DialectType::MySQL)
36391                | Some(DialectType::SQLite)
36392                | Some(DialectType::StarRocks) => {
36393                    // MySQL/SQLite/StarRocks: DATE(x)
36394                    self.write_keyword("DATE");
36395                    self.write("(");
36396                    self.generate_expression(&e.this)?;
36397                    self.write(")");
36398                }
36399                Some(DialectType::Hive)
36400                | Some(DialectType::Spark)
36401                | Some(DialectType::Databricks)
36402                | Some(DialectType::Snowflake)
36403                | Some(DialectType::Doris) => {
36404                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
36405                    self.write_keyword("TO_DATE");
36406                    self.write("(");
36407                    self.generate_expression(&e.this)?;
36408                    self.write(")");
36409                }
36410                Some(DialectType::Presto)
36411                | Some(DialectType::Trino)
36412                | Some(DialectType::Athena) => {
36413                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
36414                    self.write_keyword("CAST");
36415                    self.write("(");
36416                    self.write_keyword("CAST");
36417                    self.write("(");
36418                    self.generate_expression(&e.this)?;
36419                    self.write_keyword(" AS ");
36420                    self.write_keyword("TIMESTAMP");
36421                    self.write(")");
36422                    self.write_keyword(" AS ");
36423                    self.write_keyword("DATE");
36424                    self.write(")");
36425                }
36426                Some(DialectType::ClickHouse) => {
36427                    // ClickHouse: CAST(x AS Nullable(DATE))
36428                    self.write_keyword("CAST");
36429                    self.write("(");
36430                    self.generate_expression(&e.this)?;
36431                    self.write_keyword(" AS ");
36432                    self.write("Nullable(DATE)");
36433                    self.write(")");
36434                }
36435                _ => {
36436                    // Default: CAST(x AS DATE)
36437                    self.write_keyword("CAST");
36438                    self.write("(");
36439                    self.generate_expression(&e.this)?;
36440                    self.write_keyword(" AS ");
36441                    self.write_keyword("DATE");
36442                    self.write(")");
36443                }
36444            }
36445        }
36446        Ok(())
36447    }
36448
36449    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
36450        // TS_OR_DS_TO_TIME(this, [format])
36451        self.write_keyword("TS_OR_DS_TO_TIME");
36452        self.write("(");
36453        self.generate_expression(&e.this)?;
36454        if let Some(format) = &e.format {
36455            self.write(", '");
36456            self.write(format);
36457            self.write("'");
36458        }
36459        self.write(")");
36460        Ok(())
36461    }
36462
36463    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
36464        // UNHEX(this, [expression])
36465        self.write_keyword("UNHEX");
36466        self.write("(");
36467        self.generate_expression(&e.this)?;
36468        if let Some(expression) = &e.expression {
36469            self.write(", ");
36470            self.generate_expression(expression)?;
36471        }
36472        self.write(")");
36473        Ok(())
36474    }
36475
36476    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
36477        // U&this [UESCAPE escape]
36478        self.write("U&");
36479        self.generate_expression(&e.this)?;
36480        if let Some(escape) = &e.escape {
36481            self.write_space();
36482            self.write_keyword("UESCAPE");
36483            self.write_space();
36484            self.generate_expression(escape)?;
36485        }
36486        Ok(())
36487    }
36488
36489    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
36490        // UNIFORM(this, expression, [gen], [seed])
36491        self.write_keyword("UNIFORM");
36492        self.write("(");
36493        self.generate_expression(&e.this)?;
36494        self.write(", ");
36495        self.generate_expression(&e.expression)?;
36496        if let Some(gen) = &e.gen {
36497            self.write(", ");
36498            self.generate_expression(gen)?;
36499        }
36500        if let Some(seed) = &e.seed {
36501            self.write(", ");
36502            self.generate_expression(seed)?;
36503        }
36504        self.write(")");
36505        Ok(())
36506    }
36507
36508    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
36509        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
36510        self.write_keyword("UNIQUE");
36511        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
36512        if e.nulls.is_some() {
36513            self.write(" NULLS NOT DISTINCT");
36514        }
36515        if let Some(this) = &e.this {
36516            self.write_space();
36517            self.generate_expression(this)?;
36518        }
36519        if let Some(index_type) = &e.index_type {
36520            self.write(" USING ");
36521            self.generate_expression(index_type)?;
36522        }
36523        if let Some(on_conflict) = &e.on_conflict {
36524            self.write_space();
36525            self.generate_expression(on_conflict)?;
36526        }
36527        for opt in &e.options {
36528            self.write_space();
36529            self.generate_expression(opt)?;
36530        }
36531        Ok(())
36532    }
36533
36534    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
36535        // UNIQUE KEY (expressions)
36536        self.write_keyword("UNIQUE KEY");
36537        self.write(" (");
36538        for (i, expr) in e.expressions.iter().enumerate() {
36539            if i > 0 {
36540                self.write(", ");
36541            }
36542            self.generate_expression(expr)?;
36543        }
36544        self.write(")");
36545        Ok(())
36546    }
36547
36548    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
36549        // ROLLUP (r1(col1, col2), r2(col1))
36550        self.write_keyword("ROLLUP");
36551        self.write(" (");
36552        for (i, index) in e.expressions.iter().enumerate() {
36553            if i > 0 {
36554                self.write(", ");
36555            }
36556            self.generate_identifier(&index.name)?;
36557            self.write("(");
36558            for (j, col) in index.expressions.iter().enumerate() {
36559                if j > 0 {
36560                    self.write(", ");
36561                }
36562                self.generate_identifier(col)?;
36563            }
36564            self.write(")");
36565        }
36566        self.write(")");
36567        Ok(())
36568    }
36569
36570    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
36571        match self.config.dialect {
36572            Some(DialectType::DuckDB) => {
36573                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
36574                self.write_keyword("STRFTIME");
36575                self.write("(");
36576                self.write_keyword("TO_TIMESTAMP");
36577                self.write("(");
36578                self.generate_expression(&e.this)?;
36579                self.write("), '");
36580                if let Some(format) = &e.format {
36581                    self.write(format);
36582                }
36583                self.write("')");
36584            }
36585            Some(DialectType::Hive) => {
36586                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
36587                self.write_keyword("FROM_UNIXTIME");
36588                self.write("(");
36589                self.generate_expression(&e.this)?;
36590                if let Some(format) = &e.format {
36591                    if format != "yyyy-MM-dd HH:mm:ss" {
36592                        self.write(", '");
36593                        self.write(format);
36594                        self.write("'");
36595                    }
36596                }
36597                self.write(")");
36598            }
36599            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36600                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
36601                self.write_keyword("DATE_FORMAT");
36602                self.write("(");
36603                self.write_keyword("FROM_UNIXTIME");
36604                self.write("(");
36605                self.generate_expression(&e.this)?;
36606                self.write("), '");
36607                if let Some(format) = &e.format {
36608                    self.write(format);
36609                }
36610                self.write("')");
36611            }
36612            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
36613                // Spark: FROM_UNIXTIME(value, format)
36614                self.write_keyword("FROM_UNIXTIME");
36615                self.write("(");
36616                self.generate_expression(&e.this)?;
36617                if let Some(format) = &e.format {
36618                    self.write(", '");
36619                    self.write(format);
36620                    self.write("'");
36621                }
36622                self.write(")");
36623            }
36624            _ => {
36625                // Default: UNIX_TO_STR(this, [format])
36626                self.write_keyword("UNIX_TO_STR");
36627                self.write("(");
36628                self.generate_expression(&e.this)?;
36629                if let Some(format) = &e.format {
36630                    self.write(", '");
36631                    self.write(format);
36632                    self.write("'");
36633                }
36634                self.write(")");
36635            }
36636        }
36637        Ok(())
36638    }
36639
36640    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
36641        use crate::dialects::DialectType;
36642        let scale = e.scale.unwrap_or(0); // 0 = seconds
36643
36644        match self.config.dialect {
36645            Some(DialectType::Snowflake) => {
36646                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
36647                self.write_keyword("TO_TIMESTAMP");
36648                self.write("(");
36649                self.generate_expression(&e.this)?;
36650                if let Some(s) = e.scale {
36651                    if s > 0 {
36652                        self.write(", ");
36653                        self.write(&s.to_string());
36654                    }
36655                }
36656                self.write(")");
36657            }
36658            Some(DialectType::BigQuery) => {
36659                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
36660                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
36661                match scale {
36662                    0 => {
36663                        self.write_keyword("TIMESTAMP_SECONDS");
36664                        self.write("(");
36665                        self.generate_expression(&e.this)?;
36666                        self.write(")");
36667                    }
36668                    3 => {
36669                        self.write_keyword("TIMESTAMP_MILLIS");
36670                        self.write("(");
36671                        self.generate_expression(&e.this)?;
36672                        self.write(")");
36673                    }
36674                    6 => {
36675                        self.write_keyword("TIMESTAMP_MICROS");
36676                        self.write("(");
36677                        self.generate_expression(&e.this)?;
36678                        self.write(")");
36679                    }
36680                    _ => {
36681                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
36682                        self.write_keyword("TIMESTAMP_SECONDS");
36683                        self.write("(CAST(");
36684                        self.generate_expression(&e.this)?;
36685                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
36686                    }
36687                }
36688            }
36689            Some(DialectType::Spark) => {
36690                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
36691                // TIMESTAMP_MILLIS(value) for scale=3
36692                // TIMESTAMP_MICROS(value) for scale=6
36693                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
36694                match scale {
36695                    0 => {
36696                        self.write_keyword("CAST");
36697                        self.write("(");
36698                        self.write_keyword("FROM_UNIXTIME");
36699                        self.write("(");
36700                        self.generate_expression(&e.this)?;
36701                        self.write(") ");
36702                        self.write_keyword("AS TIMESTAMP");
36703                        self.write(")");
36704                    }
36705                    3 => {
36706                        self.write_keyword("TIMESTAMP_MILLIS");
36707                        self.write("(");
36708                        self.generate_expression(&e.this)?;
36709                        self.write(")");
36710                    }
36711                    6 => {
36712                        self.write_keyword("TIMESTAMP_MICROS");
36713                        self.write("(");
36714                        self.generate_expression(&e.this)?;
36715                        self.write(")");
36716                    }
36717                    _ => {
36718                        self.write_keyword("TIMESTAMP_SECONDS");
36719                        self.write("(");
36720                        self.generate_expression(&e.this)?;
36721                        self.write(&format!(" / POWER(10, {}))", scale));
36722                    }
36723                }
36724            }
36725            Some(DialectType::Databricks) => {
36726                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
36727                // TIMESTAMP_MILLIS(value) for scale=3
36728                // TIMESTAMP_MICROS(value) for scale=6
36729                match scale {
36730                    0 => {
36731                        self.write_keyword("CAST");
36732                        self.write("(");
36733                        self.write_keyword("FROM_UNIXTIME");
36734                        self.write("(");
36735                        self.generate_expression(&e.this)?;
36736                        self.write(") ");
36737                        self.write_keyword("AS TIMESTAMP");
36738                        self.write(")");
36739                    }
36740                    3 => {
36741                        self.write_keyword("TIMESTAMP_MILLIS");
36742                        self.write("(");
36743                        self.generate_expression(&e.this)?;
36744                        self.write(")");
36745                    }
36746                    6 => {
36747                        self.write_keyword("TIMESTAMP_MICROS");
36748                        self.write("(");
36749                        self.generate_expression(&e.this)?;
36750                        self.write(")");
36751                    }
36752                    _ => {
36753                        self.write_keyword("TIMESTAMP_SECONDS");
36754                        self.write("(");
36755                        self.generate_expression(&e.this)?;
36756                        self.write(&format!(" / POWER(10, {}))", scale));
36757                    }
36758                }
36759            }
36760            Some(DialectType::Hive) => {
36761                // Hive: FROM_UNIXTIME(value)
36762                if scale == 0 {
36763                    self.write_keyword("FROM_UNIXTIME");
36764                    self.write("(");
36765                    self.generate_expression(&e.this)?;
36766                    self.write(")");
36767                } else {
36768                    self.write_keyword("FROM_UNIXTIME");
36769                    self.write("(");
36770                    self.generate_expression(&e.this)?;
36771                    self.write(&format!(" / POWER(10, {})", scale));
36772                    self.write(")");
36773                }
36774            }
36775            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36776                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
36777                // FROM_UNIXTIME(value) for scale=0
36778                if scale == 0 {
36779                    self.write_keyword("FROM_UNIXTIME");
36780                    self.write("(");
36781                    self.generate_expression(&e.this)?;
36782                    self.write(")");
36783                } else {
36784                    self.write_keyword("FROM_UNIXTIME");
36785                    self.write("(CAST(");
36786                    self.generate_expression(&e.this)?;
36787                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
36788                }
36789            }
36790            Some(DialectType::DuckDB) => {
36791                // DuckDB: TO_TIMESTAMP(value) for scale=0
36792                // EPOCH_MS(value) for scale=3
36793                // MAKE_TIMESTAMP(value) for scale=6
36794                match scale {
36795                    0 => {
36796                        self.write_keyword("TO_TIMESTAMP");
36797                        self.write("(");
36798                        self.generate_expression(&e.this)?;
36799                        self.write(")");
36800                    }
36801                    3 => {
36802                        self.write_keyword("EPOCH_MS");
36803                        self.write("(");
36804                        self.generate_expression(&e.this)?;
36805                        self.write(")");
36806                    }
36807                    6 => {
36808                        self.write_keyword("MAKE_TIMESTAMP");
36809                        self.write("(");
36810                        self.generate_expression(&e.this)?;
36811                        self.write(")");
36812                    }
36813                    _ => {
36814                        self.write_keyword("TO_TIMESTAMP");
36815                        self.write("(");
36816                        self.generate_expression(&e.this)?;
36817                        self.write(&format!(" / POWER(10, {}))", scale));
36818                        self.write_keyword(" AT TIME ZONE");
36819                        self.write(" 'UTC'");
36820                    }
36821                }
36822            }
36823            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
36824                // Doris/StarRocks: FROM_UNIXTIME(value)
36825                self.write_keyword("FROM_UNIXTIME");
36826                self.write("(");
36827                self.generate_expression(&e.this)?;
36828                self.write(")");
36829            }
36830            Some(DialectType::Oracle) => {
36831                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
36832                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
36833                self.generate_expression(&e.this)?;
36834                self.write(" / 86400)");
36835            }
36836            Some(DialectType::Redshift) => {
36837                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
36838                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
36839                self.write("(TIMESTAMP 'epoch' + ");
36840                if scale == 0 {
36841                    self.generate_expression(&e.this)?;
36842                } else {
36843                    self.write("(");
36844                    self.generate_expression(&e.this)?;
36845                    self.write(&format!(" / POWER(10, {}))", scale));
36846                }
36847                self.write(" * INTERVAL '1 SECOND')");
36848            }
36849            Some(DialectType::Exasol) => {
36850                // Exasol: FROM_POSIX_TIME(value)
36851                self.write_keyword("FROM_POSIX_TIME");
36852                self.write("(");
36853                self.generate_expression(&e.this)?;
36854                self.write(")");
36855            }
36856            _ => {
36857                // Default: TO_TIMESTAMP(value[, scale])
36858                self.write_keyword("TO_TIMESTAMP");
36859                self.write("(");
36860                self.generate_expression(&e.this)?;
36861                if let Some(s) = e.scale {
36862                    self.write(", ");
36863                    self.write(&s.to_string());
36864                }
36865                self.write(")");
36866            }
36867        }
36868        Ok(())
36869    }
36870
36871    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
36872        // NAME col VALUE col1, col2, ...
36873        if !matches!(&*e.this, Expression::Null(_)) {
36874            self.write_keyword("NAME");
36875            self.write_space();
36876            self.generate_expression(&e.this)?;
36877        }
36878        if !e.expressions.is_empty() {
36879            self.write_space();
36880            self.write_keyword("VALUE");
36881            self.write_space();
36882            for (i, expr) in e.expressions.iter().enumerate() {
36883                if i > 0 {
36884                    self.write(", ");
36885                }
36886                self.generate_expression(expr)?;
36887            }
36888        }
36889        Ok(())
36890    }
36891
36892    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
36893        // this(expressions) or (this)(expressions)
36894        if e.wrapped.is_some() {
36895            self.write("(");
36896        }
36897        self.generate_expression(&e.this)?;
36898        if e.wrapped.is_some() {
36899            self.write(")");
36900        }
36901        self.write("(");
36902        for (i, expr) in e.expressions.iter().enumerate() {
36903            if i > 0 {
36904                self.write(", ");
36905            }
36906            self.generate_expression(expr)?;
36907        }
36908        self.write(")");
36909        Ok(())
36910    }
36911
36912    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
36913        // USING TEMPLATE this
36914        self.write_keyword("USING TEMPLATE");
36915        self.write_space();
36916        self.generate_expression(&e.this)?;
36917        Ok(())
36918    }
36919
36920    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
36921        // UTC_TIME
36922        self.write_keyword("UTC_TIME");
36923        Ok(())
36924    }
36925
36926    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
36927        if matches!(
36928            self.config.dialect,
36929            Some(crate::dialects::DialectType::ClickHouse)
36930        ) {
36931            self.write_keyword("CURRENT_TIMESTAMP");
36932            self.write("('UTC')");
36933        } else {
36934            self.write_keyword("UTC_TIMESTAMP");
36935        }
36936        Ok(())
36937    }
36938
36939    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
36940        use crate::dialects::DialectType;
36941        // Choose UUID function name based on target dialect
36942        let func_name = match self.config.dialect {
36943            Some(DialectType::Snowflake) => "UUID_STRING",
36944            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
36945            Some(DialectType::BigQuery) => "GENERATE_UUID",
36946            _ => {
36947                if let Some(name) = &e.name {
36948                    name.as_str()
36949                } else {
36950                    "UUID"
36951                }
36952            }
36953        };
36954        self.write_keyword(func_name);
36955        self.write("(");
36956        if let Some(this) = &e.this {
36957            self.generate_expression(this)?;
36958        }
36959        self.write(")");
36960        Ok(())
36961    }
36962
36963    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
36964        // MAP(key1, value1, key2, value2, ...)
36965        self.write_keyword("MAP");
36966        self.write("(");
36967        let mut first = true;
36968        for (k, v) in e.keys.iter().zip(e.values.iter()) {
36969            if !first {
36970                self.write(", ");
36971            }
36972            self.generate_expression(k)?;
36973            self.write(", ");
36974            self.generate_expression(v)?;
36975            first = false;
36976        }
36977        self.write(")");
36978        Ok(())
36979    }
36980
36981    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
36982        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
36983        self.write_keyword("VECTOR_SEARCH");
36984        self.write("(");
36985        self.generate_expression(&e.this)?;
36986        if let Some(col) = &e.column_to_search {
36987            self.write(", ");
36988            self.generate_expression(col)?;
36989        }
36990        if let Some(query_table) = &e.query_table {
36991            self.write(", ");
36992            self.generate_expression(query_table)?;
36993        }
36994        if let Some(query_col) = &e.query_column_to_search {
36995            self.write(", ");
36996            self.generate_expression(query_col)?;
36997        }
36998        if let Some(top_k) = &e.top_k {
36999            self.write(", ");
37000            self.generate_expression(top_k)?;
37001        }
37002        if let Some(dist_type) = &e.distance_type {
37003            self.write(", ");
37004            self.generate_expression(dist_type)?;
37005        }
37006        self.write(")");
37007        Ok(())
37008    }
37009
37010    fn generate_version(&mut self, e: &Version) -> Result<()> {
37011        // Python: f"FOR {expression.name} {kind} {expr}"
37012        // e.this = Identifier("TIMESTAMP" or "VERSION")
37013        // e.kind = "AS OF" (or "BETWEEN", etc.)
37014        // e.expression = the value expression
37015        // Hive does NOT use the FOR prefix for time travel
37016        use crate::dialects::DialectType;
37017        let skip_for = matches!(
37018            self.config.dialect,
37019            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
37020        );
37021        if !skip_for {
37022            self.write_keyword("FOR");
37023            self.write_space();
37024        }
37025        // Extract the name from this (which is an Identifier expression)
37026        match e.this.as_ref() {
37027            Expression::Identifier(ident) => {
37028                self.write_keyword(&ident.name);
37029            }
37030            _ => {
37031                self.generate_expression(&e.this)?;
37032            }
37033        }
37034        self.write_space();
37035        self.write_keyword(&e.kind);
37036        if let Some(expression) = &e.expression {
37037            self.write_space();
37038            self.generate_expression(expression)?;
37039        }
37040        Ok(())
37041    }
37042
37043    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
37044        // Python: return self.sql(expression, "this")
37045        self.generate_expression(&e.this)?;
37046        Ok(())
37047    }
37048
37049    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
37050        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
37051        if e.this.is_some() {
37052            self.write_keyword("NOT VOLATILE");
37053        } else {
37054            self.write_keyword("VOLATILE");
37055        }
37056        Ok(())
37057    }
37058
37059    fn generate_watermark_column_constraint(
37060        &mut self,
37061        e: &WatermarkColumnConstraint,
37062    ) -> Result<()> {
37063        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
37064        self.write_keyword("WATERMARK FOR");
37065        self.write_space();
37066        self.generate_expression(&e.this)?;
37067        self.write_space();
37068        self.write_keyword("AS");
37069        self.write_space();
37070        self.generate_expression(&e.expression)?;
37071        Ok(())
37072    }
37073
37074    fn generate_week(&mut self, e: &Week) -> Result<()> {
37075        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
37076        self.write_keyword("WEEK");
37077        self.write("(");
37078        self.generate_expression(&e.this)?;
37079        if let Some(mode) = &e.mode {
37080            self.write(", ");
37081            self.generate_expression(mode)?;
37082        }
37083        self.write(")");
37084        Ok(())
37085    }
37086
37087    fn generate_when(&mut self, e: &When) -> Result<()> {
37088        // Python: WHEN {matched}{source}{condition} THEN {then}
37089        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
37090        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
37091        self.write_keyword("WHEN");
37092        self.write_space();
37093
37094        // Check if matched
37095        if let Some(matched) = &e.matched {
37096            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
37097            match matched.as_ref() {
37098                Expression::Boolean(b) if b.value => {
37099                    self.write_keyword("MATCHED");
37100                }
37101                _ => {
37102                    self.write_keyword("NOT MATCHED");
37103                }
37104            }
37105        } else {
37106            self.write_keyword("NOT MATCHED");
37107        }
37108
37109        // BY SOURCE / BY TARGET
37110        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
37111        // BY TARGET is the default and typically omitted in output
37112        // Only emit if the dialect supports BY SOURCE syntax
37113        if self.config.matched_by_source {
37114            if let Some(source) = &e.source {
37115                if let Expression::Boolean(b) = source.as_ref() {
37116                    if b.value {
37117                        // BY SOURCE
37118                        self.write_space();
37119                        self.write_keyword("BY SOURCE");
37120                    }
37121                    // BY TARGET (b.value == false) is omitted as it's the default
37122                } else {
37123                    // For non-boolean source, output as BY SOURCE (legacy behavior)
37124                    self.write_space();
37125                    self.write_keyword("BY SOURCE");
37126                }
37127            }
37128        }
37129
37130        // Condition
37131        if let Some(condition) = &e.condition {
37132            self.write_space();
37133            self.write_keyword("AND");
37134            self.write_space();
37135            self.generate_expression(condition)?;
37136        }
37137
37138        self.write_space();
37139        self.write_keyword("THEN");
37140        self.write_space();
37141
37142        // Generate the then expression (could be INSERT, UPDATE, DELETE)
37143        // MERGE actions are stored as Tuples with the action keyword as first element
37144        self.generate_merge_action(&e.then)?;
37145
37146        Ok(())
37147    }
37148
37149    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
37150        match action {
37151            Expression::Tuple(tuple) => {
37152                let elements = &tuple.expressions;
37153                if elements.is_empty() {
37154                    return self.generate_expression(action);
37155                }
37156                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
37157                match &elements[0] {
37158                    Expression::Var(v) if v.this == "INSERT" => {
37159                        self.write_keyword("INSERT");
37160                        // Spark: INSERT * (insert all columns)
37161                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
37162                            self.write(" *");
37163                        } else {
37164                            let mut values_idx = 1;
37165                            // Check if second element is column list (Tuple)
37166                            if elements.len() > 1 {
37167                                if let Expression::Tuple(cols) = &elements[1] {
37168                                    // Could be columns or values - if there's a third element, second is columns
37169                                    if elements.len() > 2 {
37170                                        // Second is columns, third is values
37171                                        self.write(" (");
37172                                        for (i, col) in cols.expressions.iter().enumerate() {
37173                                            if i > 0 {
37174                                                self.write(", ");
37175                                            }
37176                                            // Strip MERGE target qualifiers from INSERT column list
37177                                            if !self.merge_strip_qualifiers.is_empty() {
37178                                                let stripped = self.strip_merge_qualifier(col);
37179                                                self.generate_expression(&stripped)?;
37180                                            } else {
37181                                                self.generate_expression(col)?;
37182                                            }
37183                                        }
37184                                        self.write(")");
37185                                        values_idx = 2;
37186                                    } else {
37187                                        // Only two elements: INSERT + values (no explicit columns)
37188                                        values_idx = 1;
37189                                    }
37190                                }
37191                            }
37192                            // Generate VALUES clause
37193                            if values_idx < elements.len() {
37194                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
37195                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
37196                                if !is_row {
37197                                    self.write_space();
37198                                    self.write_keyword("VALUES");
37199                                }
37200                                self.write(" ");
37201                                if let Expression::Tuple(vals) = &elements[values_idx] {
37202                                    self.write("(");
37203                                    for (i, val) in vals.expressions.iter().enumerate() {
37204                                        if i > 0 {
37205                                            self.write(", ");
37206                                        }
37207                                        self.generate_expression(val)?;
37208                                    }
37209                                    self.write(")");
37210                                } else {
37211                                    self.generate_expression(&elements[values_idx])?;
37212                                }
37213                            }
37214                        } // close else for INSERT * check
37215                    }
37216                    Expression::Var(v) if v.this == "UPDATE" => {
37217                        self.write_keyword("UPDATE");
37218                        // Spark: UPDATE * (update all columns)
37219                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
37220                            self.write(" *");
37221                        } else if elements.len() > 1 {
37222                            self.write_space();
37223                            self.write_keyword("SET");
37224                            // In pretty mode, put assignments on next line with extra indent
37225                            if self.config.pretty {
37226                                self.write_newline();
37227                                self.indent_level += 1;
37228                                self.write_indent();
37229                            } else {
37230                                self.write_space();
37231                            }
37232                            if let Expression::Tuple(assignments) = &elements[1] {
37233                                for (i, assignment) in assignments.expressions.iter().enumerate() {
37234                                    if i > 0 {
37235                                        if self.config.pretty {
37236                                            self.write(",");
37237                                            self.write_newline();
37238                                            self.write_indent();
37239                                        } else {
37240                                            self.write(", ");
37241                                        }
37242                                    }
37243                                    // Strip MERGE target qualifiers from left side of UPDATE SET
37244                                    if !self.merge_strip_qualifiers.is_empty() {
37245                                        self.generate_merge_set_assignment(assignment)?;
37246                                    } else {
37247                                        self.generate_expression(assignment)?;
37248                                    }
37249                                }
37250                            } else {
37251                                self.generate_expression(&elements[1])?;
37252                            }
37253                            if self.config.pretty {
37254                                self.indent_level -= 1;
37255                            }
37256                        }
37257                    }
37258                    _ => {
37259                        // Fallback: generic tuple generation
37260                        self.generate_expression(action)?;
37261                    }
37262                }
37263            }
37264            Expression::Var(v)
37265                if v.this == "INSERT"
37266                    || v.this == "UPDATE"
37267                    || v.this == "DELETE"
37268                    || v.this == "DO NOTHING" =>
37269            {
37270                self.write_keyword(&v.this);
37271            }
37272            _ => {
37273                self.generate_expression(action)?;
37274            }
37275        }
37276        Ok(())
37277    }
37278
37279    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
37280    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
37281        match assignment {
37282            Expression::Eq(eq) => {
37283                // Strip qualifier from the left side if it matches a MERGE target name
37284                let stripped_left = self.strip_merge_qualifier(&eq.left);
37285                self.generate_expression(&stripped_left)?;
37286                self.write(" = ");
37287                self.generate_expression(&eq.right)?;
37288                Ok(())
37289            }
37290            other => self.generate_expression(other),
37291        }
37292    }
37293
37294    /// Strip table qualifier from a column reference if it matches a MERGE target name
37295    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
37296        match expr {
37297            Expression::Column(col) => {
37298                if let Some(ref table_ident) = col.table {
37299                    if self
37300                        .merge_strip_qualifiers
37301                        .iter()
37302                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
37303                    {
37304                        // Strip the table qualifier
37305                        let mut col = col.clone();
37306                        col.table = None;
37307                        return Expression::Column(col);
37308                    }
37309                }
37310                expr.clone()
37311            }
37312            Expression::Dot(dot) => {
37313                // table.column -> column (strip qualifier)
37314                if let Expression::Identifier(id) = &dot.this {
37315                    if self
37316                        .merge_strip_qualifiers
37317                        .iter()
37318                        .any(|n| n.eq_ignore_ascii_case(&id.name))
37319                    {
37320                        return Expression::Identifier(dot.field.clone());
37321                    }
37322                }
37323                expr.clone()
37324            }
37325            _ => expr.clone(),
37326        }
37327    }
37328
37329    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
37330        // Python: return self.expressions(expression, sep=" ", indent=False)
37331        for (i, expr) in e.expressions.iter().enumerate() {
37332            if i > 0 {
37333                // In pretty mode, each WHEN clause on its own line
37334                if self.config.pretty {
37335                    self.write_newline();
37336                    self.write_indent();
37337                } else {
37338                    self.write_space();
37339                }
37340            }
37341            self.generate_expression(expr)?;
37342        }
37343        Ok(())
37344    }
37345
37346    fn generate_where(&mut self, e: &Where) -> Result<()> {
37347        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
37348        self.write_keyword("WHERE");
37349        self.write_space();
37350        self.generate_expression(&e.this)?;
37351        Ok(())
37352    }
37353
37354    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
37355        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
37356        self.write_keyword("WIDTH_BUCKET");
37357        self.write("(");
37358        self.generate_expression(&e.this)?;
37359        if let Some(min_value) = &e.min_value {
37360            self.write(", ");
37361            self.generate_expression(min_value)?;
37362        }
37363        if let Some(max_value) = &e.max_value {
37364            self.write(", ");
37365            self.generate_expression(max_value)?;
37366        }
37367        if let Some(num_buckets) = &e.num_buckets {
37368            self.write(", ");
37369            self.generate_expression(num_buckets)?;
37370        }
37371        self.write(")");
37372        Ok(())
37373    }
37374
37375    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
37376        // Window specification: PARTITION BY ... ORDER BY ... frame
37377        self.generate_window_spec(e)
37378    }
37379
37380    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
37381        // Window specification: PARTITION BY ... ORDER BY ... frame
37382        let mut has_content = false;
37383
37384        // PARTITION BY
37385        if !e.partition_by.is_empty() {
37386            self.write_keyword("PARTITION BY");
37387            self.write_space();
37388            for (i, expr) in e.partition_by.iter().enumerate() {
37389                if i > 0 {
37390                    self.write(", ");
37391                }
37392                self.generate_expression(expr)?;
37393            }
37394            has_content = true;
37395        }
37396
37397        // ORDER BY
37398        if !e.order_by.is_empty() {
37399            if has_content {
37400                self.write_space();
37401            }
37402            self.write_keyword("ORDER BY");
37403            self.write_space();
37404            for (i, ordered) in e.order_by.iter().enumerate() {
37405                if i > 0 {
37406                    self.write(", ");
37407                }
37408                self.generate_expression(&ordered.this)?;
37409                if ordered.desc {
37410                    self.write_space();
37411                    self.write_keyword("DESC");
37412                } else if ordered.explicit_asc {
37413                    self.write_space();
37414                    self.write_keyword("ASC");
37415                }
37416                if let Some(nulls_first) = ordered.nulls_first {
37417                    self.write_space();
37418                    self.write_keyword("NULLS");
37419                    self.write_space();
37420                    if nulls_first {
37421                        self.write_keyword("FIRST");
37422                    } else {
37423                        self.write_keyword("LAST");
37424                    }
37425                }
37426            }
37427            has_content = true;
37428        }
37429
37430        // Frame specification
37431        if let Some(frame) = &e.frame {
37432            if has_content {
37433                self.write_space();
37434            }
37435            self.generate_window_frame(frame)?;
37436        }
37437
37438        Ok(())
37439    }
37440
37441    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
37442        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
37443        self.write_keyword("WITH");
37444        self.write_space();
37445        if e.no.is_some() {
37446            self.write_keyword("NO");
37447            self.write_space();
37448        }
37449        self.write_keyword("DATA");
37450
37451        // statistics
37452        if let Some(statistics) = &e.statistics {
37453            self.write_space();
37454            self.write_keyword("AND");
37455            self.write_space();
37456            // Check if statistics is true or false
37457            match statistics.as_ref() {
37458                Expression::Boolean(b) if !b.value => {
37459                    self.write_keyword("NO");
37460                    self.write_space();
37461                }
37462                _ => {}
37463            }
37464            self.write_keyword("STATISTICS");
37465        }
37466        Ok(())
37467    }
37468
37469    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
37470        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
37471        self.write_keyword("WITH FILL");
37472
37473        if let Some(from_) = &e.from_ {
37474            self.write_space();
37475            self.write_keyword("FROM");
37476            self.write_space();
37477            self.generate_expression(from_)?;
37478        }
37479
37480        if let Some(to) = &e.to {
37481            self.write_space();
37482            self.write_keyword("TO");
37483            self.write_space();
37484            self.generate_expression(to)?;
37485        }
37486
37487        if let Some(step) = &e.step {
37488            self.write_space();
37489            self.write_keyword("STEP");
37490            self.write_space();
37491            self.generate_expression(step)?;
37492        }
37493
37494        if let Some(staleness) = &e.staleness {
37495            self.write_space();
37496            self.write_keyword("STALENESS");
37497            self.write_space();
37498            self.generate_expression(staleness)?;
37499        }
37500
37501        if let Some(interpolate) = &e.interpolate {
37502            self.write_space();
37503            self.write_keyword("INTERPOLATE");
37504            self.write(" (");
37505            // INTERPOLATE items use reversed alias format: name AS expression
37506            self.generate_interpolate_item(interpolate)?;
37507            self.write(")");
37508        }
37509
37510        Ok(())
37511    }
37512
37513    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
37514    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
37515        match expr {
37516            Expression::Alias(alias) => {
37517                // Output as: alias_name AS expression
37518                self.generate_identifier(&alias.alias)?;
37519                self.write_space();
37520                self.write_keyword("AS");
37521                self.write_space();
37522                self.generate_expression(&alias.this)?;
37523            }
37524            Expression::Tuple(tuple) => {
37525                for (i, item) in tuple.expressions.iter().enumerate() {
37526                    if i > 0 {
37527                        self.write(", ");
37528                    }
37529                    self.generate_interpolate_item(item)?;
37530                }
37531            }
37532            other => {
37533                self.generate_expression(other)?;
37534            }
37535        }
37536        Ok(())
37537    }
37538
37539    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
37540        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
37541        self.write_keyword("WITH JOURNAL TABLE");
37542        self.write("=");
37543        self.generate_expression(&e.this)?;
37544        Ok(())
37545    }
37546
37547    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
37548        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
37549        self.generate_expression(&e.this)?;
37550        self.write_space();
37551        self.write_keyword("WITH");
37552        self.write_space();
37553        self.write_keyword(&e.op);
37554        Ok(())
37555    }
37556
37557    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
37558        // Python: return f"WITH {self.expressions(expression, flat=True)}"
37559        self.write_keyword("WITH");
37560        self.write_space();
37561        for (i, expr) in e.expressions.iter().enumerate() {
37562            if i > 0 {
37563                self.write(", ");
37564            }
37565            self.generate_expression(expr)?;
37566        }
37567        Ok(())
37568    }
37569
37570    fn generate_with_schema_binding_property(
37571        &mut self,
37572        e: &WithSchemaBindingProperty,
37573    ) -> Result<()> {
37574        // Python: return f"WITH {self.sql(expression, 'this')}"
37575        self.write_keyword("WITH");
37576        self.write_space();
37577        self.generate_expression(&e.this)?;
37578        Ok(())
37579    }
37580
37581    fn generate_with_system_versioning_property(
37582        &mut self,
37583        e: &WithSystemVersioningProperty,
37584    ) -> Result<()> {
37585        // Python: complex logic for SYSTEM_VERSIONING with options
37586        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
37587        // or SYSTEM_VERSIONING=ON/OFF
37588        // with WITH(...) wrapper if with_ is set
37589
37590        let mut parts = Vec::new();
37591
37592        if let Some(this) = &e.this {
37593            // HISTORY_TABLE=...
37594            let mut s = String::from("HISTORY_TABLE=");
37595            let mut gen = Generator::new();
37596            gen.generate_expression(this)?;
37597            s.push_str(&gen.output);
37598            parts.push(s);
37599        }
37600
37601        if let Some(data_consistency) = &e.data_consistency {
37602            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
37603            let mut gen = Generator::new();
37604            gen.generate_expression(data_consistency)?;
37605            s.push_str(&gen.output);
37606            parts.push(s);
37607        }
37608
37609        if let Some(retention_period) = &e.retention_period {
37610            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
37611            let mut gen = Generator::new();
37612            gen.generate_expression(retention_period)?;
37613            s.push_str(&gen.output);
37614            parts.push(s);
37615        }
37616
37617        self.write_keyword("SYSTEM_VERSIONING");
37618        self.write("=");
37619
37620        if !parts.is_empty() {
37621            self.write_keyword("ON");
37622            self.write("(");
37623            self.write(&parts.join(", "));
37624            self.write(")");
37625        } else if e.on.is_some() {
37626            self.write_keyword("ON");
37627        } else {
37628            self.write_keyword("OFF");
37629        }
37630
37631        // Wrap in WITH(...) if with_ is set
37632        if e.with_.is_some() {
37633            let inner = self.output.clone();
37634            self.output.clear();
37635            self.write("WITH(");
37636            self.write(&inner);
37637            self.write(")");
37638        }
37639
37640        Ok(())
37641    }
37642
37643    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
37644        // Python: f"WITH ({self.expressions(expression, flat=True)})"
37645        self.write_keyword("WITH");
37646        self.write(" (");
37647        for (i, expr) in e.expressions.iter().enumerate() {
37648            if i > 0 {
37649                self.write(", ");
37650            }
37651            self.generate_expression(expr)?;
37652        }
37653        self.write(")");
37654        Ok(())
37655    }
37656
37657    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
37658        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
37659        // return self.func("XMLELEMENT", name, *expression.expressions)
37660        self.write_keyword("XMLELEMENT");
37661        self.write("(");
37662
37663        if e.evalname.is_some() {
37664            self.write_keyword("EVALNAME");
37665        } else {
37666            self.write_keyword("NAME");
37667        }
37668        self.write_space();
37669        self.generate_expression(&e.this)?;
37670
37671        for expr in &e.expressions {
37672            self.write(", ");
37673            self.generate_expression(expr)?;
37674        }
37675        self.write(")");
37676        Ok(())
37677    }
37678
37679    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
37680        // XMLGET(this, expression [, instance])
37681        self.write_keyword("XMLGET");
37682        self.write("(");
37683        self.generate_expression(&e.this)?;
37684        self.write(", ");
37685        self.generate_expression(&e.expression)?;
37686        if let Some(instance) = &e.instance {
37687            self.write(", ");
37688            self.generate_expression(instance)?;
37689        }
37690        self.write(")");
37691        Ok(())
37692    }
37693
37694    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
37695        // Python: this + optional (expr)
37696        self.generate_expression(&e.this)?;
37697        if let Some(expression) = &e.expression {
37698            self.write("(");
37699            self.generate_expression(expression)?;
37700            self.write(")");
37701        }
37702        Ok(())
37703    }
37704
37705    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
37706        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
37707        self.write_keyword("XMLTABLE");
37708        self.write("(");
37709
37710        if self.config.pretty {
37711            self.indent_level += 1;
37712            self.write_newline();
37713            self.write_indent();
37714            self.generate_expression(&e.this)?;
37715
37716            if let Some(passing) = &e.passing {
37717                self.write_newline();
37718                self.write_indent();
37719                self.write_keyword("PASSING");
37720                if let Expression::Tuple(tuple) = passing.as_ref() {
37721                    for expr in &tuple.expressions {
37722                        self.write_newline();
37723                        self.indent_level += 1;
37724                        self.write_indent();
37725                        self.generate_expression(expr)?;
37726                        self.indent_level -= 1;
37727                    }
37728                } else {
37729                    self.write_newline();
37730                    self.indent_level += 1;
37731                    self.write_indent();
37732                    self.generate_expression(passing)?;
37733                    self.indent_level -= 1;
37734                }
37735            }
37736
37737            if e.by_ref.is_some() {
37738                self.write_newline();
37739                self.write_indent();
37740                self.write_keyword("RETURNING SEQUENCE BY REF");
37741            }
37742
37743            if !e.columns.is_empty() {
37744                self.write_newline();
37745                self.write_indent();
37746                self.write_keyword("COLUMNS");
37747                for (i, col) in e.columns.iter().enumerate() {
37748                    self.write_newline();
37749                    self.indent_level += 1;
37750                    self.write_indent();
37751                    self.generate_expression(col)?;
37752                    self.indent_level -= 1;
37753                    if i < e.columns.len() - 1 {
37754                        self.write(",");
37755                    }
37756                }
37757            }
37758
37759            self.indent_level -= 1;
37760            self.write_newline();
37761            self.write_indent();
37762            self.write(")");
37763            return Ok(());
37764        }
37765
37766        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
37767        if let Some(namespaces) = &e.namespaces {
37768            self.write_keyword("XMLNAMESPACES");
37769            self.write("(");
37770            // Unwrap Tuple if present to avoid extra parentheses
37771            if let Expression::Tuple(tuple) = namespaces.as_ref() {
37772                for (i, expr) in tuple.expressions.iter().enumerate() {
37773                    if i > 0 {
37774                        self.write(", ");
37775                    }
37776                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
37777                    // See xmlnamespace_sql in generator.py
37778                    if !matches!(expr, Expression::Alias(_)) {
37779                        self.write_keyword("DEFAULT");
37780                        self.write_space();
37781                    }
37782                    self.generate_expression(expr)?;
37783                }
37784            } else {
37785                // Single namespace - check if DEFAULT
37786                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
37787                    self.write_keyword("DEFAULT");
37788                    self.write_space();
37789                }
37790                self.generate_expression(namespaces)?;
37791            }
37792            self.write("), ");
37793        }
37794
37795        // XPath expression
37796        self.generate_expression(&e.this)?;
37797
37798        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
37799        if let Some(passing) = &e.passing {
37800            self.write_space();
37801            self.write_keyword("PASSING");
37802            self.write_space();
37803            // Unwrap Tuple if present to avoid extra parentheses
37804            if let Expression::Tuple(tuple) = passing.as_ref() {
37805                for (i, expr) in tuple.expressions.iter().enumerate() {
37806                    if i > 0 {
37807                        self.write(", ");
37808                    }
37809                    self.generate_expression(expr)?;
37810                }
37811            } else {
37812                self.generate_expression(passing)?;
37813            }
37814        }
37815
37816        // RETURNING SEQUENCE BY REF
37817        if e.by_ref.is_some() {
37818            self.write_space();
37819            self.write_keyword("RETURNING SEQUENCE BY REF");
37820        }
37821
37822        // COLUMNS clause
37823        if !e.columns.is_empty() {
37824            self.write_space();
37825            self.write_keyword("COLUMNS");
37826            self.write_space();
37827            for (i, col) in e.columns.iter().enumerate() {
37828                if i > 0 {
37829                    self.write(", ");
37830                }
37831                self.generate_expression(col)?;
37832            }
37833        }
37834
37835        self.write(")");
37836        Ok(())
37837    }
37838
37839    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
37840        // Python: return self.connector_sql(expression, "XOR", stack)
37841        // Handles: this XOR expression or expressions joined by XOR
37842        if let Some(this) = &e.this {
37843            self.generate_expression(this)?;
37844            if let Some(expression) = &e.expression {
37845                self.write_space();
37846                self.write_keyword("XOR");
37847                self.write_space();
37848                self.generate_expression(expression)?;
37849            }
37850        }
37851
37852        // Handle multiple expressions
37853        for (i, expr) in e.expressions.iter().enumerate() {
37854            if i > 0 || e.this.is_some() {
37855                self.write_space();
37856                self.write_keyword("XOR");
37857                self.write_space();
37858            }
37859            self.generate_expression(expr)?;
37860        }
37861        Ok(())
37862    }
37863
37864    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
37865        // ZIPF(this, elementcount [, gen])
37866        self.write_keyword("ZIPF");
37867        self.write("(");
37868        self.generate_expression(&e.this)?;
37869        if let Some(elementcount) = &e.elementcount {
37870            self.write(", ");
37871            self.generate_expression(elementcount)?;
37872        }
37873        if let Some(gen) = &e.gen {
37874            self.write(", ");
37875            self.generate_expression(gen)?;
37876        }
37877        self.write(")");
37878        Ok(())
37879    }
37880}
37881
37882impl Default for Generator {
37883    fn default() -> Self {
37884        Self::new()
37885    }
37886}
37887
37888#[cfg(test)]
37889mod tests {
37890    use super::*;
37891    use crate::parser::Parser;
37892
37893    fn roundtrip(sql: &str) -> String {
37894        let ast = Parser::parse_sql(sql).unwrap();
37895        Generator::sql(&ast[0]).unwrap()
37896    }
37897
37898    #[test]
37899    fn test_simple_select() {
37900        let result = roundtrip("SELECT 1");
37901        assert_eq!(result, "SELECT 1");
37902    }
37903
37904    #[test]
37905    fn test_select_from() {
37906        let result = roundtrip("SELECT a, b FROM t");
37907        assert_eq!(result, "SELECT a, b FROM t");
37908    }
37909
37910    #[test]
37911    fn test_select_where() {
37912        let result = roundtrip("SELECT * FROM t WHERE x = 1");
37913        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
37914    }
37915
37916    #[test]
37917    fn test_select_join() {
37918        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
37919        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
37920    }
37921
37922    #[test]
37923    fn test_insert() {
37924        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
37925        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
37926    }
37927
37928    #[test]
37929    fn test_pretty_print() {
37930        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
37931        let result = Generator::pretty_sql(&ast[0]).unwrap();
37932        assert!(result.contains('\n'));
37933    }
37934
37935    #[test]
37936    fn test_window_function() {
37937        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
37938        assert_eq!(
37939            result,
37940            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
37941        );
37942    }
37943
37944    #[test]
37945    fn test_window_function_with_frame() {
37946        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
37947        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
37948    }
37949
37950    #[test]
37951    fn test_aggregate_with_filter() {
37952        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
37953        assert_eq!(
37954            result,
37955            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
37956        );
37957    }
37958
37959    #[test]
37960    fn test_subscript() {
37961        let result = roundtrip("SELECT arr[0]");
37962        assert_eq!(result, "SELECT arr[0]");
37963    }
37964
37965    // DDL tests
37966    #[test]
37967    fn test_create_table() {
37968        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
37969        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
37970    }
37971
37972    #[test]
37973    fn test_create_table_with_constraints() {
37974        let result = roundtrip(
37975            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
37976        );
37977        assert_eq!(
37978            result,
37979            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
37980        );
37981    }
37982
37983    #[test]
37984    fn test_create_table_if_not_exists() {
37985        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
37986        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
37987    }
37988
37989    #[test]
37990    fn test_drop_table() {
37991        let result = roundtrip("DROP TABLE users");
37992        assert_eq!(result, "DROP TABLE users");
37993    }
37994
37995    #[test]
37996    fn test_drop_table_if_exists_cascade() {
37997        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
37998        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
37999    }
38000
38001    #[test]
38002    fn test_alter_table_add_column() {
38003        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
38004        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
38005    }
38006
38007    #[test]
38008    fn test_alter_table_drop_column() {
38009        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
38010        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
38011    }
38012
38013    #[test]
38014    fn test_create_index() {
38015        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
38016        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
38017    }
38018
38019    #[test]
38020    fn test_create_unique_index() {
38021        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
38022        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
38023    }
38024
38025    #[test]
38026    fn test_drop_index() {
38027        let result = roundtrip("DROP INDEX idx_name");
38028        assert_eq!(result, "DROP INDEX idx_name");
38029    }
38030
38031    #[test]
38032    fn test_create_view() {
38033        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
38034        assert_eq!(
38035            result,
38036            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
38037        );
38038    }
38039
38040    #[test]
38041    fn test_drop_view() {
38042        let result = roundtrip("DROP VIEW active_users");
38043        assert_eq!(result, "DROP VIEW active_users");
38044    }
38045
38046    #[test]
38047    fn test_truncate() {
38048        let result = roundtrip("TRUNCATE TABLE users");
38049        assert_eq!(result, "TRUNCATE TABLE users");
38050    }
38051
38052    #[test]
38053    fn test_string_literal_escaping_default() {
38054        // Default: double single quotes
38055        let result = roundtrip("SELECT 'hello'");
38056        assert_eq!(result, "SELECT 'hello'");
38057
38058        // Single quotes are doubled
38059        let result = roundtrip("SELECT 'it''s a test'");
38060        assert_eq!(result, "SELECT 'it''s a test'");
38061    }
38062
38063    #[test]
38064    fn test_not_in_style_prefix_default_generic() {
38065        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
38066        assert_eq!(
38067            result,
38068            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
38069        );
38070    }
38071
38072    #[test]
38073    fn test_not_in_style_infix_generic_override() {
38074        let ast =
38075            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
38076                .unwrap();
38077        let config = GeneratorConfig {
38078            not_in_style: NotInStyle::Infix,
38079            ..Default::default()
38080        };
38081        let mut gen = Generator::with_config(config);
38082        let result = gen.generate(&ast[0]).unwrap();
38083        assert_eq!(
38084            result,
38085            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
38086        );
38087    }
38088
38089    #[test]
38090    fn test_string_literal_escaping_mysql() {
38091        use crate::dialects::DialectType;
38092
38093        let config = GeneratorConfig {
38094            dialect: Some(DialectType::MySQL),
38095            ..Default::default()
38096        };
38097
38098        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38099        let mut gen = Generator::with_config(config.clone());
38100        let result = gen.generate(&ast[0]).unwrap();
38101        assert_eq!(result, "SELECT 'hello'");
38102
38103        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
38104        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38105        let mut gen = Generator::with_config(config.clone());
38106        let result = gen.generate(&ast[0]).unwrap();
38107        assert_eq!(result, "SELECT 'it''s'");
38108    }
38109
38110    #[test]
38111    fn test_string_literal_escaping_postgres() {
38112        use crate::dialects::DialectType;
38113
38114        let config = GeneratorConfig {
38115            dialect: Some(DialectType::PostgreSQL),
38116            ..Default::default()
38117        };
38118
38119        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38120        let mut gen = Generator::with_config(config.clone());
38121        let result = gen.generate(&ast[0]).unwrap();
38122        assert_eq!(result, "SELECT 'hello'");
38123
38124        // PostgreSQL uses doubled quotes for regular strings
38125        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38126        let mut gen = Generator::with_config(config.clone());
38127        let result = gen.generate(&ast[0]).unwrap();
38128        assert_eq!(result, "SELECT 'it''s'");
38129    }
38130
38131    #[test]
38132    fn test_string_literal_escaping_bigquery() {
38133        use crate::dialects::DialectType;
38134
38135        let config = GeneratorConfig {
38136            dialect: Some(DialectType::BigQuery),
38137            ..Default::default()
38138        };
38139
38140        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38141        let mut gen = Generator::with_config(config.clone());
38142        let result = gen.generate(&ast[0]).unwrap();
38143        assert_eq!(result, "SELECT 'hello'");
38144
38145        // BigQuery escapes single quotes with backslash
38146        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38147        let mut gen = Generator::with_config(config.clone());
38148        let result = gen.generate(&ast[0]).unwrap();
38149        assert_eq!(result, "SELECT 'it\\'s'");
38150    }
38151
38152    #[test]
38153    fn test_generate_deep_and_chain_without_stack_growth() {
38154        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
38155            Expression::column("c0"),
38156            Expression::number(0),
38157        )));
38158
38159        for i in 1..2500 {
38160            let predicate = Expression::Eq(Box::new(BinaryOp::new(
38161                Expression::column(format!("c{i}")),
38162                Expression::number(i as i64),
38163            )));
38164            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
38165        }
38166
38167        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
38168        assert!(sql.contains("c2499 = 2499"), "{}", sql);
38169    }
38170
38171    #[test]
38172    fn test_generate_deep_or_chain_without_stack_growth() {
38173        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
38174            Expression::column("c0"),
38175            Expression::number(0),
38176        )));
38177
38178        for i in 1..2500 {
38179            let predicate = Expression::Eq(Box::new(BinaryOp::new(
38180                Expression::column(format!("c{i}")),
38181                Expression::number(i as i64),
38182            )));
38183            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
38184        }
38185
38186        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
38187        assert!(sql.contains("c2499 = 2499"), "{}", sql);
38188    }
38189}