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 crate::error::Result;
14use crate::expressions::*;
15use crate::DialectType;
16
17/// SQL code generator that converts an AST (`Expression`) back into a SQL string.
18///
19/// The generator walks the expression tree and emits dialect-specific SQL text.
20/// It supports pretty-printing with configurable indentation, identifier quoting,
21/// keyword casing, function name normalization, and 30+ SQL dialect variants.
22///
23/// # Usage
24///
25/// ```rust,ignore
26/// use polyglot_sql::generator::Generator;
27/// use polyglot_sql::parser::Parser;
28///
29/// let ast = Parser::parse_sql("SELECT 1")?;
30/// // Quick one-shot generation (default config):
31/// let sql = Generator::sql(&ast[0])?;
32///
33/// // Pretty-printed output:
34/// let pretty = Generator::pretty_sql(&ast[0])?;
35///
36/// // Custom config (e.g. for a specific dialect):
37/// let config = GeneratorConfig { pretty: true, ..GeneratorConfig::default() };
38/// let mut gen = Generator::with_config(config);
39/// let sql = gen.generate(&ast[0])?;
40/// ```
41pub struct Generator {
42    config: GeneratorConfig,
43    output: String,
44    indent_level: usize,
45    /// Athena dialect: true when generating Hive-style DDL (uses backticks)
46    /// false when generating Trino-style DML/CREATE VIEW (uses double quotes)
47    athena_hive_context: bool,
48    /// SQLite: column names that should have PRIMARY KEY inlined (from single-column table constraints)
49    sqlite_inline_pk_columns: std::collections::HashSet<String>,
50    /// MERGE: table name/alias qualifiers to strip from UPDATE SET left side (for PostgreSQL)
51    merge_strip_qualifiers: Vec<String>,
52    /// ClickHouse: depth counter for Nullable wrapping context in CAST types.
53    /// 0 = not in cast context, 1 = top-level cast type, 2+ = inside container type.
54    /// Positive values indicate the type should be wrapped in Nullable (for non-container types).
55    /// Negative values indicate map key context (should NOT be wrapped).
56    clickhouse_nullable_depth: i32,
57}
58
59/// Controls how SQL function names are cased in generated output.
60///
61/// - `Upper` (default) -- `COUNT`, `SUM`, `COALESCE`
62/// - `Lower` -- `count`, `sum`, `coalesce`
63/// - `None` -- preserve the original casing from the parsed input
64#[derive(Debug, Clone, Copy, PartialEq, Default)]
65pub enum NormalizeFunctions {
66    /// Emit function names in UPPER CASE (default).
67    #[default]
68    Upper,
69    /// Emit function names in lower case.
70    Lower,
71    /// Preserve the original casing from the parsed input.
72    None,
73}
74
75/// Strategy for generating row-limiting clauses across SQL dialects.
76#[derive(Debug, Clone, Copy, PartialEq, Default)]
77pub enum LimitFetchStyle {
78    /// `LIMIT n` -- MySQL, PostgreSQL, DuckDB, and most modern dialects.
79    #[default]
80    Limit,
81    /// `TOP n` -- TSQL (SQL Server).
82    Top,
83    /// `FETCH FIRST n ROWS ONLY` -- ISO/ANSI SQL standard, Oracle, DB2.
84    FetchFirst,
85}
86
87/// Strategy for rendering negated IN predicates.
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
89pub enum NotInStyle {
90    /// Emit `NOT x IN (...)` in generic mode (current compatibility behavior).
91    #[default]
92    Prefix,
93    /// Emit `x NOT IN (...)` in generic mode (canonical SQL style).
94    Infix,
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98enum ConnectorOperator {
99    And,
100    Or,
101}
102
103impl ConnectorOperator {
104    fn keyword(self) -> &'static str {
105        match self {
106            Self::And => "AND",
107            Self::Or => "OR",
108        }
109    }
110}
111
112/// Identifier quote style (start/end characters)
113#[derive(Debug, Clone, Copy, PartialEq)]
114pub struct IdentifierQuoteStyle {
115    /// Start character for quoting identifiers (e.g., '"', '`', '[')
116    pub start: char,
117    /// End character for quoting identifiers (e.g., '"', '`', ']')
118    pub end: char,
119}
120
121impl Default for IdentifierQuoteStyle {
122    fn default() -> Self {
123        Self {
124            start: '"',
125            end: '"',
126        }
127    }
128}
129
130impl IdentifierQuoteStyle {
131    /// Double-quote style (PostgreSQL, Oracle, standard SQL)
132    pub const DOUBLE_QUOTE: Self = Self {
133        start: '"',
134        end: '"',
135    };
136    /// Backtick style (MySQL, BigQuery, Spark, Hive)
137    pub const BACKTICK: Self = Self {
138        start: '`',
139        end: '`',
140    };
141    /// Square bracket style (TSQL, SQLite)
142    pub const BRACKET: Self = Self {
143        start: '[',
144        end: ']',
145    };
146}
147
148/// Configuration for the SQL [`Generator`].
149///
150/// This is a comprehensive port of the Python sqlglot `Generator` class attributes.
151/// It controls every aspect of SQL output: formatting, quoting, dialect-specific
152/// syntax, feature support flags, and more.
153///
154/// Most users should start from `GeneratorConfig::default()` and override only the
155/// fields they need. Dialect-specific presets are applied automatically when
156/// `dialect` is set via the higher-level transpilation API.
157///
158/// # Key fields
159///
160/// | Field | Default | Purpose |
161/// |-------|---------|---------|
162/// | `dialect` | `None` | Target SQL dialect (e.g. PostgreSQL, MySQL, BigQuery) |
163/// | `pretty` | `false` | Enable multi-line, indented output |
164/// | `indent` | `"  "` | Indentation string used when `pretty` is true |
165/// | `max_text_width` | `80` | Soft line-width limit for pretty-printing |
166/// | `normalize_functions` | `Upper` | Function name casing (`Upper`, `Lower`, `None`) |
167/// | `identifier_quote_style` | `"…"` | Quote characters for identifiers |
168/// | `uppercase_keywords` | `true` | Whether SQL keywords are upper-cased |
169#[derive(Debug, Clone)]
170pub struct GeneratorConfig {
171    // ===== Basic formatting =====
172    /// Pretty print with indentation
173    pub pretty: bool,
174    /// Indentation string (default 2 spaces)
175    pub indent: String,
176    /// Maximum text width before wrapping (default 80)
177    pub max_text_width: usize,
178    /// Quote identifier style (deprecated, use identifier_quote_style instead)
179    pub identifier_quote: char,
180    /// Identifier quote style with separate start/end characters
181    pub identifier_quote_style: IdentifierQuoteStyle,
182    /// Uppercase keywords
183    pub uppercase_keywords: bool,
184    /// Normalize identifiers to lowercase when generating
185    pub normalize_identifiers: bool,
186    /// Dialect type for dialect-specific generation
187    pub dialect: Option<crate::dialects::DialectType>,
188    /// Source dialect type (used during transpilation to distinguish identity vs cross-dialect)
189    pub source_dialect: Option<crate::dialects::DialectType>,
190    /// How to output function names (UPPER, lower, or as-is)
191    pub normalize_functions: NormalizeFunctions,
192    /// String escape character
193    pub string_escape: char,
194    /// Whether identifiers are case-sensitive
195    pub case_sensitive_identifiers: bool,
196    /// Whether unquoted identifiers can start with a digit
197    pub identifiers_can_start_with_digit: bool,
198    /// Whether to always quote identifiers regardless of reserved keyword status
199    /// Used by dialects like Athena/Presto that prefer quoted identifiers
200    pub always_quote_identifiers: bool,
201    /// How to render negated IN predicates in generic output.
202    pub not_in_style: NotInStyle,
203
204    // ===== Null handling =====
205    /// Whether null ordering (NULLS FIRST/LAST) is supported in ORDER BY
206    /// True: Full Support, false: No support
207    pub null_ordering_supported: bool,
208    /// Whether ignore nulls is inside the agg or outside
209    /// FIRST(x IGNORE NULLS) OVER vs FIRST(x) IGNORE NULLS OVER
210    pub ignore_nulls_in_func: bool,
211    /// Whether the NVL2 function is supported
212    pub nvl2_supported: bool,
213
214    // ===== Limit/Fetch =====
215    /// How to output LIMIT clauses
216    pub limit_fetch_style: LimitFetchStyle,
217    /// Whether to generate the limit as TOP <value> instead of LIMIT <value>
218    pub limit_is_top: bool,
219    /// Whether limit and fetch allows expressions or just literals
220    pub limit_only_literals: bool,
221
222    // ===== Interval =====
223    /// Whether INTERVAL uses single quoted string ('1 day' vs 1 DAY)
224    pub single_string_interval: bool,
225    /// Whether the plural form of date parts (e.g., "days") is supported in INTERVALs
226    pub interval_allows_plural_form: bool,
227
228    // ===== CTE =====
229    /// Whether WITH RECURSIVE keyword is required (vs just WITH for recursive CTEs)
230    pub cte_recursive_keyword_required: bool,
231
232    // ===== VALUES =====
233    /// Whether VALUES can be used as a table source
234    pub values_as_table: bool,
235    /// Wrap derived values in parens (standard but Spark doesn't support)
236    pub wrap_derived_values: bool,
237
238    // ===== TABLESAMPLE =====
239    /// Keyword for TABLESAMPLE seed: "SEED" or "REPEATABLE"
240    pub tablesample_seed_keyword: &'static str,
241    /// Whether parentheses are required around the table sample's expression
242    pub tablesample_requires_parens: bool,
243    /// Whether a table sample clause's size needs to be followed by ROWS keyword
244    pub tablesample_size_is_rows: bool,
245    /// The keyword(s) to use when generating a sample clause
246    pub tablesample_keywords: &'static str,
247    /// Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
248    pub tablesample_with_method: bool,
249    /// Whether the table alias comes after tablesample (Oracle, Hive)
250    pub alias_post_tablesample: bool,
251
252    // ===== Aggregate =====
253    /// Whether aggregate FILTER (WHERE ...) is supported
254    pub aggregate_filter_supported: bool,
255    /// Whether DISTINCT can be followed by multiple args in an AggFunc
256    pub multi_arg_distinct: bool,
257    /// Whether ANY/ALL quantifiers have no space before `(`: `ANY(` vs `ANY (`
258    pub quantified_no_paren_space: bool,
259    /// Whether MEDIAN(expr) is supported; if not, generates PERCENTILE_CONT
260    pub supports_median: bool,
261
262    // ===== SELECT =====
263    /// Whether SELECT ... INTO is supported
264    pub supports_select_into: bool,
265    /// Whether locking reads (SELECT ... FOR UPDATE/SHARE) are supported
266    pub locking_reads_supported: bool,
267
268    // ===== Table/Join =====
269    /// Whether a table is allowed to be renamed with a db
270    pub rename_table_with_db: bool,
271    /// Whether JOIN sides (LEFT, RIGHT) are supported with SEMI/ANTI join kinds
272    pub semi_anti_join_with_side: bool,
273    /// Whether named columns are allowed in table aliases
274    pub supports_table_alias_columns: bool,
275    /// Whether join hints should be generated
276    pub join_hints: bool,
277    /// Whether table hints should be generated
278    pub table_hints: bool,
279    /// Whether query hints should be generated
280    pub query_hints: bool,
281    /// What kind of separator to use for query hints
282    pub query_hint_sep: &'static str,
283    /// Whether Oracle-style (+) join markers are supported (Oracle, Exasol)
284    pub supports_column_join_marks: bool,
285
286    // ===== DDL =====
287    /// Whether CREATE INDEX USING method should have no space before column parens
288    /// true: `USING btree(col)`, false: `USING btree (col)`
289    pub index_using_no_space: bool,
290    /// Whether UNLOGGED tables can be created
291    pub supports_unlogged_tables: bool,
292    /// Whether CREATE TABLE LIKE statement is supported
293    pub supports_create_table_like: bool,
294    /// Whether the LikeProperty needs to be inside the schema clause
295    pub like_property_inside_schema: bool,
296    /// Whether the word COLUMN is included when adding a column with ALTER TABLE
297    pub alter_table_include_column_keyword: bool,
298    /// Whether CREATE TABLE .. COPY .. is supported (false = CLONE instead)
299    pub supports_table_copy: bool,
300    /// The syntax to use when altering the type of a column
301    pub alter_set_type: &'static str,
302    /// Whether to wrap <props> in AlterSet, e.g., ALTER ... SET (<props>)
303    pub alter_set_wrapped: bool,
304
305    // ===== Timestamp/Timezone =====
306    /// Whether TIMESTAMP WITH TIME ZONE is used (vs TIMESTAMPTZ)
307    pub tz_to_with_time_zone: bool,
308    /// Whether CONVERT_TIMEZONE() is supported
309    pub supports_convert_timezone: bool,
310
311    // ===== JSON =====
312    /// Whether the JSON extraction operators expect a value of type JSON
313    pub json_type_required_for_extraction: bool,
314    /// Whether bracketed keys like ["foo"] are supported in JSON paths
315    pub json_path_bracketed_key_supported: bool,
316    /// Whether to escape keys using single quotes in JSON paths
317    pub json_path_single_quote_escape: bool,
318    /// Whether to quote the generated expression of JsonPath
319    pub quote_json_path: bool,
320    /// What delimiter to use for separating JSON key/value pairs
321    pub json_key_value_pair_sep: &'static str,
322
323    // ===== COPY =====
324    /// Whether parameters from COPY statement are wrapped in parentheses
325    pub copy_params_are_wrapped: bool,
326    /// Whether values of params are set with "=" token or empty space
327    pub copy_params_eq_required: bool,
328    /// Whether COPY statement has INTO keyword
329    pub copy_has_into_keyword: bool,
330
331    // ===== Window functions =====
332    /// Whether EXCLUDE in window specification is supported
333    pub supports_window_exclude: bool,
334    /// UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
335    pub unnest_with_ordinality: bool,
336    /// Whether window frame keywords (ROWS/RANGE/GROUPS, PRECEDING/FOLLOWING) should be lowercase
337    /// Exasol uses lowercase for these specific keywords
338    pub lowercase_window_frame_keywords: bool,
339    /// Whether to normalize single-bound window frames to BETWEEN form
340    /// e.g., ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
341    pub normalize_window_frame_between: bool,
342
343    // ===== Array =====
344    /// Whether ARRAY_CONCAT can be generated with varlen args
345    pub array_concat_is_var_len: bool,
346    /// Whether exp.ArraySize should generate the dimension arg too
347    /// None -> Doesn't support, false -> optional, true -> required
348    pub array_size_dim_required: Option<bool>,
349    /// Whether any(f(x) for x in array) can be implemented
350    pub can_implement_array_any: bool,
351    /// Function used for array size
352    pub array_size_name: &'static str,
353
354    // ===== BETWEEN =====
355    /// Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN
356    pub supports_between_flags: bool,
357
358    // ===== Boolean =====
359    /// Whether comparing against booleans (e.g. x IS TRUE) is supported
360    pub is_bool_allowed: bool,
361    /// Whether conditions require booleans WHERE x = 0 vs WHERE x
362    pub ensure_bools: bool,
363
364    // ===== EXTRACT =====
365    /// Whether to generate an unquoted value for EXTRACT's date part argument
366    pub extract_allows_quotes: bool,
367    /// Whether to normalize date parts in EXTRACT
368    pub normalize_extract_date_parts: bool,
369
370    // ===== Other features =====
371    /// Whether the conditional TRY(expression) function is supported
372    pub try_supported: bool,
373    /// Whether the UESCAPE syntax in unicode strings is supported
374    pub supports_uescape: bool,
375    /// Whether the function TO_NUMBER is supported
376    pub supports_to_number: bool,
377    /// Whether CONCAT requires >1 arguments
378    pub supports_single_arg_concat: bool,
379    /// Whether LAST_DAY function supports a date part argument
380    pub last_day_supports_date_part: bool,
381    /// Whether a projection can explode into multiple rows
382    pub supports_exploding_projections: bool,
383    /// Whether UNIX_SECONDS(timestamp) is supported
384    pub supports_unix_seconds: bool,
385    /// Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
386    pub supports_like_quantifiers: bool,
387    /// Whether multi-argument DECODE(...) function is supported
388    pub supports_decode_case: bool,
389    /// Whether set op modifiers apply to the outer set op or select
390    pub set_op_modifiers: bool,
391    /// Whether FROM is supported in UPDATE statements
392    pub update_statement_supports_from: bool,
393
394    // ===== COLLATE =====
395    /// Whether COLLATE is a function instead of a binary operator
396    pub collate_is_func: bool,
397
398    // ===== INSERT =====
399    /// Whether to include "SET" keyword in "INSERT ... ON DUPLICATE KEY UPDATE"
400    pub duplicate_key_update_with_set: bool,
401    /// INSERT OVERWRITE TABLE x override
402    pub insert_overwrite: &'static str,
403
404    // ===== RETURNING =====
405    /// Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
406    pub returning_end: bool,
407
408    // ===== MERGE =====
409    /// Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
410    pub matched_by_source: bool,
411
412    // ===== CREATE FUNCTION =====
413    /// Whether create function uses an AS before the RETURN
414    pub create_function_return_as: bool,
415    /// Whether to use = instead of DEFAULT for parameter defaults (TSQL style)
416    pub parameter_default_equals: bool,
417
418    // ===== COMPUTED COLUMN =====
419    /// Whether to include the type of a computed column in the CREATE DDL
420    pub computed_column_with_type: bool,
421
422    // ===== UNPIVOT =====
423    /// Whether UNPIVOT aliases are Identifiers (false means they're Literals)
424    pub unpivot_aliases_are_identifiers: bool,
425
426    // ===== STAR =====
427    /// The keyword to use when generating a star projection with excluded columns
428    pub star_except: &'static str,
429
430    // ===== HEX =====
431    /// The HEX function name
432    pub hex_func: &'static str,
433
434    // ===== WITH =====
435    /// The keywords to use when prefixing WITH based properties
436    pub with_properties_prefix: &'static str,
437
438    // ===== PAD =====
439    /// Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional
440    pub pad_fill_pattern_is_required: bool,
441
442    // ===== INDEX =====
443    /// The string used for creating an index on a table
444    pub index_on: &'static str,
445
446    // ===== GROUPING =====
447    /// The separator for grouping sets and rollups
448    pub groupings_sep: &'static str,
449
450    // ===== STRUCT =====
451    /// Delimiters for STRUCT type
452    pub struct_delimiter: (&'static str, &'static str),
453    /// Whether Struct expressions use curly brace notation: {'key': value} (DuckDB)
454    pub struct_curly_brace_notation: bool,
455    /// Whether Array expressions omit the ARRAY keyword: [1, 2] instead of ARRAY[1, 2]
456    pub array_bracket_only: bool,
457    /// Separator between struct field name and type (": " for Hive, " " for others)
458    pub struct_field_sep: &'static str,
459
460    // ===== EXCEPT/INTERSECT =====
461    /// Whether EXCEPT and INTERSECT operations can return duplicates
462    pub except_intersect_support_all_clause: bool,
463
464    // ===== PARAMETERS/PLACEHOLDERS =====
465    /// Parameter token character (@ for TSQL, $ for PostgreSQL)
466    pub parameter_token: &'static str,
467    /// Named placeholder token (: for most, % for PostgreSQL)
468    pub named_placeholder_token: &'static str,
469
470    // ===== DATA TYPES =====
471    /// Whether data types support additional specifiers like CHAR or BYTE (oracle)
472    pub data_type_specifiers_allowed: bool,
473
474    // ===== COMMENT =====
475    /// Whether schema comments use `=` sign (COMMENT='value' vs COMMENT 'value')
476    /// StarRocks and Doris use naked COMMENT syntax without `=`
477    pub schema_comment_with_eq: bool,
478}
479
480impl Default for GeneratorConfig {
481    fn default() -> Self {
482        Self {
483            // ===== Basic formatting =====
484            pretty: false,
485            indent: "  ".to_string(),
486            max_text_width: 80,
487            identifier_quote: '"',
488            identifier_quote_style: IdentifierQuoteStyle::DOUBLE_QUOTE,
489            uppercase_keywords: true,
490            normalize_identifiers: false,
491            dialect: None,
492            source_dialect: None,
493            normalize_functions: NormalizeFunctions::Upper,
494            string_escape: '\'',
495            case_sensitive_identifiers: false,
496            identifiers_can_start_with_digit: false,
497            always_quote_identifiers: false,
498            not_in_style: NotInStyle::Prefix,
499
500            // ===== Null handling =====
501            null_ordering_supported: true,
502            ignore_nulls_in_func: false,
503            nvl2_supported: true,
504
505            // ===== Limit/Fetch =====
506            limit_fetch_style: LimitFetchStyle::Limit,
507            limit_is_top: false,
508            limit_only_literals: false,
509
510            // ===== Interval =====
511            single_string_interval: false,
512            interval_allows_plural_form: true,
513
514            // ===== CTE =====
515            cte_recursive_keyword_required: true,
516
517            // ===== VALUES =====
518            values_as_table: true,
519            wrap_derived_values: true,
520
521            // ===== TABLESAMPLE =====
522            tablesample_seed_keyword: "SEED",
523            tablesample_requires_parens: true,
524            tablesample_size_is_rows: true,
525            tablesample_keywords: "TABLESAMPLE",
526            tablesample_with_method: true,
527            alias_post_tablesample: false,
528
529            // ===== Aggregate =====
530            aggregate_filter_supported: true,
531            multi_arg_distinct: true,
532            quantified_no_paren_space: false,
533            supports_median: true,
534
535            // ===== SELECT =====
536            supports_select_into: false,
537            locking_reads_supported: true,
538
539            // ===== Table/Join =====
540            rename_table_with_db: true,
541            semi_anti_join_with_side: true,
542            supports_table_alias_columns: true,
543            join_hints: true,
544            table_hints: true,
545            query_hints: true,
546            query_hint_sep: ", ",
547            supports_column_join_marks: false,
548
549            // ===== DDL =====
550            index_using_no_space: false,
551            supports_unlogged_tables: false,
552            supports_create_table_like: true,
553            like_property_inside_schema: false,
554            alter_table_include_column_keyword: true,
555            supports_table_copy: true,
556            alter_set_type: "SET DATA TYPE",
557            alter_set_wrapped: false,
558
559            // ===== Timestamp/Timezone =====
560            tz_to_with_time_zone: false,
561            supports_convert_timezone: false,
562
563            // ===== JSON =====
564            json_type_required_for_extraction: false,
565            json_path_bracketed_key_supported: true,
566            json_path_single_quote_escape: false,
567            quote_json_path: true,
568            json_key_value_pair_sep: ":",
569
570            // ===== COPY =====
571            copy_params_are_wrapped: true,
572            copy_params_eq_required: false,
573            copy_has_into_keyword: true,
574
575            // ===== Window functions =====
576            supports_window_exclude: false,
577            unnest_with_ordinality: true,
578            lowercase_window_frame_keywords: false,
579            normalize_window_frame_between: false,
580
581            // ===== Array =====
582            array_concat_is_var_len: true,
583            array_size_dim_required: None,
584            can_implement_array_any: false,
585            array_size_name: "ARRAY_LENGTH",
586
587            // ===== BETWEEN =====
588            supports_between_flags: false,
589
590            // ===== Boolean =====
591            is_bool_allowed: true,
592            ensure_bools: false,
593
594            // ===== EXTRACT =====
595            extract_allows_quotes: true,
596            normalize_extract_date_parts: false,
597
598            // ===== Other features =====
599            try_supported: true,
600            supports_uescape: true,
601            supports_to_number: true,
602            supports_single_arg_concat: true,
603            last_day_supports_date_part: true,
604            supports_exploding_projections: true,
605            supports_unix_seconds: false,
606            supports_like_quantifiers: true,
607            supports_decode_case: true,
608            set_op_modifiers: true,
609            update_statement_supports_from: true,
610
611            // ===== COLLATE =====
612            collate_is_func: false,
613
614            // ===== INSERT =====
615            duplicate_key_update_with_set: true,
616            insert_overwrite: " OVERWRITE TABLE",
617
618            // ===== RETURNING =====
619            returning_end: true,
620
621            // ===== MERGE =====
622            matched_by_source: true,
623
624            // ===== CREATE FUNCTION =====
625            create_function_return_as: true,
626            parameter_default_equals: false,
627
628            // ===== COMPUTED COLUMN =====
629            computed_column_with_type: true,
630
631            // ===== UNPIVOT =====
632            unpivot_aliases_are_identifiers: true,
633
634            // ===== STAR =====
635            star_except: "EXCEPT",
636
637            // ===== HEX =====
638            hex_func: "HEX",
639
640            // ===== WITH =====
641            with_properties_prefix: "WITH",
642
643            // ===== PAD =====
644            pad_fill_pattern_is_required: false,
645
646            // ===== INDEX =====
647            index_on: "ON",
648
649            // ===== GROUPING =====
650            groupings_sep: ",",
651
652            // ===== STRUCT =====
653            struct_delimiter: ("<", ">"),
654            struct_curly_brace_notation: false,
655            array_bracket_only: false,
656            struct_field_sep: " ",
657
658            // ===== EXCEPT/INTERSECT =====
659            except_intersect_support_all_clause: true,
660
661            // ===== PARAMETERS/PLACEHOLDERS =====
662            parameter_token: "@",
663            named_placeholder_token: ":",
664
665            // ===== DATA TYPES =====
666            data_type_specifiers_allowed: false,
667
668            // ===== COMMENT =====
669            schema_comment_with_eq: true,
670        }
671    }
672}
673
674/// SQL reserved keywords that require quoting when used as identifiers
675/// Based on ANSI SQL standards and common dialect-specific reserved words
676mod reserved_keywords {
677    use std::collections::HashSet;
678    use std::sync::LazyLock;
679
680    /// Standard SQL reserved keywords (ANSI SQL:2016)
681    pub static SQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
682        [
683            "all",
684            "alter",
685            "and",
686            "any",
687            "array",
688            "as",
689            "asc",
690            "at",
691            "authorization",
692            "begin",
693            "between",
694            "both",
695            "by",
696            "case",
697            "cast",
698            "check",
699            "collate",
700            "column",
701            "commit",
702            "constraint",
703            "create",
704            "cross",
705            "cube",
706            "current",
707            "current_date",
708            "current_time",
709            "current_timestamp",
710            "current_user",
711            "default",
712            "delete",
713            "desc",
714            "distinct",
715            "drop",
716            "else",
717            "end",
718            "escape",
719            "except",
720            "execute",
721            "exists",
722            "external",
723            "false",
724            "fetch",
725            "filter",
726            "for",
727            "foreign",
728            "from",
729            "full",
730            "function",
731            "grant",
732            "group",
733            "grouping",
734            "having",
735            "if",
736            "in",
737            "index",
738            "inner",
739            "insert",
740            "intersect",
741            "interval",
742            "into",
743            "is",
744            "join",
745            "key",
746            "leading",
747            "left",
748            "like",
749            "limit",
750            "local",
751            "localtime",
752            "localtimestamp",
753            "match",
754            "merge",
755            "natural",
756            "no",
757            "not",
758            "null",
759            "of",
760            "offset",
761            "on",
762            "only",
763            "or",
764            "order",
765            "outer",
766            "over",
767            "partition",
768            "primary",
769            "procedure",
770            "range",
771            "references",
772            "right",
773            "rollback",
774            "rollup",
775            "row",
776            "rows",
777            "select",
778            "session_user",
779            "set",
780            "some",
781            "table",
782            "tablesample",
783            "then",
784            "to",
785            "trailing",
786            "true",
787            "truncate",
788            "union",
789            "unique",
790            "unknown",
791            "update",
792            "user",
793            "using",
794            "values",
795            "view",
796            "when",
797            "where",
798            "window",
799            "with",
800        ]
801        .into_iter()
802        .collect()
803    });
804
805    /// BigQuery-specific reserved keywords
806    /// Based on: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords
807    pub static BIGQUERY_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
808        let mut set = SQL_RESERVED.clone();
809        set.extend([
810            "assert_rows_modified",
811            "at",
812            "contains",
813            "cube",
814            "current",
815            "define",
816            "enum",
817            "escape",
818            "exclude",
819            "following",
820            "for",
821            "groups",
822            "hash",
823            "ignore",
824            "lateral",
825            "lookup",
826            "new",
827            "no",
828            "nulls",
829            "of",
830            "over",
831            "preceding",
832            "proto",
833            "qualify",
834            "recursive",
835            "respect",
836            "struct",
837            "tablesample",
838            "treat",
839            "unbounded",
840            "unnest",
841            "window",
842            "within",
843        ]);
844        // BigQuery does NOT reserve these keywords - they can be used as identifiers unquoted
845        set.remove("grant");
846        set.remove("key");
847        set.remove("index");
848        set.remove("values");
849        set.remove("table");
850        set
851    });
852
853    /// MySQL-specific reserved keywords
854    pub static MYSQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
855        let mut set = SQL_RESERVED.clone();
856        set.extend([
857            "accessible",
858            "add",
859            "analyze",
860            "asensitive",
861            "before",
862            "bigint",
863            "binary",
864            "blob",
865            "call",
866            "cascade",
867            "change",
868            "char",
869            "character",
870            "condition",
871            "continue",
872            "convert",
873            "current_date",
874            "current_time",
875            "current_timestamp",
876            "current_user",
877            "cursor",
878            "database",
879            "databases",
880            "day_hour",
881            "day_microsecond",
882            "day_minute",
883            "day_second",
884            "dec",
885            "decimal",
886            "declare",
887            "delayed",
888            "describe",
889            "deterministic",
890            "distinctrow",
891            "div",
892            "double",
893            "dual",
894            "each",
895            "elseif",
896            "enclosed",
897            "escaped",
898            "exit",
899            "explain",
900            "float",
901            "float4",
902            "float8",
903            "force",
904            "get",
905            "high_priority",
906            "hour_microsecond",
907            "hour_minute",
908            "hour_second",
909            "ignore",
910            "infile",
911            "inout",
912            "insensitive",
913            "int",
914            "int1",
915            "int2",
916            "int3",
917            "int4",
918            "int8",
919            "integer",
920            "iterate",
921            "keys",
922            "kill",
923            "leave",
924            "linear",
925            "lines",
926            "load",
927            "lock",
928            "long",
929            "longblob",
930            "longtext",
931            "loop",
932            "low_priority",
933            "master_ssl_verify_server_cert",
934            "maxvalue",
935            "mediumblob",
936            "mediumint",
937            "mediumtext",
938            "middleint",
939            "minute_microsecond",
940            "minute_second",
941            "mod",
942            "modifies",
943            "no_write_to_binlog",
944            "numeric",
945            "optimize",
946            "option",
947            "optionally",
948            "out",
949            "outfile",
950            "precision",
951            "purge",
952            "read",
953            "reads",
954            "real",
955            "regexp",
956            "release",
957            "rename",
958            "repeat",
959            "replace",
960            "require",
961            "resignal",
962            "restrict",
963            "return",
964            "revoke",
965            "rlike",
966            "schema",
967            "schemas",
968            "second_microsecond",
969            "sensitive",
970            "separator",
971            "show",
972            "signal",
973            "smallint",
974            "spatial",
975            "specific",
976            "sql",
977            "sql_big_result",
978            "sql_calc_found_rows",
979            "sql_small_result",
980            "sqlexception",
981            "sqlstate",
982            "sqlwarning",
983            "ssl",
984            "starting",
985            "straight_join",
986            "terminated",
987            "text",
988            "tinyblob",
989            "tinyint",
990            "tinytext",
991            "trigger",
992            "undo",
993            "unlock",
994            "unsigned",
995            "usage",
996            "utc_date",
997            "utc_time",
998            "utc_timestamp",
999            "varbinary",
1000            "varchar",
1001            "varcharacter",
1002            "varying",
1003            "while",
1004            "write",
1005            "xor",
1006            "year_month",
1007            "zerofill",
1008        ]);
1009        set.remove("table");
1010        set
1011    });
1012
1013    /// Doris-specific reserved keywords
1014    /// Extends MySQL reserved with additional Doris-specific words
1015    pub static DORIS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1016        let mut set = MYSQL_RESERVED.clone();
1017        set.extend([
1018            "aggregate",
1019            "anti",
1020            "array",
1021            "backend",
1022            "backup",
1023            "begin",
1024            "bitmap",
1025            "boolean",
1026            "broker",
1027            "buckets",
1028            "cached",
1029            "cancel",
1030            "cast",
1031            "catalog",
1032            "charset",
1033            "cluster",
1034            "collation",
1035            "columns",
1036            "comment",
1037            "commit",
1038            "config",
1039            "connection",
1040            "count",
1041            "current",
1042            "data",
1043            "date",
1044            "datetime",
1045            "day",
1046            "deferred",
1047            "distributed",
1048            "dynamic",
1049            "enable",
1050            "end",
1051            "events",
1052            "export",
1053            "external",
1054            "fields",
1055            "first",
1056            "follower",
1057            "format",
1058            "free",
1059            "frontend",
1060            "full",
1061            "functions",
1062            "global",
1063            "grants",
1064            "hash",
1065            "help",
1066            "hour",
1067            "install",
1068            "intermediate",
1069            "json",
1070            "label",
1071            "last",
1072            "less",
1073            "level",
1074            "link",
1075            "local",
1076            "location",
1077            "max",
1078            "merge",
1079            "min",
1080            "minute",
1081            "modify",
1082            "month",
1083            "name",
1084            "names",
1085            "negative",
1086            "nulls",
1087            "observer",
1088            "offset",
1089            "only",
1090            "open",
1091            "overwrite",
1092            "password",
1093            "path",
1094            "plan",
1095            "plugin",
1096            "plugins",
1097            "policy",
1098            "process",
1099            "properties",
1100            "property",
1101            "query",
1102            "quota",
1103            "recover",
1104            "refresh",
1105            "repair",
1106            "replica",
1107            "repository",
1108            "resource",
1109            "restore",
1110            "resume",
1111            "role",
1112            "roles",
1113            "rollback",
1114            "rollup",
1115            "routine",
1116            "sample",
1117            "second",
1118            "semi",
1119            "session",
1120            "signed",
1121            "snapshot",
1122            "start",
1123            "stats",
1124            "status",
1125            "stop",
1126            "stream",
1127            "string",
1128            "sum",
1129            "tables",
1130            "tablet",
1131            "temporary",
1132            "text",
1133            "timestamp",
1134            "transaction",
1135            "trash",
1136            "trim",
1137            "truncate",
1138            "type",
1139            "user",
1140            "value",
1141            "variables",
1142            "verbose",
1143            "version",
1144            "view",
1145            "warnings",
1146            "week",
1147            "work",
1148            "year",
1149        ]);
1150        set
1151    });
1152
1153    /// PostgreSQL-specific reserved keywords
1154    pub static POSTGRES_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1155        let mut set = SQL_RESERVED.clone();
1156        set.extend([
1157            "analyse",
1158            "analyze",
1159            "asymmetric",
1160            "binary",
1161            "collation",
1162            "concurrently",
1163            "current_catalog",
1164            "current_role",
1165            "current_schema",
1166            "deferrable",
1167            "do",
1168            "freeze",
1169            "ilike",
1170            "initially",
1171            "isnull",
1172            "lateral",
1173            "notnull",
1174            "placing",
1175            "returning",
1176            "similar",
1177            "symmetric",
1178            "variadic",
1179            "verbose",
1180        ]);
1181        // PostgreSQL doesn't require quoting for these keywords
1182        set.remove("default");
1183        set.remove("interval");
1184        set.remove("match");
1185        set.remove("offset");
1186        set.remove("table");
1187        set
1188    });
1189
1190    /// Redshift-specific reserved keywords
1191    /// Based on: https://docs.aws.amazon.com/redshift/latest/dg/r_pg_keywords.html
1192    /// Note: `index` is NOT reserved in Redshift (unlike PostgreSQL)
1193    pub static REDSHIFT_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1194        [
1195            "aes128",
1196            "aes256",
1197            "all",
1198            "allowoverwrite",
1199            "analyse",
1200            "analyze",
1201            "and",
1202            "any",
1203            "array",
1204            "as",
1205            "asc",
1206            "authorization",
1207            "az64",
1208            "backup",
1209            "between",
1210            "binary",
1211            "blanksasnull",
1212            "both",
1213            "bytedict",
1214            "bzip2",
1215            "case",
1216            "cast",
1217            "check",
1218            "collate",
1219            "column",
1220            "constraint",
1221            "create",
1222            "credentials",
1223            "cross",
1224            "current_date",
1225            "current_time",
1226            "current_timestamp",
1227            "current_user",
1228            "current_user_id",
1229            "default",
1230            "deferrable",
1231            "deflate",
1232            "defrag",
1233            "delta",
1234            "delta32k",
1235            "desc",
1236            "disable",
1237            "distinct",
1238            "do",
1239            "else",
1240            "emptyasnull",
1241            "enable",
1242            "encode",
1243            "encrypt",
1244            "encryption",
1245            "end",
1246            "except",
1247            "explicit",
1248            "false",
1249            "for",
1250            "foreign",
1251            "freeze",
1252            "from",
1253            "full",
1254            "globaldict256",
1255            "globaldict64k",
1256            "grant",
1257            "group",
1258            "gzip",
1259            "having",
1260            "identity",
1261            "ignore",
1262            "ilike",
1263            "in",
1264            "initially",
1265            "inner",
1266            "intersect",
1267            "interval",
1268            "into",
1269            "is",
1270            "isnull",
1271            "join",
1272            "leading",
1273            "left",
1274            "like",
1275            "limit",
1276            "localtime",
1277            "localtimestamp",
1278            "lun",
1279            "luns",
1280            "lzo",
1281            "lzop",
1282            "minus",
1283            "mostly16",
1284            "mostly32",
1285            "mostly8",
1286            "natural",
1287            "new",
1288            "not",
1289            "notnull",
1290            "null",
1291            "nulls",
1292            "off",
1293            "offline",
1294            "offset",
1295            "oid",
1296            "old",
1297            "on",
1298            "only",
1299            "open",
1300            "or",
1301            "order",
1302            "outer",
1303            "overlaps",
1304            "parallel",
1305            "partition",
1306            "percent",
1307            "permissions",
1308            "pivot",
1309            "placing",
1310            "primary",
1311            "raw",
1312            "readratio",
1313            "recover",
1314            "references",
1315            "rejectlog",
1316            "resort",
1317            "respect",
1318            "restore",
1319            "right",
1320            "select",
1321            "session_user",
1322            "similar",
1323            "snapshot",
1324            "some",
1325            "sysdate",
1326            "system",
1327            "table",
1328            "tag",
1329            "tdes",
1330            "text255",
1331            "text32k",
1332            "then",
1333            "timestamp",
1334            "to",
1335            "top",
1336            "trailing",
1337            "true",
1338            "truncatecolumns",
1339            "type",
1340            "union",
1341            "unique",
1342            "unnest",
1343            "unpivot",
1344            "user",
1345            "using",
1346            "verbose",
1347            "wallet",
1348            "when",
1349            "where",
1350            "with",
1351            "without",
1352        ]
1353        .into_iter()
1354        .collect()
1355    });
1356
1357    /// DuckDB-specific reserved keywords
1358    pub static DUCKDB_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1359        let mut set = POSTGRES_RESERVED.clone();
1360        set.extend([
1361            "anti",
1362            "asof",
1363            "columns",
1364            "describe",
1365            "groups",
1366            "macro",
1367            "pivot",
1368            "pivot_longer",
1369            "pivot_wider",
1370            "qualify",
1371            "replace",
1372            "respect",
1373            "semi",
1374            "show",
1375            "table",
1376            "unpivot",
1377        ]);
1378        set.remove("at");
1379        set.remove("key");
1380        set.remove("row");
1381        set
1382    });
1383
1384    /// Presto/Trino/Athena-specific reserved keywords
1385    pub static PRESTO_TRINO_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1386        let mut set = SQL_RESERVED.clone();
1387        set.extend([
1388            "alter",
1389            "and",
1390            "as",
1391            "between",
1392            "by",
1393            "case",
1394            "cast",
1395            "constraint",
1396            "create",
1397            "cross",
1398            "cube",
1399            "current_catalog",
1400            "current_date",
1401            "current_path",
1402            "current_role",
1403            "current_schema",
1404            "current_time",
1405            "current_timestamp",
1406            "current_user",
1407            "deallocate",
1408            "delete",
1409            "describe",
1410            "distinct",
1411            "drop",
1412            "else",
1413            "end",
1414            "escape",
1415            "except",
1416            "execute",
1417            "exists",
1418            "extract",
1419            "false",
1420            "for",
1421            "from",
1422            "full",
1423            "group",
1424            "grouping",
1425            "having",
1426            "in",
1427            "inner",
1428            "insert",
1429            "intersect",
1430            "into",
1431            "is",
1432            "join",
1433            "json_array",
1434            "json_exists",
1435            "json_object",
1436            "json_query",
1437            "json_table",
1438            "json_value",
1439            "left",
1440            "like",
1441            "listagg",
1442            "localtime",
1443            "localtimestamp",
1444            "natural",
1445            "normalize",
1446            "not",
1447            "null",
1448            "on",
1449            "or",
1450            "order",
1451            "outer",
1452            "prepare",
1453            "recursive",
1454            "right",
1455            "rollup",
1456            "select",
1457            "skip",
1458            "table",
1459            "then",
1460            "trim",
1461            "true",
1462            "uescape",
1463            "union",
1464            "unnest",
1465            "using",
1466            "values",
1467            "when",
1468            "where",
1469            "with",
1470        ]);
1471        // Match sqlglot behavior for Presto/Trino: KEY does not require identifier quoting.
1472        set.remove("key");
1473        set
1474    });
1475
1476    /// StarRocks-specific reserved keywords
1477    /// Based on: https://docs.starrocks.io/docs/sql-reference/sql-statements/keywords/#reserved-keywords
1478    pub static STARROCKS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1479        [
1480            "add",
1481            "all",
1482            "alter",
1483            "analyze",
1484            "and",
1485            "array",
1486            "as",
1487            "asc",
1488            "between",
1489            "bigint",
1490            "bitmap",
1491            "both",
1492            "by",
1493            "case",
1494            "char",
1495            "character",
1496            "check",
1497            "collate",
1498            "column",
1499            "compaction",
1500            "convert",
1501            "create",
1502            "cross",
1503            "cube",
1504            "current_date",
1505            "current_role",
1506            "current_time",
1507            "current_timestamp",
1508            "current_user",
1509            "database",
1510            "databases",
1511            "decimal",
1512            "decimalv2",
1513            "decimal32",
1514            "decimal64",
1515            "decimal128",
1516            "default",
1517            "deferred",
1518            "delete",
1519            "dense_rank",
1520            "desc",
1521            "describe",
1522            "distinct",
1523            "double",
1524            "drop",
1525            "dual",
1526            "else",
1527            "except",
1528            "exists",
1529            "explain",
1530            "false",
1531            "first_value",
1532            "float",
1533            "for",
1534            "force",
1535            "from",
1536            "full",
1537            "function",
1538            "grant",
1539            "group",
1540            "grouping",
1541            "grouping_id",
1542            "groups",
1543            "having",
1544            "hll",
1545            "host",
1546            "if",
1547            "ignore",
1548            "immediate",
1549            "in",
1550            "index",
1551            "infile",
1552            "inner",
1553            "insert",
1554            "int",
1555            "integer",
1556            "intersect",
1557            "into",
1558            "is",
1559            "join",
1560            "json",
1561            "key",
1562            "keys",
1563            "kill",
1564            "lag",
1565            "largeint",
1566            "last_value",
1567            "lateral",
1568            "lead",
1569            "left",
1570            "like",
1571            "limit",
1572            "load",
1573            "localtime",
1574            "localtimestamp",
1575            "maxvalue",
1576            "minus",
1577            "mod",
1578            "not",
1579            "ntile",
1580            "null",
1581            "on",
1582            "or",
1583            "order",
1584            "outer",
1585            "outfile",
1586            "over",
1587            "partition",
1588            "percentile",
1589            "primary",
1590            "procedure",
1591            "qualify",
1592            "range",
1593            "rank",
1594            "read",
1595            "regexp",
1596            "release",
1597            "rename",
1598            "replace",
1599            "revoke",
1600            "right",
1601            "rlike",
1602            "row",
1603            "row_number",
1604            "rows",
1605            "schema",
1606            "schemas",
1607            "select",
1608            "set",
1609            "set_var",
1610            "show",
1611            "smallint",
1612            "system",
1613            "table",
1614            "terminated",
1615            "text",
1616            "then",
1617            "tinyint",
1618            "to",
1619            "true",
1620            "union",
1621            "unique",
1622            "unsigned",
1623            "update",
1624            "use",
1625            "using",
1626            "values",
1627            "varchar",
1628            "when",
1629            "where",
1630            "with",
1631        ]
1632        .into_iter()
1633        .collect()
1634    });
1635
1636    /// SingleStore-specific reserved keywords
1637    /// Based on: https://docs.singlestore.com/cloud/reference/sql-reference/restricted-keywords/list-of-restricted-keywords/
1638    pub static SINGLESTORE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1639        let mut set = MYSQL_RESERVED.clone();
1640        set.extend([
1641            // Additional SingleStore reserved keywords from Python sqlglot
1642            // NOTE: "all" is excluded because ORDER BY ALL needs ALL unquoted
1643            "abs",
1644            "account",
1645            "acos",
1646            "adddate",
1647            "addtime",
1648            "admin",
1649            "aes_decrypt",
1650            "aes_encrypt",
1651            "aggregate",
1652            "aggregates",
1653            "aggregator",
1654            "anti_join",
1655            "any_value",
1656            "approx_count_distinct",
1657            "approx_percentile",
1658            "arrange",
1659            "arrangement",
1660            "asin",
1661            "atan",
1662            "atan2",
1663            "attach",
1664            "autostats",
1665            "avro",
1666            "background",
1667            "backup",
1668            "batch",
1669            "batches",
1670            "boot_strapping",
1671            "ceil",
1672            "ceiling",
1673            "coercibility",
1674            "columnar",
1675            "columnstore",
1676            "compile",
1677            "concurrent",
1678            "connection_id",
1679            "cos",
1680            "cot",
1681            "current_security_groups",
1682            "current_security_roles",
1683            "dayname",
1684            "dayofmonth",
1685            "dayofweek",
1686            "dayofyear",
1687            "degrees",
1688            "dot_product",
1689            "dump",
1690            "durability",
1691            "earliest",
1692            "echo",
1693            "election",
1694            "euclidean_distance",
1695            "exp",
1696            "extractor",
1697            "extractors",
1698            "floor",
1699            "foreground",
1700            "found_rows",
1701            "from_base64",
1702            "from_days",
1703            "from_unixtime",
1704            "fs",
1705            "fulltext",
1706            "gc",
1707            "gcs",
1708            "geography",
1709            "geography_area",
1710            "geography_contains",
1711            "geography_distance",
1712            "geography_intersects",
1713            "geography_latitude",
1714            "geography_length",
1715            "geography_longitude",
1716            "geographypoint",
1717            "geography_point",
1718            "geography_within_distance",
1719            "geometry",
1720            "geometry_area",
1721            "geometry_contains",
1722            "geometry_distance",
1723            "geometry_filter",
1724            "geometry_intersects",
1725            "geometry_length",
1726            "geometrypoint",
1727            "geometry_point",
1728            "geometry_within_distance",
1729            "geometry_x",
1730            "geometry_y",
1731            "greatest",
1732            "groups",
1733            "group_concat",
1734            "gzip",
1735            "hdfs",
1736            "hex",
1737            "highlight",
1738            "ifnull",
1739            "ilike",
1740            "inet_aton",
1741            "inet_ntoa",
1742            "inet6_aton",
1743            "inet6_ntoa",
1744            "initcap",
1745            "instr",
1746            "interpreter_mode",
1747            "isnull",
1748            "json",
1749            "json_agg",
1750            "json_array_contains_double",
1751            "json_array_contains_json",
1752            "json_array_contains_string",
1753            "json_delete_key",
1754            "json_extract_double",
1755            "json_extract_json",
1756            "json_extract_string",
1757            "json_extract_bigint",
1758            "json_get_type",
1759            "json_length",
1760            "json_set_double",
1761            "json_set_json",
1762            "json_set_string",
1763            "kafka",
1764            "lag",
1765            "last_day",
1766            "last_insert_id",
1767            "latest",
1768            "lcase",
1769            "lead",
1770            "leaf",
1771            "least",
1772            "leaves",
1773            "length",
1774            "license",
1775            "links",
1776            "llvm",
1777            "ln",
1778            "load",
1779            "locate",
1780            "log",
1781            "log10",
1782            "log2",
1783            "lpad",
1784            "lz4",
1785            "management",
1786            "match",
1787            "mbc",
1788            "md5",
1789            "median",
1790            "memsql",
1791            "memsql_deserialize",
1792            "memsql_serialize",
1793            "metadata",
1794            "microsecond",
1795            "minute",
1796            "model",
1797            "monthname",
1798            "months_between",
1799            "mpl",
1800            "namespace",
1801            "node",
1802            "noparam",
1803            "now",
1804            "nth_value",
1805            "ntile",
1806            "nullcols",
1807            "nullif",
1808            "object",
1809            "octet_length",
1810            "offsets",
1811            "online",
1812            "optimizer",
1813            "orphan",
1814            "parquet",
1815            "partitions",
1816            "pause",
1817            "percentile_cont",
1818            "percentile_disc",
1819            "periodic",
1820            "persisted",
1821            "pi",
1822            "pipeline",
1823            "pipelines",
1824            "plancache",
1825            "plugins",
1826            "pool",
1827            "pools",
1828            "pow",
1829            "power",
1830            "process",
1831            "processlist",
1832            "profile",
1833            "profiles",
1834            "quarter",
1835            "queries",
1836            "query",
1837            "radians",
1838            "rand",
1839            "record",
1840            "reduce",
1841            "redundancy",
1842            "regexp_match",
1843            "regexp_substr",
1844            "remote",
1845            "replication",
1846            "resource",
1847            "resource_pool",
1848            "restore",
1849            "retry",
1850            "role",
1851            "roles",
1852            "round",
1853            "rpad",
1854            "rtrim",
1855            "running",
1856            "s3",
1857            "scalar",
1858            "sec_to_time",
1859            "second",
1860            "security_lists_intersect",
1861            "semi_join",
1862            "sha",
1863            "sha1",
1864            "sha2",
1865            "shard",
1866            "sharded",
1867            "sharded_id",
1868            "sigmoid",
1869            "sign",
1870            "sin",
1871            "skip",
1872            "sleep",
1873            "snapshot",
1874            "soname",
1875            "sparse",
1876            "spatial_check_index",
1877            "split",
1878            "sqrt",
1879            "standalone",
1880            "std",
1881            "stddev",
1882            "stddev_pop",
1883            "stddev_samp",
1884            "stop",
1885            "str_to_date",
1886            "subdate",
1887            "substr",
1888            "substring_index",
1889            "success",
1890            "synchronize",
1891            "table_checksum",
1892            "tan",
1893            "task",
1894            "timediff",
1895            "time_bucket",
1896            "time_format",
1897            "time_to_sec",
1898            "timestampadd",
1899            "timestampdiff",
1900            "to_base64",
1901            "to_char",
1902            "to_date",
1903            "to_days",
1904            "to_json",
1905            "to_number",
1906            "to_seconds",
1907            "to_timestamp",
1908            "tracelogs",
1909            "transform",
1910            "trim",
1911            "trunc",
1912            "truncate",
1913            "ucase",
1914            "unhex",
1915            "unix_timestamp",
1916            "utc_date",
1917            "utc_time",
1918            "utc_timestamp",
1919            "vacuum",
1920            "variance",
1921            "var_pop",
1922            "var_samp",
1923            "vector_sub",
1924            "voting",
1925            "week",
1926            "weekday",
1927            "weekofyear",
1928            "workload",
1929            "year",
1930        ]);
1931        // Remove "all" because ORDER BY ALL needs ALL unquoted
1932        set.remove("all");
1933        set
1934    });
1935
1936    /// SQLite-specific reserved keywords
1937    /// SQLite has a very minimal set of reserved keywords - most things can be used as identifiers unquoted
1938    /// Reference: https://www.sqlite.org/lang_keywords.html
1939    pub static SQLITE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1940        // SQLite only truly reserves these - everything else can be used as identifier unquoted
1941        [
1942            "abort",
1943            "action",
1944            "add",
1945            "after",
1946            "all",
1947            "alter",
1948            "always",
1949            "analyze",
1950            "and",
1951            "as",
1952            "asc",
1953            "attach",
1954            "autoincrement",
1955            "before",
1956            "begin",
1957            "between",
1958            "by",
1959            "cascade",
1960            "case",
1961            "cast",
1962            "check",
1963            "collate",
1964            "column",
1965            "commit",
1966            "conflict",
1967            "constraint",
1968            "create",
1969            "cross",
1970            "current",
1971            "current_date",
1972            "current_time",
1973            "current_timestamp",
1974            "database",
1975            "default",
1976            "deferrable",
1977            "deferred",
1978            "delete",
1979            "desc",
1980            "detach",
1981            "distinct",
1982            "do",
1983            "drop",
1984            "each",
1985            "else",
1986            "end",
1987            "escape",
1988            "except",
1989            "exclude",
1990            "exclusive",
1991            "exists",
1992            "explain",
1993            "fail",
1994            "filter",
1995            "first",
1996            "following",
1997            "for",
1998            "foreign",
1999            "from",
2000            "full",
2001            "generated",
2002            "glob",
2003            "group",
2004            "groups",
2005            "having",
2006            "if",
2007            "ignore",
2008            "immediate",
2009            "in",
2010            "index",
2011            "indexed",
2012            "initially",
2013            "inner",
2014            "insert",
2015            "instead",
2016            "intersect",
2017            "into",
2018            "is",
2019            "isnull",
2020            "join",
2021            "key",
2022            "last",
2023            "left",
2024            "like",
2025            "limit",
2026            "natural",
2027            "no",
2028            "not",
2029            "nothing",
2030            "notnull",
2031            "null",
2032            "nulls",
2033            "of",
2034            "offset",
2035            "on",
2036            "or",
2037            "order",
2038            "others",
2039            "outer",
2040            "partition",
2041            "plan",
2042            "pragma",
2043            "preceding",
2044            "primary",
2045            "query",
2046            "raise",
2047            "range",
2048            "recursive",
2049            "references",
2050            "regexp",
2051            "reindex",
2052            "release",
2053            "rename",
2054            "replace",
2055            "restrict",
2056            "returning",
2057            "right",
2058            "rollback",
2059            "row",
2060            "rows",
2061            "savepoint",
2062            "select",
2063            "set",
2064            "table",
2065            "temp",
2066            "temporary",
2067            "then",
2068            "ties",
2069            "to",
2070            "transaction",
2071            "trigger",
2072            "unbounded",
2073            "union",
2074            "unique",
2075            "update",
2076            "using",
2077            "vacuum",
2078            "values",
2079            "view",
2080            "virtual",
2081            "when",
2082            "where",
2083            "window",
2084            "with",
2085            "without",
2086        ]
2087        .into_iter()
2088        .collect()
2089    });
2090}
2091
2092impl Generator {
2093    /// Create a new generator with the default configuration.
2094    ///
2095    /// Equivalent to `Generator::with_config(GeneratorConfig::default())`.
2096    /// Uses uppercase keywords, double-quote identifier quoting, no pretty-printing,
2097    /// and no dialect-specific transformations.
2098    pub fn new() -> Self {
2099        Self::with_config(GeneratorConfig::default())
2100    }
2101
2102    /// Create a generator with a custom [`GeneratorConfig`].
2103    ///
2104    /// Use this when you need dialect-specific output, pretty-printing, or other
2105    /// non-default settings.
2106    pub fn with_config(config: GeneratorConfig) -> Self {
2107        Self {
2108            config,
2109            output: String::new(),
2110            indent_level: 0,
2111            athena_hive_context: false,
2112            sqlite_inline_pk_columns: std::collections::HashSet::new(),
2113            merge_strip_qualifiers: Vec::new(),
2114            clickhouse_nullable_depth: 0,
2115        }
2116    }
2117
2118    /// Add column aliases to a query expression for TSQL SELECT INTO.
2119    /// This ensures that unaliased columns get explicit aliases (e.g., `a` -> `a AS a`).
2120    /// Recursively processes all SELECT expressions in the query tree.
2121    fn add_column_aliases_to_query(expr: Expression) -> Expression {
2122        match expr {
2123            Expression::Select(mut select) => {
2124                // Add aliases to all select expressions that don't already have them
2125                select.expressions = select
2126                    .expressions
2127                    .into_iter()
2128                    .map(|e| Self::add_alias_to_expression(e))
2129                    .collect();
2130
2131                // Recursively process subqueries in FROM clause
2132                if let Some(ref mut from) = select.from {
2133                    from.expressions = from
2134                        .expressions
2135                        .iter()
2136                        .cloned()
2137                        .map(|e| Self::add_column_aliases_to_query(e))
2138                        .collect();
2139                }
2140
2141                Expression::Select(select)
2142            }
2143            Expression::Subquery(mut sq) => {
2144                sq.this = Self::add_column_aliases_to_query(sq.this);
2145                Expression::Subquery(sq)
2146            }
2147            Expression::Paren(mut p) => {
2148                p.this = Self::add_column_aliases_to_query(p.this);
2149                Expression::Paren(p)
2150            }
2151            // For other expressions (Union, Intersect, etc.), pass through
2152            other => other,
2153        }
2154    }
2155
2156    /// Add an alias to a single select expression if it doesn't already have one.
2157    /// Returns the expression with alias (e.g., `a` -> `a AS a`).
2158    fn add_alias_to_expression(expr: Expression) -> Expression {
2159        use crate::expressions::Alias;
2160
2161        match &expr {
2162            // Already aliased - just return it
2163            Expression::Alias(_) => expr,
2164
2165            // Column reference: add alias from column name
2166            Expression::Column(col) => Expression::Alias(Box::new(Alias {
2167                this: expr.clone(),
2168                alias: col.name.clone(),
2169                column_aliases: Vec::new(),
2170                pre_alias_comments: Vec::new(),
2171                trailing_comments: Vec::new(),
2172            })),
2173
2174            // Identifier: add alias from identifier name
2175            Expression::Identifier(ident) => Expression::Alias(Box::new(Alias {
2176                this: expr.clone(),
2177                alias: ident.clone(),
2178                column_aliases: Vec::new(),
2179                pre_alias_comments: Vec::new(),
2180                trailing_comments: Vec::new(),
2181            })),
2182
2183            // Subquery: recursively process and add alias if inner returns a named column
2184            Expression::Subquery(sq) => {
2185                let processed = Self::add_column_aliases_to_query(Expression::Subquery(sq.clone()));
2186                // Subqueries that are already aliased keep their alias
2187                if sq.alias.is_some() {
2188                    processed
2189                } else {
2190                    // If there's no alias, keep it as-is (let TSQL handle it)
2191                    processed
2192                }
2193            }
2194
2195            // Star expressions (*) - don't alias
2196            Expression::Star(_) => expr,
2197
2198            // For other expressions, don't add an alias
2199            // (function calls, literals, etc. would need explicit aliases anyway)
2200            _ => expr,
2201        }
2202    }
2203
2204    /// Try to evaluate a constant arithmetic expression to a number literal.
2205    /// Returns the evaluated result if the expression is a constant arithmetic expression,
2206    /// otherwise returns the original expression.
2207    fn try_evaluate_constant(expr: &Expression) -> Option<i64> {
2208        match expr {
2209            Expression::Literal(Literal::Number(n)) => n.parse::<i64>().ok(),
2210            Expression::Add(op) => {
2211                let left = Self::try_evaluate_constant(&op.left)?;
2212                let right = Self::try_evaluate_constant(&op.right)?;
2213                Some(left + right)
2214            }
2215            Expression::Sub(op) => {
2216                let left = Self::try_evaluate_constant(&op.left)?;
2217                let right = Self::try_evaluate_constant(&op.right)?;
2218                Some(left - right)
2219            }
2220            Expression::Mul(op) => {
2221                let left = Self::try_evaluate_constant(&op.left)?;
2222                let right = Self::try_evaluate_constant(&op.right)?;
2223                Some(left * right)
2224            }
2225            Expression::Div(op) => {
2226                let left = Self::try_evaluate_constant(&op.left)?;
2227                let right = Self::try_evaluate_constant(&op.right)?;
2228                if right != 0 {
2229                    Some(left / right)
2230                } else {
2231                    None
2232                }
2233            }
2234            Expression::Paren(p) => Self::try_evaluate_constant(&p.this),
2235            _ => None,
2236        }
2237    }
2238
2239    /// Check if an identifier is a reserved keyword for the current dialect
2240    fn is_reserved_keyword(&self, name: &str) -> bool {
2241        use crate::dialects::DialectType;
2242        let lower = name.to_lowercase();
2243        let lower_ref = lower.as_str();
2244
2245        match self.config.dialect {
2246            Some(DialectType::BigQuery) => reserved_keywords::BIGQUERY_RESERVED.contains(lower_ref),
2247            Some(DialectType::MySQL) | Some(DialectType::TiDB) => {
2248                reserved_keywords::MYSQL_RESERVED.contains(lower_ref)
2249            }
2250            Some(DialectType::Doris) => reserved_keywords::DORIS_RESERVED.contains(lower_ref),
2251            Some(DialectType::SingleStore) => {
2252                reserved_keywords::SINGLESTORE_RESERVED.contains(lower_ref)
2253            }
2254            Some(DialectType::StarRocks) => {
2255                reserved_keywords::STARROCKS_RESERVED.contains(lower_ref)
2256            }
2257            Some(DialectType::PostgreSQL)
2258            | Some(DialectType::CockroachDB)
2259            | Some(DialectType::Materialize)
2260            | Some(DialectType::RisingWave) => {
2261                reserved_keywords::POSTGRES_RESERVED.contains(lower_ref)
2262            }
2263            Some(DialectType::Redshift) => reserved_keywords::REDSHIFT_RESERVED.contains(lower_ref),
2264            // Snowflake: Python sqlglot has RESERVED_KEYWORDS = set() for Snowflake,
2265            // meaning it never quotes identifiers based on reserved word status.
2266            Some(DialectType::Snowflake) => false,
2267            // ClickHouse: don't quote reserved keywords to preserve identity output
2268            Some(DialectType::ClickHouse) => false,
2269            Some(DialectType::DuckDB) => reserved_keywords::DUCKDB_RESERVED.contains(lower_ref),
2270            // Teradata: Python sqlglot has RESERVED_KEYWORDS = set() for Teradata
2271            Some(DialectType::Teradata) => false,
2272            // TSQL, Fabric, Oracle, Spark, Hive, Solr: Python sqlglot has no RESERVED_KEYWORDS for these dialects, so don't quote identifiers
2273            Some(DialectType::TSQL)
2274            | Some(DialectType::Fabric)
2275            | Some(DialectType::Oracle)
2276            | Some(DialectType::Spark)
2277            | Some(DialectType::Databricks)
2278            | Some(DialectType::Hive)
2279            | Some(DialectType::Solr) => false,
2280            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
2281                reserved_keywords::PRESTO_TRINO_RESERVED.contains(lower_ref)
2282            }
2283            Some(DialectType::SQLite) => reserved_keywords::SQLITE_RESERVED.contains(lower_ref),
2284            // For Generic dialect or None, don't add extra quoting to preserve identity
2285            Some(DialectType::Generic) | None => false,
2286            // For other dialects, use standard SQL reserved keywords
2287            _ => reserved_keywords::SQL_RESERVED.contains(lower_ref),
2288        }
2289    }
2290
2291    /// Normalize function name based on dialect settings
2292    fn normalize_func_name(&self, name: &str) -> String {
2293        match self.config.normalize_functions {
2294            NormalizeFunctions::Upper => name.to_uppercase(),
2295            NormalizeFunctions::Lower => name.to_lowercase(),
2296            NormalizeFunctions::None => name.to_string(),
2297        }
2298    }
2299
2300    /// Generate a SQL string from an AST expression.
2301    ///
2302    /// This is the primary generation method. It clears any previous internal state,
2303    /// walks the expression tree, and returns the resulting SQL text. The output
2304    /// respects the [`GeneratorConfig`] that was supplied at construction time.
2305    ///
2306    /// The generator can be reused across multiple calls; each call to `generate`
2307    /// resets the internal buffer.
2308    pub fn generate(&mut self, expr: &Expression) -> Result<String> {
2309        self.output.clear();
2310        self.generate_expression(expr)?;
2311        Ok(std::mem::take(&mut self.output))
2312    }
2313
2314    /// Convenience: generate SQL with the default configuration (no dialect, compact output).
2315    ///
2316    /// This is a static helper that creates a throwaway `Generator` internally.
2317    /// For repeated generation, prefer constructing a `Generator` once and calling
2318    /// [`generate`](Self::generate) on it.
2319    pub fn sql(expr: &Expression) -> Result<String> {
2320        let mut gen = Generator::new();
2321        gen.generate(expr)
2322    }
2323
2324    /// Convenience: generate SQL with pretty-printing enabled (indented, multi-line).
2325    ///
2326    /// Produces human-readable output with newlines and indentation. A trailing
2327    /// semicolon is appended automatically if not already present.
2328    pub fn pretty_sql(expr: &Expression) -> Result<String> {
2329        let config = GeneratorConfig {
2330            pretty: true,
2331            ..Default::default()
2332        };
2333        let mut gen = Generator::with_config(config);
2334        let mut sql = gen.generate(expr)?;
2335        // Add semicolon for pretty output
2336        if !sql.ends_with(';') {
2337            sql.push(';');
2338        }
2339        Ok(sql)
2340    }
2341
2342    fn generate_expression(&mut self, expr: &Expression) -> Result<()> {
2343        match expr {
2344            Expression::Select(select) => self.generate_select(select),
2345            Expression::Union(union) => self.generate_union(union),
2346            Expression::Intersect(intersect) => self.generate_intersect(intersect),
2347            Expression::Except(except) => self.generate_except(except),
2348            Expression::Insert(insert) => self.generate_insert(insert),
2349            Expression::Update(update) => self.generate_update(update),
2350            Expression::Delete(delete) => self.generate_delete(delete),
2351            Expression::Literal(lit) => self.generate_literal(lit),
2352            Expression::Boolean(b) => self.generate_boolean(b),
2353            Expression::Null(_) => {
2354                self.write_keyword("NULL");
2355                Ok(())
2356            }
2357            Expression::Identifier(id) => self.generate_identifier(id),
2358            Expression::Column(col) => self.generate_column(col),
2359            Expression::Pseudocolumn(pc) => self.generate_pseudocolumn(pc),
2360            Expression::Connect(c) => self.generate_connect_expr(c),
2361            Expression::Prior(p) => self.generate_prior(p),
2362            Expression::ConnectByRoot(cbr) => self.generate_connect_by_root(cbr),
2363            Expression::MatchRecognize(mr) => self.generate_match_recognize(mr),
2364            Expression::Table(table) => self.generate_table(table),
2365            Expression::StageReference(sr) => self.generate_stage_reference(sr),
2366            Expression::HistoricalData(hd) => self.generate_historical_data(hd),
2367            Expression::JoinedTable(jt) => self.generate_joined_table(jt),
2368            Expression::Star(star) => self.generate_star(star),
2369            Expression::BracedWildcard(expr) => self.generate_braced_wildcard(expr),
2370            Expression::Alias(alias) => self.generate_alias(alias),
2371            Expression::Cast(cast) => self.generate_cast(cast),
2372            Expression::Collation(coll) => self.generate_collation(coll),
2373            Expression::Case(case) => self.generate_case(case),
2374            Expression::Function(func) => self.generate_function(func),
2375            Expression::AggregateFunction(func) => self.generate_aggregate_function(func),
2376            Expression::WindowFunction(wf) => self.generate_window_function(wf),
2377            Expression::WithinGroup(wg) => self.generate_within_group(wg),
2378            Expression::Interval(interval) => self.generate_interval(interval),
2379
2380            // String functions
2381            Expression::ConcatWs(f) => self.generate_concat_ws(f),
2382            Expression::Substring(f) => self.generate_substring(f),
2383            Expression::Upper(f) => self.generate_unary_func("UPPER", f),
2384            Expression::Lower(f) => self.generate_unary_func("LOWER", f),
2385            Expression::Length(f) => self.generate_unary_func("LENGTH", f),
2386            Expression::Trim(f) => self.generate_trim(f),
2387            Expression::LTrim(f) => self.generate_simple_func("LTRIM", &f.this),
2388            Expression::RTrim(f) => self.generate_simple_func("RTRIM", &f.this),
2389            Expression::Replace(f) => self.generate_replace(f),
2390            Expression::Reverse(f) => self.generate_simple_func("REVERSE", &f.this),
2391            Expression::Left(f) => self.generate_left_right("LEFT", f),
2392            Expression::Right(f) => self.generate_left_right("RIGHT", f),
2393            Expression::Repeat(f) => self.generate_repeat(f),
2394            Expression::Lpad(f) => self.generate_pad("LPAD", f),
2395            Expression::Rpad(f) => self.generate_pad("RPAD", f),
2396            Expression::Split(f) => self.generate_split(f),
2397            Expression::RegexpLike(f) => self.generate_regexp_like(f),
2398            Expression::RegexpReplace(f) => self.generate_regexp_replace(f),
2399            Expression::RegexpExtract(f) => self.generate_regexp_extract(f),
2400            Expression::Overlay(f) => self.generate_overlay(f),
2401
2402            // Math functions
2403            Expression::Abs(f) => self.generate_simple_func("ABS", &f.this),
2404            Expression::Round(f) => self.generate_round(f),
2405            Expression::Floor(f) => self.generate_floor(f),
2406            Expression::Ceil(f) => self.generate_ceil(f),
2407            Expression::Power(f) => self.generate_power(f),
2408            Expression::Sqrt(f) => self.generate_sqrt_cbrt(f, "SQRT", "|/"),
2409            Expression::Cbrt(f) => self.generate_sqrt_cbrt(f, "CBRT", "||/"),
2410            Expression::Ln(f) => self.generate_simple_func("LN", &f.this),
2411            Expression::Log(f) => self.generate_log(f),
2412            Expression::Exp(f) => self.generate_simple_func("EXP", &f.this),
2413            Expression::Sign(f) => self.generate_simple_func("SIGN", &f.this),
2414            Expression::Greatest(f) => self.generate_vararg_func("GREATEST", &f.expressions),
2415            Expression::Least(f) => self.generate_vararg_func("LEAST", &f.expressions),
2416
2417            // Date/time functions
2418            Expression::CurrentDate(_) => {
2419                self.write_keyword("CURRENT_DATE");
2420                Ok(())
2421            }
2422            Expression::CurrentTime(f) => self.generate_current_time(f),
2423            Expression::CurrentTimestamp(f) => self.generate_current_timestamp(f),
2424            Expression::AtTimeZone(f) => self.generate_at_time_zone(f),
2425            Expression::DateAdd(f) => self.generate_date_add(f, "DATE_ADD"),
2426            Expression::DateSub(f) => self.generate_date_add(f, "DATE_SUB"),
2427            Expression::DateDiff(f) => self.generate_datediff(f),
2428            Expression::DateTrunc(f) => self.generate_date_trunc(f),
2429            Expression::Extract(f) => self.generate_extract(f),
2430            Expression::ToDate(f) => self.generate_to_date(f),
2431            Expression::ToTimestamp(f) => self.generate_to_timestamp(f),
2432
2433            // Control flow functions
2434            Expression::Coalesce(f) => {
2435                // Use original function name if preserved (COALESCE, IFNULL)
2436                let func_name = f.original_name.as_deref().unwrap_or("COALESCE");
2437                self.generate_vararg_func(func_name, &f.expressions)
2438            }
2439            Expression::NullIf(f) => self.generate_binary_func("NULLIF", &f.this, &f.expression),
2440            Expression::IfFunc(f) => self.generate_if_func(f),
2441            Expression::IfNull(f) => self.generate_ifnull(f),
2442            Expression::Nvl(f) => self.generate_nvl(f),
2443            Expression::Nvl2(f) => self.generate_nvl2(f),
2444
2445            // Type conversion
2446            Expression::TryCast(cast) => self.generate_try_cast(cast),
2447            Expression::SafeCast(cast) => self.generate_safe_cast(cast),
2448
2449            // Typed aggregate functions
2450            Expression::Count(f) => self.generate_count(f),
2451            Expression::Sum(f) => self.generate_agg_func("SUM", f),
2452            Expression::Avg(f) => self.generate_agg_func("AVG", f),
2453            Expression::Min(f) => self.generate_agg_func("MIN", f),
2454            Expression::Max(f) => self.generate_agg_func("MAX", f),
2455            Expression::GroupConcat(f) => self.generate_group_concat(f),
2456            Expression::StringAgg(f) => self.generate_string_agg(f),
2457            Expression::ListAgg(f) => self.generate_listagg(f),
2458            Expression::ArrayAgg(f) => {
2459                // Allow cross-dialect transforms to override the function name
2460                // (e.g., COLLECT_LIST for Spark)
2461                let override_name = f
2462                    .name
2463                    .as_ref()
2464                    .filter(|n| n.to_uppercase() != "ARRAY_AGG")
2465                    .map(|n| n.to_uppercase());
2466                match override_name {
2467                    Some(name) => self.generate_agg_func(&name, f),
2468                    None => self.generate_agg_func("ARRAY_AGG", f),
2469                }
2470            }
2471            Expression::ArrayConcatAgg(f) => self.generate_agg_func("ARRAY_CONCAT_AGG", f),
2472            Expression::CountIf(f) => self.generate_agg_func("COUNT_IF", f),
2473            Expression::SumIf(f) => self.generate_sum_if(f),
2474            Expression::Stddev(f) => self.generate_agg_func("STDDEV", f),
2475            Expression::StddevPop(f) => self.generate_agg_func("STDDEV_POP", f),
2476            Expression::StddevSamp(f) => self.generate_stddev_samp(f),
2477            Expression::Variance(f) => self.generate_agg_func("VARIANCE", f),
2478            Expression::VarPop(f) => {
2479                let name = if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
2480                    "VARIANCE_POP"
2481                } else {
2482                    "VAR_POP"
2483                };
2484                self.generate_agg_func(name, f)
2485            }
2486            Expression::VarSamp(f) => self.generate_agg_func("VAR_SAMP", f),
2487            Expression::Skewness(f) => {
2488                let name = match self.config.dialect {
2489                    Some(DialectType::Snowflake) => "SKEW",
2490                    _ => "SKEWNESS",
2491                };
2492                self.generate_agg_func(name, f)
2493            }
2494            Expression::Median(f) => self.generate_agg_func("MEDIAN", f),
2495            Expression::Mode(f) => self.generate_agg_func("MODE", f),
2496            Expression::First(f) => self.generate_agg_func("FIRST", f),
2497            Expression::Last(f) => self.generate_agg_func("LAST", f),
2498            Expression::AnyValue(f) => self.generate_agg_func("ANY_VALUE", f),
2499            Expression::ApproxDistinct(f) => {
2500                match self.config.dialect {
2501                    Some(DialectType::Hive)
2502                    | Some(DialectType::Spark)
2503                    | Some(DialectType::Databricks)
2504                    | Some(DialectType::BigQuery) => {
2505                        // These dialects use APPROX_COUNT_DISTINCT (single arg only)
2506                        self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2507                    }
2508                    Some(DialectType::Redshift) => {
2509                        // Redshift uses APPROXIMATE COUNT(DISTINCT expr)
2510                        self.write_keyword("APPROXIMATE COUNT");
2511                        self.write("(");
2512                        self.write_keyword("DISTINCT");
2513                        self.write(" ");
2514                        self.generate_expression(&f.this)?;
2515                        self.write(")");
2516                        Ok(())
2517                    }
2518                    _ => self.generate_agg_func("APPROX_DISTINCT", f),
2519                }
2520            }
2521            Expression::ApproxCountDistinct(f) => {
2522                self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2523            }
2524            Expression::ApproxPercentile(f) => self.generate_approx_percentile(f),
2525            Expression::Percentile(f) => self.generate_percentile("PERCENTILE", f),
2526            Expression::LogicalAnd(f) => {
2527                let name = match self.config.dialect {
2528                    Some(DialectType::Snowflake) => "BOOLAND_AGG",
2529                    Some(DialectType::Spark)
2530                    | Some(DialectType::Databricks)
2531                    | Some(DialectType::PostgreSQL)
2532                    | Some(DialectType::DuckDB)
2533                    | Some(DialectType::Redshift) => "BOOL_AND",
2534                    Some(DialectType::Oracle)
2535                    | Some(DialectType::SQLite)
2536                    | Some(DialectType::MySQL) => "MIN",
2537                    _ => "BOOL_AND",
2538                };
2539                self.generate_agg_func(name, f)
2540            }
2541            Expression::LogicalOr(f) => {
2542                let name = match self.config.dialect {
2543                    Some(DialectType::Snowflake) => "BOOLOR_AGG",
2544                    Some(DialectType::Spark)
2545                    | Some(DialectType::Databricks)
2546                    | Some(DialectType::PostgreSQL)
2547                    | Some(DialectType::DuckDB)
2548                    | Some(DialectType::Redshift) => "BOOL_OR",
2549                    Some(DialectType::Oracle)
2550                    | Some(DialectType::SQLite)
2551                    | Some(DialectType::MySQL) => "MAX",
2552                    _ => "BOOL_OR",
2553                };
2554                self.generate_agg_func(name, f)
2555            }
2556
2557            // Typed window functions
2558            Expression::RowNumber(_) => {
2559                if self.config.dialect == Some(DialectType::ClickHouse) {
2560                    self.write("row_number");
2561                } else {
2562                    self.write_keyword("ROW_NUMBER");
2563                }
2564                self.write("()");
2565                Ok(())
2566            }
2567            Expression::Rank(r) => {
2568                self.write_keyword("RANK");
2569                self.write("(");
2570                // Oracle hypothetical rank args: RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2571                if !r.args.is_empty() {
2572                    for (i, arg) in r.args.iter().enumerate() {
2573                        if i > 0 {
2574                            self.write(", ");
2575                        }
2576                        self.generate_expression(arg)?;
2577                    }
2578                } else if let Some(order_by) = &r.order_by {
2579                    // DuckDB: RANK(ORDER BY col)
2580                    self.write_keyword(" ORDER BY ");
2581                    for (i, ob) in order_by.iter().enumerate() {
2582                        if i > 0 {
2583                            self.write(", ");
2584                        }
2585                        self.generate_ordered(ob)?;
2586                    }
2587                }
2588                self.write(")");
2589                Ok(())
2590            }
2591            Expression::DenseRank(dr) => {
2592                self.write_keyword("DENSE_RANK");
2593                self.write("(");
2594                // Oracle hypothetical rank args: DENSE_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2595                for (i, arg) in dr.args.iter().enumerate() {
2596                    if i > 0 {
2597                        self.write(", ");
2598                    }
2599                    self.generate_expression(arg)?;
2600                }
2601                self.write(")");
2602                Ok(())
2603            }
2604            Expression::NTile(f) => self.generate_ntile(f),
2605            Expression::Lead(f) => self.generate_lead_lag("LEAD", f),
2606            Expression::Lag(f) => self.generate_lead_lag("LAG", f),
2607            Expression::FirstValue(f) => self.generate_value_func("FIRST_VALUE", f),
2608            Expression::LastValue(f) => self.generate_value_func("LAST_VALUE", f),
2609            Expression::NthValue(f) => self.generate_nth_value(f),
2610            Expression::PercentRank(pr) => {
2611                self.write_keyword("PERCENT_RANK");
2612                self.write("(");
2613                // Oracle hypothetical rank args: PERCENT_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2614                if !pr.args.is_empty() {
2615                    for (i, arg) in pr.args.iter().enumerate() {
2616                        if i > 0 {
2617                            self.write(", ");
2618                        }
2619                        self.generate_expression(arg)?;
2620                    }
2621                } else if let Some(order_by) = &pr.order_by {
2622                    // DuckDB: PERCENT_RANK(ORDER BY col)
2623                    self.write_keyword(" ORDER BY ");
2624                    for (i, ob) in order_by.iter().enumerate() {
2625                        if i > 0 {
2626                            self.write(", ");
2627                        }
2628                        self.generate_ordered(ob)?;
2629                    }
2630                }
2631                self.write(")");
2632                Ok(())
2633            }
2634            Expression::CumeDist(cd) => {
2635                self.write_keyword("CUME_DIST");
2636                self.write("(");
2637                // Oracle hypothetical rank args: CUME_DIST(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2638                if !cd.args.is_empty() {
2639                    for (i, arg) in cd.args.iter().enumerate() {
2640                        if i > 0 {
2641                            self.write(", ");
2642                        }
2643                        self.generate_expression(arg)?;
2644                    }
2645                } else if let Some(order_by) = &cd.order_by {
2646                    // DuckDB: CUME_DIST(ORDER BY col)
2647                    self.write_keyword(" ORDER BY ");
2648                    for (i, ob) in order_by.iter().enumerate() {
2649                        if i > 0 {
2650                            self.write(", ");
2651                        }
2652                        self.generate_ordered(ob)?;
2653                    }
2654                }
2655                self.write(")");
2656                Ok(())
2657            }
2658            Expression::PercentileCont(f) => self.generate_percentile("PERCENTILE_CONT", f),
2659            Expression::PercentileDisc(f) => self.generate_percentile("PERCENTILE_DISC", f),
2660
2661            // Additional string functions
2662            Expression::Contains(f) => {
2663                self.generate_binary_func("CONTAINS", &f.this, &f.expression)
2664            }
2665            Expression::StartsWith(f) => {
2666                let name = match self.config.dialect {
2667                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "STARTSWITH",
2668                    _ => "STARTS_WITH",
2669                };
2670                self.generate_binary_func(name, &f.this, &f.expression)
2671            }
2672            Expression::EndsWith(f) => {
2673                let name = match self.config.dialect {
2674                    Some(DialectType::Snowflake) => "ENDSWITH",
2675                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "ENDSWITH",
2676                    Some(DialectType::ClickHouse) => "endsWith",
2677                    _ => "ENDS_WITH",
2678                };
2679                self.generate_binary_func(name, &f.this, &f.expression)
2680            }
2681            Expression::Position(f) => self.generate_position(f),
2682            Expression::Initcap(f) => match self.config.dialect {
2683                Some(DialectType::Presto)
2684                | Some(DialectType::Trino)
2685                | Some(DialectType::Athena) => {
2686                    self.write_keyword("REGEXP_REPLACE");
2687                    self.write("(");
2688                    self.generate_expression(&f.this)?;
2689                    self.write(", '(\\w)(\\w*)', x -> UPPER(x[1]) || LOWER(x[2]))");
2690                    Ok(())
2691                }
2692                _ => self.generate_simple_func("INITCAP", &f.this),
2693            },
2694            Expression::Ascii(f) => self.generate_simple_func("ASCII", &f.this),
2695            Expression::Chr(f) => self.generate_simple_func("CHR", &f.this),
2696            Expression::CharFunc(f) => self.generate_char_func(f),
2697            Expression::Soundex(f) => self.generate_simple_func("SOUNDEX", &f.this),
2698            Expression::Levenshtein(f) => {
2699                self.generate_binary_func("LEVENSHTEIN", &f.this, &f.expression)
2700            }
2701
2702            // Additional math functions
2703            Expression::ModFunc(f) => self.generate_mod_func(f),
2704            Expression::Random(_) => {
2705                self.write_keyword("RANDOM");
2706                self.write("()");
2707                Ok(())
2708            }
2709            Expression::Rand(f) => self.generate_rand(f),
2710            Expression::TruncFunc(f) => self.generate_truncate_func(f),
2711            Expression::Pi(_) => {
2712                self.write_keyword("PI");
2713                self.write("()");
2714                Ok(())
2715            }
2716            Expression::Radians(f) => self.generate_simple_func("RADIANS", &f.this),
2717            Expression::Degrees(f) => self.generate_simple_func("DEGREES", &f.this),
2718            Expression::Sin(f) => self.generate_simple_func("SIN", &f.this),
2719            Expression::Cos(f) => self.generate_simple_func("COS", &f.this),
2720            Expression::Tan(f) => self.generate_simple_func("TAN", &f.this),
2721            Expression::Asin(f) => self.generate_simple_func("ASIN", &f.this),
2722            Expression::Acos(f) => self.generate_simple_func("ACOS", &f.this),
2723            Expression::Atan(f) => self.generate_simple_func("ATAN", &f.this),
2724            Expression::Atan2(f) => {
2725                let name = f.original_name.as_deref().unwrap_or("ATAN2");
2726                self.generate_binary_func(name, &f.this, &f.expression)
2727            }
2728
2729            // Control flow
2730            Expression::Decode(f) => self.generate_decode(f),
2731
2732            // Additional date/time functions
2733            Expression::DateFormat(f) => self.generate_date_format("DATE_FORMAT", f),
2734            Expression::FormatDate(f) => self.generate_date_format("FORMAT_DATE", f),
2735            Expression::Year(f) => self.generate_simple_func("YEAR", &f.this),
2736            Expression::Month(f) => self.generate_simple_func("MONTH", &f.this),
2737            Expression::Day(f) => self.generate_simple_func("DAY", &f.this),
2738            Expression::Hour(f) => self.generate_simple_func("HOUR", &f.this),
2739            Expression::Minute(f) => self.generate_simple_func("MINUTE", &f.this),
2740            Expression::Second(f) => self.generate_simple_func("SECOND", &f.this),
2741            Expression::DayOfWeek(f) => {
2742                let name = match self.config.dialect {
2743                    Some(DialectType::Presto)
2744                    | Some(DialectType::Trino)
2745                    | Some(DialectType::Athena) => "DAY_OF_WEEK",
2746                    Some(DialectType::DuckDB) => "ISODOW",
2747                    _ => "DAYOFWEEK",
2748                };
2749                self.generate_simple_func(name, &f.this)
2750            }
2751            Expression::DayOfMonth(f) => {
2752                let name = match self.config.dialect {
2753                    Some(DialectType::Presto)
2754                    | Some(DialectType::Trino)
2755                    | Some(DialectType::Athena) => "DAY_OF_MONTH",
2756                    _ => "DAYOFMONTH",
2757                };
2758                self.generate_simple_func(name, &f.this)
2759            }
2760            Expression::DayOfYear(f) => {
2761                let name = match self.config.dialect {
2762                    Some(DialectType::Presto)
2763                    | Some(DialectType::Trino)
2764                    | Some(DialectType::Athena) => "DAY_OF_YEAR",
2765                    _ => "DAYOFYEAR",
2766                };
2767                self.generate_simple_func(name, &f.this)
2768            }
2769            Expression::WeekOfYear(f) => {
2770                // Python sqlglot default is WEEK_OF_YEAR; Hive/DuckDB/Spark/MySQL override to WEEKOFYEAR
2771                let name = match self.config.dialect {
2772                    Some(DialectType::Hive)
2773                    | Some(DialectType::DuckDB)
2774                    | Some(DialectType::Spark)
2775                    | Some(DialectType::Databricks)
2776                    | Some(DialectType::MySQL) => "WEEKOFYEAR",
2777                    _ => "WEEK_OF_YEAR",
2778                };
2779                self.generate_simple_func(name, &f.this)
2780            }
2781            Expression::Quarter(f) => self.generate_simple_func("QUARTER", &f.this),
2782            Expression::AddMonths(f) => {
2783                self.generate_binary_func("ADD_MONTHS", &f.this, &f.expression)
2784            }
2785            Expression::MonthsBetween(f) => {
2786                self.generate_binary_func("MONTHS_BETWEEN", &f.this, &f.expression)
2787            }
2788            Expression::LastDay(f) => self.generate_last_day(f),
2789            Expression::NextDay(f) => self.generate_binary_func("NEXT_DAY", &f.this, &f.expression),
2790            Expression::Epoch(f) => self.generate_simple_func("EPOCH", &f.this),
2791            Expression::EpochMs(f) => self.generate_simple_func("EPOCH_MS", &f.this),
2792            Expression::FromUnixtime(f) => self.generate_from_unixtime(f),
2793            Expression::UnixTimestamp(f) => self.generate_unix_timestamp(f),
2794            Expression::MakeDate(f) => self.generate_make_date(f),
2795            Expression::MakeTimestamp(f) => self.generate_make_timestamp(f),
2796            Expression::TimestampTrunc(f) => self.generate_date_trunc(f),
2797
2798            // Array functions
2799            Expression::ArrayFunc(f) => self.generate_array_constructor(f),
2800            Expression::ArrayLength(f) => self.generate_simple_func("ARRAY_LENGTH", &f.this),
2801            Expression::ArraySize(f) => self.generate_simple_func("ARRAY_SIZE", &f.this),
2802            Expression::Cardinality(f) => self.generate_simple_func("CARDINALITY", &f.this),
2803            Expression::ArrayContains(f) => {
2804                self.generate_binary_func("ARRAY_CONTAINS", &f.this, &f.expression)
2805            }
2806            Expression::ArrayPosition(f) => {
2807                self.generate_binary_func("ARRAY_POSITION", &f.this, &f.expression)
2808            }
2809            Expression::ArrayAppend(f) => {
2810                self.generate_binary_func("ARRAY_APPEND", &f.this, &f.expression)
2811            }
2812            Expression::ArrayPrepend(f) => {
2813                self.generate_binary_func("ARRAY_PREPEND", &f.this, &f.expression)
2814            }
2815            Expression::ArrayConcat(f) => self.generate_vararg_func("ARRAY_CONCAT", &f.expressions),
2816            Expression::ArraySort(f) => self.generate_array_sort(f),
2817            Expression::ArrayReverse(f) => self.generate_simple_func("ARRAY_REVERSE", &f.this),
2818            Expression::ArrayDistinct(f) => self.generate_simple_func("ARRAY_DISTINCT", &f.this),
2819            Expression::ArrayJoin(f) => self.generate_array_join("ARRAY_JOIN", f),
2820            Expression::ArrayToString(f) => self.generate_array_join("ARRAY_TO_STRING", f),
2821            Expression::Unnest(f) => self.generate_unnest(f),
2822            Expression::Explode(f) => self.generate_simple_func("EXPLODE", &f.this),
2823            Expression::ExplodeOuter(f) => self.generate_simple_func("EXPLODE_OUTER", &f.this),
2824            Expression::ArrayFilter(f) => self.generate_array_filter(f),
2825            Expression::ArrayTransform(f) => self.generate_array_transform(f),
2826            Expression::ArrayFlatten(f) => self.generate_simple_func("FLATTEN", &f.this),
2827            Expression::ArrayCompact(f) => {
2828                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
2829                    // DuckDB: ARRAY_COMPACT(arr) -> LIST_FILTER(arr, _u -> NOT _u IS NULL)
2830                    self.write("LIST_FILTER(");
2831                    self.generate_expression(&f.this)?;
2832                    self.write(", _u -> NOT _u IS NULL)");
2833                    Ok(())
2834                } else {
2835                    self.generate_simple_func("ARRAY_COMPACT", &f.this)
2836                }
2837            }
2838            Expression::ArrayIntersect(f) => {
2839                let func_name = f.original_name.as_deref().unwrap_or("ARRAY_INTERSECT");
2840                self.generate_vararg_func(func_name, &f.expressions)
2841            }
2842            Expression::ArrayUnion(f) => {
2843                self.generate_binary_func("ARRAY_UNION", &f.this, &f.expression)
2844            }
2845            Expression::ArrayExcept(f) => {
2846                self.generate_binary_func("ARRAY_EXCEPT", &f.this, &f.expression)
2847            }
2848            Expression::ArrayRemove(f) => {
2849                self.generate_binary_func("ARRAY_REMOVE", &f.this, &f.expression)
2850            }
2851            Expression::ArrayZip(f) => self.generate_vararg_func("ARRAYS_ZIP", &f.expressions),
2852            Expression::Sequence(f) => self.generate_sequence("SEQUENCE", f),
2853            Expression::Generate(f) => self.generate_sequence("GENERATE_SERIES", f),
2854
2855            // Struct functions
2856            Expression::StructFunc(f) => self.generate_struct_constructor(f),
2857            Expression::StructExtract(f) => self.generate_struct_extract(f),
2858            Expression::NamedStruct(f) => self.generate_named_struct(f),
2859
2860            // Map functions
2861            Expression::MapFunc(f) => self.generate_map_constructor(f),
2862            Expression::MapFromEntries(f) => self.generate_simple_func("MAP_FROM_ENTRIES", &f.this),
2863            Expression::MapFromArrays(f) => {
2864                self.generate_binary_func("MAP_FROM_ARRAYS", &f.this, &f.expression)
2865            }
2866            Expression::MapKeys(f) => self.generate_simple_func("MAP_KEYS", &f.this),
2867            Expression::MapValues(f) => self.generate_simple_func("MAP_VALUES", &f.this),
2868            Expression::MapContainsKey(f) => {
2869                self.generate_binary_func("MAP_CONTAINS_KEY", &f.this, &f.expression)
2870            }
2871            Expression::MapConcat(f) => self.generate_vararg_func("MAP_CONCAT", &f.expressions),
2872            Expression::ElementAt(f) => {
2873                self.generate_binary_func("ELEMENT_AT", &f.this, &f.expression)
2874            }
2875            Expression::TransformKeys(f) => self.generate_transform_func("TRANSFORM_KEYS", f),
2876            Expression::TransformValues(f) => self.generate_transform_func("TRANSFORM_VALUES", f),
2877
2878            // JSON functions
2879            Expression::JsonExtract(f) => self.generate_json_extract("JSON_EXTRACT", f),
2880            Expression::JsonExtractScalar(f) => {
2881                self.generate_json_extract("JSON_EXTRACT_SCALAR", f)
2882            }
2883            Expression::JsonExtractPath(f) => self.generate_json_path("JSON_EXTRACT_PATH", f),
2884            Expression::JsonArray(f) => self.generate_vararg_func("JSON_ARRAY", &f.expressions),
2885            Expression::JsonObject(f) => self.generate_json_object(f),
2886            Expression::JsonQuery(f) => self.generate_json_extract("JSON_QUERY", f),
2887            Expression::JsonValue(f) => self.generate_json_extract("JSON_VALUE", f),
2888            Expression::JsonArrayLength(f) => {
2889                self.generate_simple_func("JSON_ARRAY_LENGTH", &f.this)
2890            }
2891            Expression::JsonKeys(f) => self.generate_simple_func("JSON_KEYS", &f.this),
2892            Expression::JsonType(f) => self.generate_simple_func("JSON_TYPE", &f.this),
2893            Expression::ParseJson(f) => {
2894                let name = match self.config.dialect {
2895                    Some(DialectType::Presto)
2896                    | Some(DialectType::Trino)
2897                    | Some(DialectType::Athena) => "JSON_PARSE",
2898                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
2899                        // PostgreSQL: CAST(x AS JSON)
2900                        self.write_keyword("CAST");
2901                        self.write("(");
2902                        self.generate_expression(&f.this)?;
2903                        self.write_keyword(" AS ");
2904                        self.write_keyword("JSON");
2905                        self.write(")");
2906                        return Ok(());
2907                    }
2908                    Some(DialectType::Hive)
2909                    | Some(DialectType::Spark)
2910                    | Some(DialectType::MySQL)
2911                    | Some(DialectType::SingleStore)
2912                    | Some(DialectType::TiDB)
2913                    | Some(DialectType::TSQL) => {
2914                        // Hive/Spark/MySQL/TSQL: just emit the string literal
2915                        self.generate_expression(&f.this)?;
2916                        return Ok(());
2917                    }
2918                    Some(DialectType::DuckDB) => "JSON",
2919                    _ => "PARSE_JSON",
2920                };
2921                self.generate_simple_func(name, &f.this)
2922            }
2923            Expression::ToJson(f) => self.generate_simple_func("TO_JSON", &f.this),
2924            Expression::JsonSet(f) => self.generate_json_modify("JSON_SET", f),
2925            Expression::JsonInsert(f) => self.generate_json_modify("JSON_INSERT", f),
2926            Expression::JsonRemove(f) => self.generate_json_path("JSON_REMOVE", f),
2927            Expression::JsonMergePatch(f) => {
2928                self.generate_binary_func("JSON_MERGE_PATCH", &f.this, &f.expression)
2929            }
2930            Expression::JsonArrayAgg(f) => self.generate_json_array_agg(f),
2931            Expression::JsonObjectAgg(f) => self.generate_json_object_agg(f),
2932
2933            // Type casting/conversion
2934            Expression::Convert(f) => self.generate_convert(f),
2935            Expression::Typeof(f) => self.generate_simple_func("TYPEOF", &f.this),
2936
2937            // Additional expressions
2938            Expression::Lambda(f) => self.generate_lambda(f),
2939            Expression::Parameter(f) => self.generate_parameter(f),
2940            Expression::Placeholder(f) => self.generate_placeholder(f),
2941            Expression::NamedArgument(f) => self.generate_named_argument(f),
2942            Expression::TableArgument(f) => self.generate_table_argument(f),
2943            Expression::SqlComment(f) => self.generate_sql_comment(f),
2944
2945            // Additional predicates
2946            Expression::NullSafeEq(op) => self.generate_null_safe_eq(op),
2947            Expression::NullSafeNeq(op) => self.generate_null_safe_neq(op),
2948            Expression::Glob(op) => self.generate_binary_op(op, "GLOB"),
2949            Expression::SimilarTo(f) => self.generate_similar_to(f),
2950            Expression::Any(f) => self.generate_quantified("ANY", f),
2951            Expression::All(f) => self.generate_quantified("ALL", f),
2952            Expression::Overlaps(f) => self.generate_overlaps(f),
2953
2954            // Bitwise operations
2955            Expression::BitwiseLeftShift(op) => {
2956                if matches!(
2957                    self.config.dialect,
2958                    Some(DialectType::Presto) | Some(DialectType::Trino)
2959                ) {
2960                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_LEFT");
2961                    self.write("(");
2962                    self.generate_expression(&op.left)?;
2963                    self.write(", ");
2964                    self.generate_expression(&op.right)?;
2965                    self.write(")");
2966                    Ok(())
2967                } else if matches!(
2968                    self.config.dialect,
2969                    Some(DialectType::Spark) | Some(DialectType::Databricks)
2970                ) {
2971                    self.write_keyword("SHIFTLEFT");
2972                    self.write("(");
2973                    self.generate_expression(&op.left)?;
2974                    self.write(", ");
2975                    self.generate_expression(&op.right)?;
2976                    self.write(")");
2977                    Ok(())
2978                } else {
2979                    self.generate_binary_op(op, "<<")
2980                }
2981            }
2982            Expression::BitwiseRightShift(op) => {
2983                if matches!(
2984                    self.config.dialect,
2985                    Some(DialectType::Presto) | Some(DialectType::Trino)
2986                ) {
2987                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_RIGHT");
2988                    self.write("(");
2989                    self.generate_expression(&op.left)?;
2990                    self.write(", ");
2991                    self.generate_expression(&op.right)?;
2992                    self.write(")");
2993                    Ok(())
2994                } else if matches!(
2995                    self.config.dialect,
2996                    Some(DialectType::Spark) | Some(DialectType::Databricks)
2997                ) {
2998                    self.write_keyword("SHIFTRIGHT");
2999                    self.write("(");
3000                    self.generate_expression(&op.left)?;
3001                    self.write(", ");
3002                    self.generate_expression(&op.right)?;
3003                    self.write(")");
3004                    Ok(())
3005                } else {
3006                    self.generate_binary_op(op, ">>")
3007                }
3008            }
3009            Expression::BitwiseAndAgg(f) => self.generate_agg_func("BIT_AND", f),
3010            Expression::BitwiseOrAgg(f) => self.generate_agg_func("BIT_OR", f),
3011            Expression::BitwiseXorAgg(f) => self.generate_agg_func("BIT_XOR", f),
3012
3013            // Array/struct/map access
3014            Expression::Subscript(s) => self.generate_subscript(s),
3015            Expression::Dot(d) => self.generate_dot_access(d),
3016            Expression::MethodCall(m) => self.generate_method_call(m),
3017            Expression::ArraySlice(s) => self.generate_array_slice(s),
3018
3019            Expression::And(op) => self.generate_connector_op(op, ConnectorOperator::And),
3020            Expression::Or(op) => self.generate_connector_op(op, ConnectorOperator::Or),
3021            Expression::Add(op) => self.generate_binary_op(op, "+"),
3022            Expression::Sub(op) => self.generate_binary_op(op, "-"),
3023            Expression::Mul(op) => self.generate_binary_op(op, "*"),
3024            Expression::Div(op) => self.generate_binary_op(op, "/"),
3025            Expression::IntDiv(f) => {
3026                use crate::dialects::DialectType;
3027                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
3028                    // DuckDB uses // operator for integer division
3029                    self.generate_expression(&f.this)?;
3030                    self.write(" // ");
3031                    self.generate_expression(&f.expression)?;
3032                    Ok(())
3033                } else if matches!(
3034                    self.config.dialect,
3035                    Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
3036                ) {
3037                    // Hive/Spark use DIV as an infix operator
3038                    self.generate_expression(&f.this)?;
3039                    self.write(" ");
3040                    self.write_keyword("DIV");
3041                    self.write(" ");
3042                    self.generate_expression(&f.expression)?;
3043                    Ok(())
3044                } else {
3045                    // Other dialects use DIV function
3046                    self.write_keyword("DIV");
3047                    self.write("(");
3048                    self.generate_expression(&f.this)?;
3049                    self.write(", ");
3050                    self.generate_expression(&f.expression)?;
3051                    self.write(")");
3052                    Ok(())
3053                }
3054            }
3055            Expression::Mod(op) => {
3056                if matches!(self.config.dialect, Some(DialectType::Teradata)) {
3057                    self.generate_binary_op(op, "MOD")
3058                } else {
3059                    self.generate_binary_op(op, "%")
3060                }
3061            }
3062            Expression::Eq(op) => self.generate_binary_op(op, "="),
3063            Expression::Neq(op) => self.generate_binary_op(op, "<>"),
3064            Expression::Lt(op) => self.generate_binary_op(op, "<"),
3065            Expression::Lte(op) => self.generate_binary_op(op, "<="),
3066            Expression::Gt(op) => self.generate_binary_op(op, ">"),
3067            Expression::Gte(op) => self.generate_binary_op(op, ">="),
3068            Expression::Like(op) => self.generate_like_op(op, "LIKE"),
3069            Expression::ILike(op) => self.generate_like_op(op, "ILIKE"),
3070            Expression::Match(op) => self.generate_binary_op(op, "MATCH"),
3071            Expression::Concat(op) => {
3072                // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
3073                if self.config.dialect == Some(DialectType::Solr) {
3074                    self.generate_binary_op(op, "OR")
3075                } else {
3076                    self.generate_binary_op(op, "||")
3077                }
3078            }
3079            Expression::BitwiseAnd(op) => {
3080                // Presto/Trino use BITWISE_AND function
3081                if matches!(
3082                    self.config.dialect,
3083                    Some(DialectType::Presto) | Some(DialectType::Trino)
3084                ) {
3085                    self.write_keyword("BITWISE_AND");
3086                    self.write("(");
3087                    self.generate_expression(&op.left)?;
3088                    self.write(", ");
3089                    self.generate_expression(&op.right)?;
3090                    self.write(")");
3091                    Ok(())
3092                } else {
3093                    self.generate_binary_op(op, "&")
3094                }
3095            }
3096            Expression::BitwiseOr(op) => {
3097                // Presto/Trino use BITWISE_OR function
3098                if matches!(
3099                    self.config.dialect,
3100                    Some(DialectType::Presto) | Some(DialectType::Trino)
3101                ) {
3102                    self.write_keyword("BITWISE_OR");
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::BitwiseXor(op) => {
3114                // Presto/Trino use BITWISE_XOR function, PostgreSQL uses #, others use ^
3115                if matches!(
3116                    self.config.dialect,
3117                    Some(DialectType::Presto) | Some(DialectType::Trino)
3118                ) {
3119                    self.write_keyword("BITWISE_XOR");
3120                    self.write("(");
3121                    self.generate_expression(&op.left)?;
3122                    self.write(", ");
3123                    self.generate_expression(&op.right)?;
3124                    self.write(")");
3125                    Ok(())
3126                } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
3127                    self.generate_binary_op(op, "#")
3128                } else {
3129                    self.generate_binary_op(op, "^")
3130                }
3131            }
3132            Expression::Adjacent(op) => self.generate_binary_op(op, "-|-"),
3133            Expression::TsMatch(op) => self.generate_binary_op(op, "@@"),
3134            Expression::PropertyEQ(op) => self.generate_binary_op(op, ":="),
3135            Expression::ArrayContainsAll(op) => self.generate_binary_op(op, "@>"),
3136            Expression::ArrayContainedBy(op) => self.generate_binary_op(op, "<@"),
3137            Expression::ArrayOverlaps(op) => self.generate_binary_op(op, "&&"),
3138            Expression::JSONBContainsAllTopKeys(op) => self.generate_binary_op(op, "?&"),
3139            Expression::JSONBContainsAnyTopKeys(op) => self.generate_binary_op(op, "?|"),
3140            Expression::JSONBContains(f) => {
3141                // PostgreSQL JSONB contains key operator: a ? b
3142                self.generate_expression(&f.this)?;
3143                self.write_space();
3144                self.write("?");
3145                self.write_space();
3146                self.generate_expression(&f.expression)
3147            }
3148            Expression::JSONBDeleteAtPath(op) => self.generate_binary_op(op, "#-"),
3149            Expression::ExtendsLeft(op) => self.generate_binary_op(op, "&<"),
3150            Expression::ExtendsRight(op) => self.generate_binary_op(op, "&>"),
3151            Expression::Not(op) => self.generate_unary_op(op, "NOT"),
3152            Expression::Neg(op) => self.generate_unary_op(op, "-"),
3153            Expression::BitwiseNot(op) => {
3154                // Presto/Trino use BITWISE_NOT function
3155                if matches!(
3156                    self.config.dialect,
3157                    Some(DialectType::Presto) | Some(DialectType::Trino)
3158                ) {
3159                    self.write_keyword("BITWISE_NOT");
3160                    self.write("(");
3161                    self.generate_expression(&op.this)?;
3162                    self.write(")");
3163                    Ok(())
3164                } else {
3165                    self.generate_unary_op(op, "~")
3166                }
3167            }
3168            Expression::In(in_expr) => self.generate_in(in_expr),
3169            Expression::Between(between) => self.generate_between(between),
3170            Expression::IsNull(is_null) => self.generate_is_null(is_null),
3171            Expression::IsTrue(is_true) => self.generate_is_true(is_true),
3172            Expression::IsFalse(is_false) => self.generate_is_false(is_false),
3173            Expression::IsJson(is_json) => self.generate_is_json(is_json),
3174            Expression::Is(is_expr) => self.generate_is(is_expr),
3175            Expression::Exists(exists) => self.generate_exists(exists),
3176            Expression::MemberOf(member_of) => self.generate_member_of(member_of),
3177            Expression::Subquery(subquery) => self.generate_subquery(subquery),
3178            Expression::Paren(paren) => {
3179                // JoinedTable already outputs its own parentheses, so don't double-wrap
3180                let skip_parens = matches!(&paren.this, Expression::JoinedTable(_));
3181
3182                if !skip_parens {
3183                    self.write("(");
3184                    if self.config.pretty {
3185                        self.write_newline();
3186                        self.indent_level += 1;
3187                        self.write_indent();
3188                    }
3189                }
3190                self.generate_expression(&paren.this)?;
3191                if !skip_parens {
3192                    if self.config.pretty {
3193                        self.write_newline();
3194                        self.indent_level -= 1;
3195                        self.write_indent();
3196                    }
3197                    self.write(")");
3198                }
3199                // Output trailing comments after closing paren
3200                for comment in &paren.trailing_comments {
3201                    self.write(" ");
3202                    self.write_formatted_comment(comment);
3203                }
3204                Ok(())
3205            }
3206            Expression::Array(arr) => self.generate_array(arr),
3207            Expression::Tuple(tuple) => self.generate_tuple(tuple),
3208            Expression::PipeOperator(pipe) => self.generate_pipe_operator(pipe),
3209            Expression::Ordered(ordered) => self.generate_ordered(ordered),
3210            Expression::DataType(dt) => self.generate_data_type(dt),
3211            Expression::Raw(raw) => {
3212                self.write(&raw.sql);
3213                Ok(())
3214            }
3215            Expression::Command(cmd) => {
3216                self.write(&cmd.this);
3217                Ok(())
3218            }
3219            Expression::Kill(kill) => {
3220                self.write_keyword("KILL");
3221                if let Some(kind) = &kill.kind {
3222                    self.write_space();
3223                    self.write_keyword(kind);
3224                }
3225                self.write_space();
3226                self.generate_expression(&kill.this)?;
3227                Ok(())
3228            }
3229            Expression::Execute(exec) => {
3230                self.write_keyword("EXEC");
3231                self.write_space();
3232                self.generate_expression(&exec.this)?;
3233                for (i, param) in exec.parameters.iter().enumerate() {
3234                    if i == 0 {
3235                        self.write_space();
3236                    } else {
3237                        self.write(", ");
3238                    }
3239                    self.write(&param.name);
3240                    self.write("=");
3241                    self.generate_expression(&param.value)?;
3242                }
3243                Ok(())
3244            }
3245            Expression::Annotated(annotated) => {
3246                self.generate_expression(&annotated.this)?;
3247                for comment in &annotated.trailing_comments {
3248                    self.write(" ");
3249                    self.write_formatted_comment(comment);
3250                }
3251                Ok(())
3252            }
3253
3254            // DDL statements
3255            Expression::CreateTable(ct) => self.generate_create_table(ct),
3256            Expression::DropTable(dt) => self.generate_drop_table(dt),
3257            Expression::AlterTable(at) => self.generate_alter_table(at),
3258            Expression::CreateIndex(ci) => self.generate_create_index(ci),
3259            Expression::DropIndex(di) => self.generate_drop_index(di),
3260            Expression::CreateView(cv) => self.generate_create_view(cv),
3261            Expression::DropView(dv) => self.generate_drop_view(dv),
3262            Expression::AlterView(av) => self.generate_alter_view(av),
3263            Expression::AlterIndex(ai) => self.generate_alter_index(ai),
3264            Expression::Truncate(tr) => self.generate_truncate(tr),
3265            Expression::Use(u) => self.generate_use(u),
3266            // Phase 4: Additional DDL statements
3267            Expression::CreateSchema(cs) => self.generate_create_schema(cs),
3268            Expression::DropSchema(ds) => self.generate_drop_schema(ds),
3269            Expression::DropNamespace(dn) => self.generate_drop_namespace(dn),
3270            Expression::CreateDatabase(cd) => self.generate_create_database(cd),
3271            Expression::DropDatabase(dd) => self.generate_drop_database(dd),
3272            Expression::CreateFunction(cf) => self.generate_create_function(cf),
3273            Expression::DropFunction(df) => self.generate_drop_function(df),
3274            Expression::CreateProcedure(cp) => self.generate_create_procedure(cp),
3275            Expression::DropProcedure(dp) => self.generate_drop_procedure(dp),
3276            Expression::CreateSequence(cs) => self.generate_create_sequence(cs),
3277            Expression::DropSequence(ds) => self.generate_drop_sequence(ds),
3278            Expression::AlterSequence(als) => self.generate_alter_sequence(als),
3279            Expression::CreateTrigger(ct) => self.generate_create_trigger(ct),
3280            Expression::DropTrigger(dt) => self.generate_drop_trigger(dt),
3281            Expression::CreateType(ct) => self.generate_create_type(ct),
3282            Expression::DropType(dt) => self.generate_drop_type(dt),
3283            Expression::Describe(d) => self.generate_describe(d),
3284            Expression::Show(s) => self.generate_show(s),
3285
3286            // CACHE/UNCACHE/LOAD TABLE (Spark/Hive)
3287            Expression::Cache(c) => self.generate_cache(c),
3288            Expression::Uncache(u) => self.generate_uncache(u),
3289            Expression::LoadData(l) => self.generate_load_data(l),
3290            Expression::Pragma(p) => self.generate_pragma(p),
3291            Expression::Grant(g) => self.generate_grant(g),
3292            Expression::Revoke(r) => self.generate_revoke(r),
3293            Expression::Comment(c) => self.generate_comment(c),
3294            Expression::SetStatement(s) => self.generate_set_statement(s),
3295
3296            // PIVOT/UNPIVOT
3297            Expression::Pivot(pivot) => self.generate_pivot(pivot),
3298            Expression::Unpivot(unpivot) => self.generate_unpivot(unpivot),
3299
3300            // VALUES table constructor
3301            Expression::Values(values) => self.generate_values(values),
3302
3303            // === BATCH-GENERATED MATCH ARMS (481 variants) ===
3304            Expression::AIAgg(e) => self.generate_ai_agg(e),
3305            Expression::AIClassify(e) => self.generate_ai_classify(e),
3306            Expression::AddPartition(e) => self.generate_add_partition(e),
3307            Expression::AlgorithmProperty(e) => self.generate_algorithm_property(e),
3308            Expression::Aliases(e) => self.generate_aliases(e),
3309            Expression::AllowedValuesProperty(e) => self.generate_allowed_values_property(e),
3310            Expression::AlterColumn(e) => self.generate_alter_column(e),
3311            Expression::AlterSession(e) => self.generate_alter_session(e),
3312            Expression::AlterSet(e) => self.generate_alter_set(e),
3313            Expression::AlterSortKey(e) => self.generate_alter_sort_key(e),
3314            Expression::Analyze(e) => self.generate_analyze(e),
3315            Expression::AnalyzeDelete(e) => self.generate_analyze_delete(e),
3316            Expression::AnalyzeHistogram(e) => self.generate_analyze_histogram(e),
3317            Expression::AnalyzeListChainedRows(e) => self.generate_analyze_list_chained_rows(e),
3318            Expression::AnalyzeSample(e) => self.generate_analyze_sample(e),
3319            Expression::AnalyzeStatistics(e) => self.generate_analyze_statistics(e),
3320            Expression::AnalyzeValidate(e) => self.generate_analyze_validate(e),
3321            Expression::AnalyzeWith(e) => self.generate_analyze_with(e),
3322            Expression::Anonymous(e) => self.generate_anonymous(e),
3323            Expression::AnonymousAggFunc(e) => self.generate_anonymous_agg_func(e),
3324            Expression::Apply(e) => self.generate_apply(e),
3325            Expression::ApproxPercentileEstimate(e) => self.generate_approx_percentile_estimate(e),
3326            Expression::ApproxQuantile(e) => self.generate_approx_quantile(e),
3327            Expression::ApproxQuantiles(e) => self.generate_approx_quantiles(e),
3328            Expression::ApproxTopK(e) => self.generate_approx_top_k(e),
3329            Expression::ApproxTopKAccumulate(e) => self.generate_approx_top_k_accumulate(e),
3330            Expression::ApproxTopKCombine(e) => self.generate_approx_top_k_combine(e),
3331            Expression::ApproxTopKEstimate(e) => self.generate_approx_top_k_estimate(e),
3332            Expression::ApproxTopSum(e) => self.generate_approx_top_sum(e),
3333            Expression::ArgMax(e) => self.generate_arg_max(e),
3334            Expression::ArgMin(e) => self.generate_arg_min(e),
3335            Expression::ArrayAll(e) => self.generate_array_all(e),
3336            Expression::ArrayAny(e) => self.generate_array_any(e),
3337            Expression::ArrayConstructCompact(e) => self.generate_array_construct_compact(e),
3338            Expression::ArraySum(e) => self.generate_array_sum(e),
3339            Expression::AtIndex(e) => self.generate_at_index(e),
3340            Expression::Attach(e) => self.generate_attach(e),
3341            Expression::AttachOption(e) => self.generate_attach_option(e),
3342            Expression::AutoIncrementProperty(e) => self.generate_auto_increment_property(e),
3343            Expression::AutoRefreshProperty(e) => self.generate_auto_refresh_property(e),
3344            Expression::BackupProperty(e) => self.generate_backup_property(e),
3345            Expression::Base64DecodeBinary(e) => self.generate_base64_decode_binary(e),
3346            Expression::Base64DecodeString(e) => self.generate_base64_decode_string(e),
3347            Expression::Base64Encode(e) => self.generate_base64_encode(e),
3348            Expression::BlockCompressionProperty(e) => self.generate_block_compression_property(e),
3349            Expression::Booland(e) => self.generate_booland(e),
3350            Expression::Boolor(e) => self.generate_boolor(e),
3351            Expression::BuildProperty(e) => self.generate_build_property(e),
3352            Expression::ByteString(e) => self.generate_byte_string(e),
3353            Expression::CaseSpecificColumnConstraint(e) => {
3354                self.generate_case_specific_column_constraint(e)
3355            }
3356            Expression::CastToStrType(e) => self.generate_cast_to_str_type(e),
3357            Expression::Changes(e) => self.generate_changes(e),
3358            Expression::CharacterSetColumnConstraint(e) => {
3359                self.generate_character_set_column_constraint(e)
3360            }
3361            Expression::CharacterSetProperty(e) => self.generate_character_set_property(e),
3362            Expression::CheckColumnConstraint(e) => self.generate_check_column_constraint(e),
3363            Expression::CheckJson(e) => self.generate_check_json(e),
3364            Expression::CheckXml(e) => self.generate_check_xml(e),
3365            Expression::ChecksumProperty(e) => self.generate_checksum_property(e),
3366            Expression::Clone(e) => self.generate_clone(e),
3367            Expression::ClusterBy(e) => self.generate_cluster_by(e),
3368            Expression::ClusteredByProperty(e) => self.generate_clustered_by_property(e),
3369            Expression::CollateProperty(e) => self.generate_collate_property(e),
3370            Expression::ColumnConstraint(e) => self.generate_column_constraint(e),
3371            Expression::ColumnDef(e) => self.generate_column_def_expr(e),
3372            Expression::ColumnPosition(e) => self.generate_column_position(e),
3373            Expression::ColumnPrefix(e) => self.generate_column_prefix(e),
3374            Expression::Columns(e) => self.generate_columns(e),
3375            Expression::CombinedAggFunc(e) => self.generate_combined_agg_func(e),
3376            Expression::CombinedParameterizedAgg(e) => self.generate_combined_parameterized_agg(e),
3377            Expression::Commit(e) => self.generate_commit(e),
3378            Expression::Comprehension(e) => self.generate_comprehension(e),
3379            Expression::Compress(e) => self.generate_compress(e),
3380            Expression::CompressColumnConstraint(e) => self.generate_compress_column_constraint(e),
3381            Expression::ComputedColumnConstraint(e) => self.generate_computed_column_constraint(e),
3382            Expression::ConditionalInsert(e) => self.generate_conditional_insert(e),
3383            Expression::Constraint(e) => self.generate_constraint(e),
3384            Expression::ConvertTimezone(e) => self.generate_convert_timezone(e),
3385            Expression::ConvertToCharset(e) => self.generate_convert_to_charset(e),
3386            Expression::Copy(e) => self.generate_copy(e),
3387            Expression::CopyParameter(e) => self.generate_copy_parameter(e),
3388            Expression::Corr(e) => self.generate_corr(e),
3389            Expression::CosineDistance(e) => self.generate_cosine_distance(e),
3390            Expression::CovarPop(e) => self.generate_covar_pop(e),
3391            Expression::CovarSamp(e) => self.generate_covar_samp(e),
3392            Expression::Credentials(e) => self.generate_credentials(e),
3393            Expression::CredentialsProperty(e) => self.generate_credentials_property(e),
3394            Expression::Cte(e) => self.generate_cte(e),
3395            Expression::Cube(e) => self.generate_cube(e),
3396            Expression::CurrentDatetime(e) => self.generate_current_datetime(e),
3397            Expression::CurrentSchema(e) => self.generate_current_schema(e),
3398            Expression::CurrentSchemas(e) => self.generate_current_schemas(e),
3399            Expression::CurrentUser(e) => self.generate_current_user(e),
3400            Expression::DPipe(e) => self.generate_d_pipe(e),
3401            Expression::DataBlocksizeProperty(e) => self.generate_data_blocksize_property(e),
3402            Expression::DataDeletionProperty(e) => self.generate_data_deletion_property(e),
3403            Expression::Date(e) => self.generate_date_func(e),
3404            Expression::DateBin(e) => self.generate_date_bin(e),
3405            Expression::DateFormatColumnConstraint(e) => {
3406                self.generate_date_format_column_constraint(e)
3407            }
3408            Expression::DateFromParts(e) => self.generate_date_from_parts(e),
3409            Expression::Datetime(e) => self.generate_datetime(e),
3410            Expression::DatetimeAdd(e) => self.generate_datetime_add(e),
3411            Expression::DatetimeDiff(e) => self.generate_datetime_diff(e),
3412            Expression::DatetimeSub(e) => self.generate_datetime_sub(e),
3413            Expression::DatetimeTrunc(e) => self.generate_datetime_trunc(e),
3414            Expression::Dayname(e) => self.generate_dayname(e),
3415            Expression::Declare(e) => self.generate_declare(e),
3416            Expression::DeclareItem(e) => self.generate_declare_item(e),
3417            Expression::DecodeCase(e) => self.generate_decode_case(e),
3418            Expression::DecompressBinary(e) => self.generate_decompress_binary(e),
3419            Expression::DecompressString(e) => self.generate_decompress_string(e),
3420            Expression::Decrypt(e) => self.generate_decrypt(e),
3421            Expression::DecryptRaw(e) => self.generate_decrypt_raw(e),
3422            Expression::DefinerProperty(e) => self.generate_definer_property(e),
3423            Expression::Detach(e) => self.generate_detach(e),
3424            Expression::DictProperty(e) => self.generate_dict_property(e),
3425            Expression::DictRange(e) => self.generate_dict_range(e),
3426            Expression::Directory(e) => self.generate_directory(e),
3427            Expression::DistKeyProperty(e) => self.generate_dist_key_property(e),
3428            Expression::DistStyleProperty(e) => self.generate_dist_style_property(e),
3429            Expression::DistributeBy(e) => self.generate_distribute_by(e),
3430            Expression::DistributedByProperty(e) => self.generate_distributed_by_property(e),
3431            Expression::DotProduct(e) => self.generate_dot_product(e),
3432            Expression::DropPartition(e) => self.generate_drop_partition(e),
3433            Expression::DuplicateKeyProperty(e) => self.generate_duplicate_key_property(e),
3434            Expression::Elt(e) => self.generate_elt(e),
3435            Expression::Encode(e) => self.generate_encode(e),
3436            Expression::EncodeProperty(e) => self.generate_encode_property(e),
3437            Expression::Encrypt(e) => self.generate_encrypt(e),
3438            Expression::EncryptRaw(e) => self.generate_encrypt_raw(e),
3439            Expression::EngineProperty(e) => self.generate_engine_property(e),
3440            Expression::EnviromentProperty(e) => self.generate_enviroment_property(e),
3441            Expression::EphemeralColumnConstraint(e) => {
3442                self.generate_ephemeral_column_constraint(e)
3443            }
3444            Expression::EqualNull(e) => self.generate_equal_null(e),
3445            Expression::EuclideanDistance(e) => self.generate_euclidean_distance(e),
3446            Expression::ExecuteAsProperty(e) => self.generate_execute_as_property(e),
3447            Expression::Export(e) => self.generate_export(e),
3448            Expression::ExternalProperty(e) => self.generate_external_property(e),
3449            Expression::FallbackProperty(e) => self.generate_fallback_property(e),
3450            Expression::FarmFingerprint(e) => self.generate_farm_fingerprint(e),
3451            Expression::FeaturesAtTime(e) => self.generate_features_at_time(e),
3452            Expression::Fetch(e) => self.generate_fetch(e),
3453            Expression::FileFormatProperty(e) => self.generate_file_format_property(e),
3454            Expression::Filter(e) => self.generate_filter(e),
3455            Expression::Float64(e) => self.generate_float64(e),
3456            Expression::ForIn(e) => self.generate_for_in(e),
3457            Expression::ForeignKey(e) => self.generate_foreign_key(e),
3458            Expression::Format(e) => self.generate_format(e),
3459            Expression::FormatPhrase(e) => self.generate_format_phrase(e),
3460            Expression::FreespaceProperty(e) => self.generate_freespace_property(e),
3461            Expression::From(e) => self.generate_from(e),
3462            Expression::FromBase(e) => self.generate_from_base(e),
3463            Expression::FromTimeZone(e) => self.generate_from_time_zone(e),
3464            Expression::GapFill(e) => self.generate_gap_fill(e),
3465            Expression::GenerateDateArray(e) => self.generate_generate_date_array(e),
3466            Expression::GenerateEmbedding(e) => self.generate_generate_embedding(e),
3467            Expression::GenerateSeries(e) => self.generate_generate_series(e),
3468            Expression::GenerateTimestampArray(e) => self.generate_generate_timestamp_array(e),
3469            Expression::GeneratedAsIdentityColumnConstraint(e) => {
3470                self.generate_generated_as_identity_column_constraint(e)
3471            }
3472            Expression::GeneratedAsRowColumnConstraint(e) => {
3473                self.generate_generated_as_row_column_constraint(e)
3474            }
3475            Expression::Get(e) => self.generate_get(e),
3476            Expression::GetExtract(e) => self.generate_get_extract(e),
3477            Expression::Getbit(e) => self.generate_getbit(e),
3478            Expression::GrantPrincipal(e) => self.generate_grant_principal(e),
3479            Expression::GrantPrivilege(e) => self.generate_grant_privilege(e),
3480            Expression::Group(e) => self.generate_group(e),
3481            Expression::GroupBy(e) => self.generate_group_by(e),
3482            Expression::Grouping(e) => self.generate_grouping(e),
3483            Expression::GroupingId(e) => self.generate_grouping_id(e),
3484            Expression::GroupingSets(e) => self.generate_grouping_sets(e),
3485            Expression::HashAgg(e) => self.generate_hash_agg(e),
3486            Expression::Having(e) => self.generate_having(e),
3487            Expression::HavingMax(e) => self.generate_having_max(e),
3488            Expression::Heredoc(e) => self.generate_heredoc(e),
3489            Expression::HexEncode(e) => self.generate_hex_encode(e),
3490            Expression::Hll(e) => self.generate_hll(e),
3491            Expression::InOutColumnConstraint(e) => self.generate_in_out_column_constraint(e),
3492            Expression::IncludeProperty(e) => self.generate_include_property(e),
3493            Expression::Index(e) => self.generate_index(e),
3494            Expression::IndexColumnConstraint(e) => self.generate_index_column_constraint(e),
3495            Expression::IndexConstraintOption(e) => self.generate_index_constraint_option(e),
3496            Expression::IndexParameters(e) => self.generate_index_parameters(e),
3497            Expression::IndexTableHint(e) => self.generate_index_table_hint(e),
3498            Expression::InheritsProperty(e) => self.generate_inherits_property(e),
3499            Expression::InputModelProperty(e) => self.generate_input_model_property(e),
3500            Expression::InputOutputFormat(e) => self.generate_input_output_format(e),
3501            Expression::Install(e) => self.generate_install(e),
3502            Expression::IntervalOp(e) => self.generate_interval_op(e),
3503            Expression::IntervalSpan(e) => self.generate_interval_span(e),
3504            Expression::IntoClause(e) => self.generate_into_clause(e),
3505            Expression::Introducer(e) => self.generate_introducer(e),
3506            Expression::IsolatedLoadingProperty(e) => self.generate_isolated_loading_property(e),
3507            Expression::JSON(e) => self.generate_json(e),
3508            Expression::JSONArray(e) => self.generate_json_array(e),
3509            Expression::JSONArrayAgg(e) => self.generate_json_array_agg_struct(e),
3510            Expression::JSONArrayAppend(e) => self.generate_json_array_append(e),
3511            Expression::JSONArrayContains(e) => self.generate_json_array_contains(e),
3512            Expression::JSONArrayInsert(e) => self.generate_json_array_insert(e),
3513            Expression::JSONBExists(e) => self.generate_jsonb_exists(e),
3514            Expression::JSONBExtractScalar(e) => self.generate_jsonb_extract_scalar(e),
3515            Expression::JSONBObjectAgg(e) => self.generate_jsonb_object_agg(e),
3516            Expression::JSONObjectAgg(e) => self.generate_json_object_agg_struct(e),
3517            Expression::JSONColumnDef(e) => self.generate_json_column_def(e),
3518            Expression::JSONExists(e) => self.generate_json_exists(e),
3519            Expression::JSONCast(e) => self.generate_json_cast(e),
3520            Expression::JSONExtract(e) => self.generate_json_extract_path(e),
3521            Expression::JSONExtractArray(e) => self.generate_json_extract_array(e),
3522            Expression::JSONExtractQuote(e) => self.generate_json_extract_quote(e),
3523            Expression::JSONExtractScalar(e) => self.generate_json_extract_scalar(e),
3524            Expression::JSONFormat(e) => self.generate_json_format(e),
3525            Expression::JSONKeyValue(e) => self.generate_json_key_value(e),
3526            Expression::JSONKeys(e) => self.generate_json_keys(e),
3527            Expression::JSONKeysAtDepth(e) => self.generate_json_keys_at_depth(e),
3528            Expression::JSONPath(e) => self.generate_json_path_expr(e),
3529            Expression::JSONPathFilter(e) => self.generate_json_path_filter(e),
3530            Expression::JSONPathKey(e) => self.generate_json_path_key(e),
3531            Expression::JSONPathRecursive(e) => self.generate_json_path_recursive(e),
3532            Expression::JSONPathRoot(_) => self.generate_json_path_root(),
3533            Expression::JSONPathScript(e) => self.generate_json_path_script(e),
3534            Expression::JSONPathSelector(e) => self.generate_json_path_selector(e),
3535            Expression::JSONPathSlice(e) => self.generate_json_path_slice(e),
3536            Expression::JSONPathSubscript(e) => self.generate_json_path_subscript(e),
3537            Expression::JSONPathUnion(e) => self.generate_json_path_union(e),
3538            Expression::JSONRemove(e) => self.generate_json_remove(e),
3539            Expression::JSONSchema(e) => self.generate_json_schema(e),
3540            Expression::JSONSet(e) => self.generate_json_set(e),
3541            Expression::JSONStripNulls(e) => self.generate_json_strip_nulls(e),
3542            Expression::JSONTable(e) => self.generate_json_table(e),
3543            Expression::JSONType(e) => self.generate_json_type(e),
3544            Expression::JSONValue(e) => self.generate_json_value(e),
3545            Expression::JSONValueArray(e) => self.generate_json_value_array(e),
3546            Expression::JarowinklerSimilarity(e) => self.generate_jarowinkler_similarity(e),
3547            Expression::JoinHint(e) => self.generate_join_hint(e),
3548            Expression::JournalProperty(e) => self.generate_journal_property(e),
3549            Expression::LanguageProperty(e) => self.generate_language_property(e),
3550            Expression::Lateral(e) => self.generate_lateral(e),
3551            Expression::LikeProperty(e) => self.generate_like_property(e),
3552            Expression::Limit(e) => self.generate_limit(e),
3553            Expression::LimitOptions(e) => self.generate_limit_options(e),
3554            Expression::List(e) => self.generate_list(e),
3555            Expression::ToMap(e) => self.generate_tomap(e),
3556            Expression::Localtime(e) => self.generate_localtime(e),
3557            Expression::Localtimestamp(e) => self.generate_localtimestamp(e),
3558            Expression::LocationProperty(e) => self.generate_location_property(e),
3559            Expression::Lock(e) => self.generate_lock(e),
3560            Expression::LockProperty(e) => self.generate_lock_property(e),
3561            Expression::LockingProperty(e) => self.generate_locking_property(e),
3562            Expression::LockingStatement(e) => self.generate_locking_statement(e),
3563            Expression::LogProperty(e) => self.generate_log_property(e),
3564            Expression::MD5Digest(e) => self.generate_md5_digest(e),
3565            Expression::MLForecast(e) => self.generate_ml_forecast(e),
3566            Expression::MLTranslate(e) => self.generate_ml_translate(e),
3567            Expression::MakeInterval(e) => self.generate_make_interval(e),
3568            Expression::ManhattanDistance(e) => self.generate_manhattan_distance(e),
3569            Expression::Map(e) => self.generate_map(e),
3570            Expression::MapCat(e) => self.generate_map_cat(e),
3571            Expression::MapDelete(e) => self.generate_map_delete(e),
3572            Expression::MapInsert(e) => self.generate_map_insert(e),
3573            Expression::MapPick(e) => self.generate_map_pick(e),
3574            Expression::MaskingPolicyColumnConstraint(e) => {
3575                self.generate_masking_policy_column_constraint(e)
3576            }
3577            Expression::MatchAgainst(e) => self.generate_match_against(e),
3578            Expression::MatchRecognizeMeasure(e) => self.generate_match_recognize_measure(e),
3579            Expression::MaterializedProperty(e) => self.generate_materialized_property(e),
3580            Expression::Merge(e) => self.generate_merge(e),
3581            Expression::MergeBlockRatioProperty(e) => self.generate_merge_block_ratio_property(e),
3582            Expression::MergeTreeTTL(e) => self.generate_merge_tree_ttl(e),
3583            Expression::MergeTreeTTLAction(e) => self.generate_merge_tree_ttl_action(e),
3584            Expression::Minhash(e) => self.generate_minhash(e),
3585            Expression::ModelAttribute(e) => self.generate_model_attribute(e),
3586            Expression::Monthname(e) => self.generate_monthname(e),
3587            Expression::MultitableInserts(e) => self.generate_multitable_inserts(e),
3588            Expression::NextValueFor(e) => self.generate_next_value_for(e),
3589            Expression::Normal(e) => self.generate_normal(e),
3590            Expression::Normalize(e) => self.generate_normalize(e),
3591            Expression::NotNullColumnConstraint(e) => self.generate_not_null_column_constraint(e),
3592            Expression::Nullif(e) => self.generate_nullif(e),
3593            Expression::NumberToStr(e) => self.generate_number_to_str(e),
3594            Expression::ObjectAgg(e) => self.generate_object_agg(e),
3595            Expression::ObjectIdentifier(e) => self.generate_object_identifier(e),
3596            Expression::ObjectInsert(e) => self.generate_object_insert(e),
3597            Expression::Offset(e) => self.generate_offset(e),
3598            Expression::Qualify(e) => self.generate_qualify(e),
3599            Expression::OnCluster(e) => self.generate_on_cluster(e),
3600            Expression::OnCommitProperty(e) => self.generate_on_commit_property(e),
3601            Expression::OnCondition(e) => self.generate_on_condition(e),
3602            Expression::OnConflict(e) => self.generate_on_conflict(e),
3603            Expression::OnProperty(e) => self.generate_on_property(e),
3604            Expression::Opclass(e) => self.generate_opclass(e),
3605            Expression::OpenJSON(e) => self.generate_open_json(e),
3606            Expression::OpenJSONColumnDef(e) => self.generate_open_json_column_def(e),
3607            Expression::Operator(e) => self.generate_operator(e),
3608            Expression::OrderBy(e) => self.generate_order_by(e),
3609            Expression::OutputModelProperty(e) => self.generate_output_model_property(e),
3610            Expression::OverflowTruncateBehavior(e) => self.generate_overflow_truncate_behavior(e),
3611            Expression::ParameterizedAgg(e) => self.generate_parameterized_agg(e),
3612            Expression::ParseDatetime(e) => self.generate_parse_datetime(e),
3613            Expression::ParseIp(e) => self.generate_parse_ip(e),
3614            Expression::ParseJSON(e) => self.generate_parse_json(e),
3615            Expression::ParseTime(e) => self.generate_parse_time(e),
3616            Expression::ParseUrl(e) => self.generate_parse_url(e),
3617            Expression::Partition(e) => self.generate_partition_expr(e),
3618            Expression::PartitionBoundSpec(e) => self.generate_partition_bound_spec(e),
3619            Expression::PartitionByListProperty(e) => self.generate_partition_by_list_property(e),
3620            Expression::PartitionByRangeProperty(e) => self.generate_partition_by_range_property(e),
3621            Expression::PartitionByRangePropertyDynamic(e) => {
3622                self.generate_partition_by_range_property_dynamic(e)
3623            }
3624            Expression::PartitionByTruncate(e) => self.generate_partition_by_truncate(e),
3625            Expression::PartitionList(e) => self.generate_partition_list(e),
3626            Expression::PartitionRange(e) => self.generate_partition_range(e),
3627            Expression::PartitionedByBucket(e) => self.generate_partitioned_by_bucket(e),
3628            Expression::PartitionedByProperty(e) => self.generate_partitioned_by_property(e),
3629            Expression::PartitionedOfProperty(e) => self.generate_partitioned_of_property(e),
3630            Expression::PeriodForSystemTimeConstraint(e) => {
3631                self.generate_period_for_system_time_constraint(e)
3632            }
3633            Expression::PivotAlias(e) => self.generate_pivot_alias(e),
3634            Expression::PivotAny(e) => self.generate_pivot_any(e),
3635            Expression::Predict(e) => self.generate_predict(e),
3636            Expression::PreviousDay(e) => self.generate_previous_day(e),
3637            Expression::PrimaryKey(e) => self.generate_primary_key(e),
3638            Expression::PrimaryKeyColumnConstraint(e) => {
3639                self.generate_primary_key_column_constraint(e)
3640            }
3641            Expression::PathColumnConstraint(e) => self.generate_path_column_constraint(e),
3642            Expression::ProjectionDef(e) => self.generate_projection_def(e),
3643            Expression::Properties(e) => self.generate_properties(e),
3644            Expression::Property(e) => self.generate_property(e),
3645            Expression::PseudoType(e) => self.generate_pseudo_type(e),
3646            Expression::Put(e) => self.generate_put(e),
3647            Expression::Quantile(e) => self.generate_quantile(e),
3648            Expression::QueryBand(e) => self.generate_query_band(e),
3649            Expression::QueryOption(e) => self.generate_query_option(e),
3650            Expression::QueryTransform(e) => self.generate_query_transform(e),
3651            Expression::Randn(e) => self.generate_randn(e),
3652            Expression::Randstr(e) => self.generate_randstr(e),
3653            Expression::RangeBucket(e) => self.generate_range_bucket(e),
3654            Expression::RangeN(e) => self.generate_range_n(e),
3655            Expression::ReadCSV(e) => self.generate_read_csv(e),
3656            Expression::ReadParquet(e) => self.generate_read_parquet(e),
3657            Expression::RecursiveWithSearch(e) => self.generate_recursive_with_search(e),
3658            Expression::Reduce(e) => self.generate_reduce(e),
3659            Expression::Reference(e) => self.generate_reference(e),
3660            Expression::Refresh(e) => self.generate_refresh(e),
3661            Expression::RefreshTriggerProperty(e) => self.generate_refresh_trigger_property(e),
3662            Expression::RegexpCount(e) => self.generate_regexp_count(e),
3663            Expression::RegexpExtractAll(e) => self.generate_regexp_extract_all(e),
3664            Expression::RegexpFullMatch(e) => self.generate_regexp_full_match(e),
3665            Expression::RegexpILike(e) => self.generate_regexp_i_like(e),
3666            Expression::RegexpInstr(e) => self.generate_regexp_instr(e),
3667            Expression::RegexpSplit(e) => self.generate_regexp_split(e),
3668            Expression::RegrAvgx(e) => self.generate_regr_avgx(e),
3669            Expression::RegrAvgy(e) => self.generate_regr_avgy(e),
3670            Expression::RegrCount(e) => self.generate_regr_count(e),
3671            Expression::RegrIntercept(e) => self.generate_regr_intercept(e),
3672            Expression::RegrR2(e) => self.generate_regr_r2(e),
3673            Expression::RegrSlope(e) => self.generate_regr_slope(e),
3674            Expression::RegrSxx(e) => self.generate_regr_sxx(e),
3675            Expression::RegrSxy(e) => self.generate_regr_sxy(e),
3676            Expression::RegrSyy(e) => self.generate_regr_syy(e),
3677            Expression::RegrValx(e) => self.generate_regr_valx(e),
3678            Expression::RegrValy(e) => self.generate_regr_valy(e),
3679            Expression::RemoteWithConnectionModelProperty(e) => {
3680                self.generate_remote_with_connection_model_property(e)
3681            }
3682            Expression::RenameColumn(e) => self.generate_rename_column(e),
3683            Expression::ReplacePartition(e) => self.generate_replace_partition(e),
3684            Expression::Returning(e) => self.generate_returning(e),
3685            Expression::ReturnsProperty(e) => self.generate_returns_property(e),
3686            Expression::Rollback(e) => self.generate_rollback(e),
3687            Expression::Rollup(e) => self.generate_rollup(e),
3688            Expression::RowFormatDelimitedProperty(e) => {
3689                self.generate_row_format_delimited_property(e)
3690            }
3691            Expression::RowFormatProperty(e) => self.generate_row_format_property(e),
3692            Expression::RowFormatSerdeProperty(e) => self.generate_row_format_serde_property(e),
3693            Expression::SHA2(e) => self.generate_sha2(e),
3694            Expression::SHA2Digest(e) => self.generate_sha2_digest(e),
3695            Expression::SafeAdd(e) => self.generate_safe_add(e),
3696            Expression::SafeDivide(e) => self.generate_safe_divide(e),
3697            Expression::SafeMultiply(e) => self.generate_safe_multiply(e),
3698            Expression::SafeSubtract(e) => self.generate_safe_subtract(e),
3699            Expression::SampleProperty(e) => self.generate_sample_property(e),
3700            Expression::Schema(e) => self.generate_schema(e),
3701            Expression::SchemaCommentProperty(e) => self.generate_schema_comment_property(e),
3702            Expression::ScopeResolution(e) => self.generate_scope_resolution(e),
3703            Expression::Search(e) => self.generate_search(e),
3704            Expression::SearchIp(e) => self.generate_search_ip(e),
3705            Expression::SecurityProperty(e) => self.generate_security_property(e),
3706            Expression::SemanticView(e) => self.generate_semantic_view(e),
3707            Expression::SequenceProperties(e) => self.generate_sequence_properties(e),
3708            Expression::SerdeProperties(e) => self.generate_serde_properties(e),
3709            Expression::SessionParameter(e) => self.generate_session_parameter(e),
3710            Expression::Set(e) => self.generate_set(e),
3711            Expression::SetConfigProperty(e) => self.generate_set_config_property(e),
3712            Expression::SetItem(e) => self.generate_set_item(e),
3713            Expression::SetOperation(e) => self.generate_set_operation(e),
3714            Expression::SetProperty(e) => self.generate_set_property(e),
3715            Expression::SettingsProperty(e) => self.generate_settings_property(e),
3716            Expression::SharingProperty(e) => self.generate_sharing_property(e),
3717            Expression::Slice(e) => self.generate_slice(e),
3718            Expression::SortArray(e) => self.generate_sort_array(e),
3719            Expression::SortBy(e) => self.generate_sort_by(e),
3720            Expression::SortKeyProperty(e) => self.generate_sort_key_property(e),
3721            Expression::SplitPart(e) => self.generate_split_part(e),
3722            Expression::SqlReadWriteProperty(e) => self.generate_sql_read_write_property(e),
3723            Expression::SqlSecurityProperty(e) => self.generate_sql_security_property(e),
3724            Expression::StDistance(e) => self.generate_st_distance(e),
3725            Expression::StPoint(e) => self.generate_st_point(e),
3726            Expression::StabilityProperty(e) => self.generate_stability_property(e),
3727            Expression::StandardHash(e) => self.generate_standard_hash(e),
3728            Expression::StorageHandlerProperty(e) => self.generate_storage_handler_property(e),
3729            Expression::StrPosition(e) => self.generate_str_position(e),
3730            Expression::StrToDate(e) => self.generate_str_to_date(e),
3731            Expression::DateStrToDate(f) => self.generate_simple_func("DATE_STR_TO_DATE", &f.this),
3732            Expression::DateToDateStr(f) => self.generate_simple_func("DATE_TO_DATE_STR", &f.this),
3733            Expression::StrToMap(e) => self.generate_str_to_map(e),
3734            Expression::StrToTime(e) => self.generate_str_to_time(e),
3735            Expression::StrToUnix(e) => self.generate_str_to_unix(e),
3736            Expression::StringToArray(e) => self.generate_string_to_array(e),
3737            Expression::Struct(e) => self.generate_struct(e),
3738            Expression::Stuff(e) => self.generate_stuff(e),
3739            Expression::SubstringIndex(e) => self.generate_substring_index(e),
3740            Expression::Summarize(e) => self.generate_summarize(e),
3741            Expression::Systimestamp(e) => self.generate_systimestamp(e),
3742            Expression::TableAlias(e) => self.generate_table_alias(e),
3743            Expression::TableFromRows(e) => self.generate_table_from_rows(e),
3744            Expression::RowsFrom(e) => self.generate_rows_from(e),
3745            Expression::TableSample(e) => self.generate_table_sample(e),
3746            Expression::Tag(e) => self.generate_tag(e),
3747            Expression::Tags(e) => self.generate_tags(e),
3748            Expression::TemporaryProperty(e) => self.generate_temporary_property(e),
3749            Expression::Time(e) => self.generate_time_func(e),
3750            Expression::TimeAdd(e) => self.generate_time_add(e),
3751            Expression::TimeDiff(e) => self.generate_time_diff(e),
3752            Expression::TimeFromParts(e) => self.generate_time_from_parts(e),
3753            Expression::TimeSlice(e) => self.generate_time_slice(e),
3754            Expression::TimeStrToDate(e) => self.generate_time_str_to_date(e),
3755            Expression::TimeStrToTime(e) => self.generate_time_str_to_time(e),
3756            Expression::TimeSub(e) => self.generate_time_sub(e),
3757            Expression::TimeToStr(e) => self.generate_time_to_str(e),
3758            Expression::TimeToUnix(e) => self.generate_time_to_unix(e),
3759            Expression::TimeTrunc(e) => self.generate_time_trunc(e),
3760            Expression::TimeUnit(e) => self.generate_time_unit(e),
3761            Expression::Timestamp(e) => self.generate_timestamp_func(e),
3762            Expression::TimestampAdd(e) => self.generate_timestamp_add(e),
3763            Expression::TimestampDiff(e) => self.generate_timestamp_diff(e),
3764            Expression::TimestampFromParts(e) => self.generate_timestamp_from_parts(e),
3765            Expression::TimestampSub(e) => self.generate_timestamp_sub(e),
3766            Expression::TimestampTzFromParts(e) => self.generate_timestamp_tz_from_parts(e),
3767            Expression::ToBinary(e) => self.generate_to_binary(e),
3768            Expression::ToBoolean(e) => self.generate_to_boolean(e),
3769            Expression::ToChar(e) => self.generate_to_char(e),
3770            Expression::ToDecfloat(e) => self.generate_to_decfloat(e),
3771            Expression::ToDouble(e) => self.generate_to_double(e),
3772            Expression::ToFile(e) => self.generate_to_file(e),
3773            Expression::ToNumber(e) => self.generate_to_number(e),
3774            Expression::ToTableProperty(e) => self.generate_to_table_property(e),
3775            Expression::Transaction(e) => self.generate_transaction(e),
3776            Expression::Transform(e) => self.generate_transform(e),
3777            Expression::TransformModelProperty(e) => self.generate_transform_model_property(e),
3778            Expression::TransientProperty(e) => self.generate_transient_property(e),
3779            Expression::Translate(e) => self.generate_translate(e),
3780            Expression::TranslateCharacters(e) => self.generate_translate_characters(e),
3781            Expression::TruncateTable(e) => self.generate_truncate_table(e),
3782            Expression::TryBase64DecodeBinary(e) => self.generate_try_base64_decode_binary(e),
3783            Expression::TryBase64DecodeString(e) => self.generate_try_base64_decode_string(e),
3784            Expression::TryToDecfloat(e) => self.generate_try_to_decfloat(e),
3785            Expression::TsOrDsAdd(e) => self.generate_ts_or_ds_add(e),
3786            Expression::TsOrDsDiff(e) => self.generate_ts_or_ds_diff(e),
3787            Expression::TsOrDsToDate(e) => self.generate_ts_or_ds_to_date(e),
3788            Expression::TsOrDsToTime(e) => self.generate_ts_or_ds_to_time(e),
3789            Expression::Unhex(e) => self.generate_unhex(e),
3790            Expression::UnicodeString(e) => self.generate_unicode_string(e),
3791            Expression::Uniform(e) => self.generate_uniform(e),
3792            Expression::UniqueColumnConstraint(e) => self.generate_unique_column_constraint(e),
3793            Expression::UniqueKeyProperty(e) => self.generate_unique_key_property(e),
3794            Expression::RollupProperty(e) => self.generate_rollup_property(e),
3795            Expression::UnixToStr(e) => self.generate_unix_to_str(e),
3796            Expression::UnixToTime(e) => self.generate_unix_to_time(e),
3797            Expression::UnpivotColumns(e) => self.generate_unpivot_columns(e),
3798            Expression::UserDefinedFunction(e) => self.generate_user_defined_function(e),
3799            Expression::UsingTemplateProperty(e) => self.generate_using_template_property(e),
3800            Expression::UtcTime(e) => self.generate_utc_time(e),
3801            Expression::UtcTimestamp(e) => self.generate_utc_timestamp(e),
3802            Expression::Uuid(e) => self.generate_uuid(e),
3803            Expression::Var(v) => {
3804                if matches!(self.config.dialect, Some(DialectType::MySQL))
3805                    && v.this.len() > 2
3806                    && (v.this.starts_with("0x") || v.this.starts_with("0X"))
3807                    && !v.this[2..].chars().all(|c| c.is_ascii_hexdigit())
3808                {
3809                    return self.generate_identifier(&Identifier {
3810                        name: v.this.clone(),
3811                        quoted: true,
3812                        trailing_comments: Vec::new(),
3813                        span: None,
3814                    });
3815                }
3816                self.write(&v.this);
3817                Ok(())
3818            }
3819            Expression::Variadic(e) => {
3820                self.write_keyword("VARIADIC");
3821                self.write_space();
3822                self.generate_expression(&e.this)?;
3823                Ok(())
3824            }
3825            Expression::VarMap(e) => self.generate_var_map(e),
3826            Expression::VectorSearch(e) => self.generate_vector_search(e),
3827            Expression::Version(e) => self.generate_version(e),
3828            Expression::ViewAttributeProperty(e) => self.generate_view_attribute_property(e),
3829            Expression::VolatileProperty(e) => self.generate_volatile_property(e),
3830            Expression::WatermarkColumnConstraint(e) => {
3831                self.generate_watermark_column_constraint(e)
3832            }
3833            Expression::Week(e) => self.generate_week(e),
3834            Expression::When(e) => self.generate_when(e),
3835            Expression::Whens(e) => self.generate_whens(e),
3836            Expression::Where(e) => self.generate_where(e),
3837            Expression::WidthBucket(e) => self.generate_width_bucket(e),
3838            Expression::Window(e) => self.generate_window(e),
3839            Expression::WindowSpec(e) => self.generate_window_spec(e),
3840            Expression::WithDataProperty(e) => self.generate_with_data_property(e),
3841            Expression::WithFill(e) => self.generate_with_fill(e),
3842            Expression::WithJournalTableProperty(e) => self.generate_with_journal_table_property(e),
3843            Expression::WithOperator(e) => self.generate_with_operator(e),
3844            Expression::WithProcedureOptions(e) => self.generate_with_procedure_options(e),
3845            Expression::WithSchemaBindingProperty(e) => {
3846                self.generate_with_schema_binding_property(e)
3847            }
3848            Expression::WithSystemVersioningProperty(e) => {
3849                self.generate_with_system_versioning_property(e)
3850            }
3851            Expression::WithTableHint(e) => self.generate_with_table_hint(e),
3852            Expression::XMLElement(e) => self.generate_xml_element(e),
3853            Expression::XMLGet(e) => self.generate_xml_get(e),
3854            Expression::XMLKeyValueOption(e) => self.generate_xml_key_value_option(e),
3855            Expression::XMLTable(e) => self.generate_xml_table(e),
3856            Expression::Xor(e) => self.generate_xor(e),
3857            Expression::Zipf(e) => self.generate_zipf(e),
3858            _ => {
3859                // Fallback for unimplemented expressions
3860                self.write(&format!("/* unimplemented: {:?} */", expr));
3861                Ok(())
3862            }
3863        }
3864    }
3865
3866    fn generate_select(&mut self, select: &Select) -> Result<()> {
3867        use crate::dialects::DialectType;
3868
3869        // Output leading comments before SELECT
3870        for comment in &select.leading_comments {
3871            self.write_formatted_comment(comment);
3872            self.write(" ");
3873        }
3874
3875        // WITH clause
3876        if let Some(with) = &select.with {
3877            self.generate_with(with)?;
3878            if self.config.pretty {
3879                self.write_newline();
3880                self.write_indent();
3881            } else {
3882                self.write_space();
3883            }
3884        }
3885
3886        // Output post-SELECT comments (comments that appeared after SELECT keyword)
3887        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
3888        for comment in &select.post_select_comments {
3889            self.write_formatted_comment(comment);
3890            self.write(" ");
3891        }
3892
3893        self.write_keyword("SELECT");
3894
3895        // Generate query hint if present /*+ ... */
3896        if let Some(hint) = &select.hint {
3897            self.generate_hint(hint)?;
3898        }
3899
3900        // For SQL Server, convert LIMIT to TOP (structural transformation)
3901        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
3902        // TOP clause (SQL Server style - before DISTINCT)
3903        let use_top_from_limit = matches!(self.config.dialect, Some(DialectType::TSQL))
3904            && select.top.is_none()
3905            && select.limit.is_some()
3906            && select.offset.is_none(); // Don't use TOP when there's OFFSET
3907
3908        // For TOP-supporting dialects: DISTINCT before TOP
3909        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
3910        let is_top_dialect = matches!(
3911            self.config.dialect,
3912            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
3913        );
3914        let keep_top_verbatim = !is_top_dialect
3915            && select.limit.is_none()
3916            && select
3917                .top
3918                .as_ref()
3919                .map_or(false, |top| top.percent || top.with_ties);
3920
3921        if select.distinct && (is_top_dialect || select.top.is_some()) {
3922            self.write_space();
3923            self.write_keyword("DISTINCT");
3924        }
3925
3926        if is_top_dialect || keep_top_verbatim {
3927            if let Some(top) = &select.top {
3928                self.write_space();
3929                self.write_keyword("TOP");
3930                if top.parenthesized {
3931                    self.write(" (");
3932                    self.generate_expression(&top.this)?;
3933                    self.write(")");
3934                } else {
3935                    self.write_space();
3936                    self.generate_expression(&top.this)?;
3937                }
3938                if top.percent {
3939                    self.write_space();
3940                    self.write_keyword("PERCENT");
3941                }
3942                if top.with_ties {
3943                    self.write_space();
3944                    self.write_keyword("WITH TIES");
3945                }
3946            } else if use_top_from_limit {
3947                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
3948                if let Some(limit) = &select.limit {
3949                    self.write_space();
3950                    self.write_keyword("TOP");
3951                    // Use parentheses for complex expressions, but not for simple literals
3952                    let is_simple_literal =
3953                        matches!(&limit.this, Expression::Literal(Literal::Number(_)));
3954                    if is_simple_literal {
3955                        self.write_space();
3956                        self.generate_expression(&limit.this)?;
3957                    } else {
3958                        self.write(" (");
3959                        self.generate_expression(&limit.this)?;
3960                        self.write(")");
3961                    }
3962                }
3963            }
3964        }
3965
3966        if select.distinct && !is_top_dialect && select.top.is_none() {
3967            self.write_space();
3968            self.write_keyword("DISTINCT");
3969        }
3970
3971        // DISTINCT ON clause (PostgreSQL)
3972        if let Some(distinct_on) = &select.distinct_on {
3973            self.write_space();
3974            self.write_keyword("ON");
3975            self.write(" (");
3976            for (i, expr) in distinct_on.iter().enumerate() {
3977                if i > 0 {
3978                    self.write(", ");
3979                }
3980                self.generate_expression(expr)?;
3981            }
3982            self.write(")");
3983        }
3984
3985        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
3986        for modifier in &select.operation_modifiers {
3987            self.write_space();
3988            self.write_keyword(modifier);
3989        }
3990
3991        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
3992        if let Some(kind) = &select.kind {
3993            self.write_space();
3994            self.write_keyword("AS");
3995            self.write_space();
3996            self.write_keyword(kind);
3997        }
3998
3999        // Expressions (only if there are any)
4000        if !select.expressions.is_empty() {
4001            if self.config.pretty {
4002                self.write_newline();
4003                self.indent_level += 1;
4004            } else {
4005                self.write_space();
4006            }
4007        }
4008
4009        for (i, expr) in select.expressions.iter().enumerate() {
4010            if i > 0 {
4011                self.write(",");
4012                if self.config.pretty {
4013                    self.write_newline();
4014                } else {
4015                    self.write_space();
4016                }
4017            }
4018            if self.config.pretty {
4019                self.write_indent();
4020            }
4021            self.generate_expression(expr)?;
4022        }
4023
4024        if self.config.pretty && !select.expressions.is_empty() {
4025            self.indent_level -= 1;
4026        }
4027
4028        // INTO clause (SELECT ... INTO table_name)
4029        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4030        if let Some(into) = &select.into {
4031            if self.config.pretty {
4032                self.write_newline();
4033                self.write_indent();
4034            } else {
4035                self.write_space();
4036            }
4037            if into.bulk_collect {
4038                self.write_keyword("BULK COLLECT INTO");
4039            } else {
4040                self.write_keyword("INTO");
4041            }
4042            if into.temporary {
4043                self.write_space();
4044                self.write_keyword("TEMPORARY");
4045            }
4046            if into.unlogged {
4047                self.write_space();
4048                self.write_keyword("UNLOGGED");
4049            }
4050            self.write_space();
4051            // If we have multiple expressions, output them comma-separated
4052            if !into.expressions.is_empty() {
4053                for (i, expr) in into.expressions.iter().enumerate() {
4054                    if i > 0 {
4055                        self.write(", ");
4056                    }
4057                    self.generate_expression(expr)?;
4058                }
4059            } else {
4060                self.generate_expression(&into.this)?;
4061            }
4062        }
4063
4064        // FROM clause
4065        if let Some(from) = &select.from {
4066            if self.config.pretty {
4067                self.write_newline();
4068                self.write_indent();
4069            } else {
4070                self.write_space();
4071            }
4072            self.write_keyword("FROM");
4073            self.write_space();
4074
4075            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4076            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4077            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4078            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4079            let has_tablesample = from
4080                .expressions
4081                .iter()
4082                .any(|e| matches!(e, Expression::TableSample(_)));
4083            let is_cross_join_dialect = matches!(
4084                self.config.dialect,
4085                Some(DialectType::BigQuery)
4086                    | Some(DialectType::Hive)
4087                    | Some(DialectType::Spark)
4088                    | Some(DialectType::Databricks)
4089                    | Some(DialectType::SQLite)
4090                    | Some(DialectType::ClickHouse)
4091            );
4092            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4093            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4094            let source_is_same_as_target = self.config.source_dialect.is_some()
4095                && self.config.source_dialect == self.config.dialect;
4096            let source_is_cross_join_dialect = matches!(
4097                self.config.source_dialect,
4098                Some(DialectType::BigQuery)
4099                    | Some(DialectType::Hive)
4100                    | Some(DialectType::Spark)
4101                    | Some(DialectType::Databricks)
4102                    | Some(DialectType::SQLite)
4103                    | Some(DialectType::ClickHouse)
4104            );
4105            let use_cross_join = !has_tablesample
4106                && is_cross_join_dialect
4107                && (source_is_same_as_target
4108                    || source_is_cross_join_dialect
4109                    || self.config.source_dialect.is_none());
4110
4111            // Snowflake wraps standalone VALUES in FROM clause with parentheses
4112            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
4113
4114            for (i, expr) in from.expressions.iter().enumerate() {
4115                if i > 0 {
4116                    if use_cross_join {
4117                        self.write(" CROSS JOIN ");
4118                    } else {
4119                        self.write(", ");
4120                    }
4121                }
4122                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
4123                    self.write("(");
4124                    self.generate_expression(expr)?;
4125                    self.write(")");
4126                } else {
4127                    self.generate_expression(expr)?;
4128                }
4129            }
4130        }
4131
4132        // JOINs - handle nested join structure for pretty printing
4133        // Deferred-condition joins "own" the non-deferred joins that follow them
4134        // until the next deferred join or end of list
4135        if self.config.pretty {
4136            self.generate_joins_with_nesting(&select.joins)?;
4137        } else {
4138            for join in &select.joins {
4139                self.generate_join(join)?;
4140            }
4141            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4142            for join in select.joins.iter().rev() {
4143                if join.deferred_condition {
4144                    self.generate_join_condition(join)?;
4145                }
4146            }
4147        }
4148
4149        // LATERAL VIEW clauses (Hive/Spark)
4150        for lateral_view in &select.lateral_views {
4151            self.generate_lateral_view(lateral_view)?;
4152        }
4153
4154        // PREWHERE (ClickHouse)
4155        if let Some(prewhere) = &select.prewhere {
4156            self.write_clause_condition("PREWHERE", prewhere)?;
4157        }
4158
4159        // WHERE
4160        if let Some(where_clause) = &select.where_clause {
4161            self.write_clause_condition("WHERE", &where_clause.this)?;
4162        }
4163
4164        // CONNECT BY (Oracle hierarchical queries)
4165        if let Some(connect) = &select.connect {
4166            self.generate_connect(connect)?;
4167        }
4168
4169        // GROUP BY
4170        if let Some(group_by) = &select.group_by {
4171            if self.config.pretty {
4172                // Output leading comments on their own lines before GROUP BY
4173                for comment in &group_by.comments {
4174                    self.write_newline();
4175                    self.write_indent();
4176                    self.write_formatted_comment(comment);
4177                }
4178                self.write_newline();
4179                self.write_indent();
4180            } else {
4181                self.write_space();
4182                // In non-pretty mode, output comments inline
4183                for comment in &group_by.comments {
4184                    self.write_formatted_comment(comment);
4185                    self.write_space();
4186                }
4187            }
4188            self.write_keyword("GROUP BY");
4189            // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
4190            match group_by.all {
4191                Some(true) => {
4192                    self.write_space();
4193                    self.write_keyword("ALL");
4194                }
4195                Some(false) => {
4196                    self.write_space();
4197                    self.write_keyword("DISTINCT");
4198                }
4199                None => {}
4200            }
4201            if !group_by.expressions.is_empty() {
4202                // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
4203                // These are represented as Cube/Rollup expressions with empty expressions at the end
4204                let mut trailing_cube = false;
4205                let mut trailing_rollup = false;
4206                let mut plain_expressions: Vec<&Expression> = Vec::new();
4207                let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
4208                let mut cube_expressions: Vec<&Expression> = Vec::new();
4209                let mut rollup_expressions: Vec<&Expression> = Vec::new();
4210
4211                for expr in &group_by.expressions {
4212                    match expr {
4213                        Expression::Cube(c) if c.expressions.is_empty() => {
4214                            trailing_cube = true;
4215                        }
4216                        Expression::Rollup(r) if r.expressions.is_empty() => {
4217                            trailing_rollup = true;
4218                        }
4219                        Expression::Function(f) if f.name == "CUBE" => {
4220                            cube_expressions.push(expr);
4221                        }
4222                        Expression::Function(f) if f.name == "ROLLUP" => {
4223                            rollup_expressions.push(expr);
4224                        }
4225                        Expression::Function(f) if f.name == "GROUPING SETS" => {
4226                            grouping_sets_expressions.push(expr);
4227                        }
4228                        _ => {
4229                            plain_expressions.push(expr);
4230                        }
4231                    }
4232                }
4233
4234                // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
4235                let mut regular_expressions: Vec<&Expression> = Vec::new();
4236                regular_expressions.extend(plain_expressions);
4237                regular_expressions.extend(grouping_sets_expressions);
4238                regular_expressions.extend(cube_expressions);
4239                regular_expressions.extend(rollup_expressions);
4240
4241                if self.config.pretty {
4242                    self.write_newline();
4243                    self.indent_level += 1;
4244                    self.write_indent();
4245                } else {
4246                    self.write_space();
4247                }
4248
4249                for (i, expr) in regular_expressions.iter().enumerate() {
4250                    if i > 0 {
4251                        if self.config.pretty {
4252                            self.write(",");
4253                            self.write_newline();
4254                            self.write_indent();
4255                        } else {
4256                            self.write(", ");
4257                        }
4258                    }
4259                    self.generate_expression(expr)?;
4260                }
4261
4262                if self.config.pretty {
4263                    self.indent_level -= 1;
4264                }
4265
4266                // Output trailing WITH CUBE or WITH ROLLUP
4267                if trailing_cube {
4268                    self.write_space();
4269                    self.write_keyword("WITH CUBE");
4270                } else if trailing_rollup {
4271                    self.write_space();
4272                    self.write_keyword("WITH ROLLUP");
4273                }
4274            }
4275
4276            // ClickHouse: WITH TOTALS
4277            if group_by.totals {
4278                self.write_space();
4279                self.write_keyword("WITH TOTALS");
4280            }
4281        }
4282
4283        // HAVING
4284        if let Some(having) = &select.having {
4285            if self.config.pretty {
4286                // Output leading comments on their own lines before HAVING
4287                for comment in &having.comments {
4288                    self.write_newline();
4289                    self.write_indent();
4290                    self.write_formatted_comment(comment);
4291                }
4292            } else {
4293                for comment in &having.comments {
4294                    self.write_space();
4295                    self.write_formatted_comment(comment);
4296                }
4297            }
4298            self.write_clause_condition("HAVING", &having.this)?;
4299        }
4300
4301        // QUALIFY and WINDOW clause ordering depends on input SQL
4302        if select.qualify_after_window {
4303            // WINDOW before QUALIFY (DuckDB style)
4304            if let Some(windows) = &select.windows {
4305                self.write_window_clause(windows)?;
4306            }
4307            if let Some(qualify) = &select.qualify {
4308                self.write_clause_condition("QUALIFY", &qualify.this)?;
4309            }
4310        } else {
4311            // QUALIFY before WINDOW (Snowflake/BigQuery default)
4312            if let Some(qualify) = &select.qualify {
4313                self.write_clause_condition("QUALIFY", &qualify.this)?;
4314            }
4315            if let Some(windows) = &select.windows {
4316                self.write_window_clause(windows)?;
4317            }
4318        }
4319
4320        // DISTRIBUTE BY (Hive/Spark)
4321        if let Some(distribute_by) = &select.distribute_by {
4322            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
4323        }
4324
4325        // CLUSTER BY (Hive/Spark)
4326        if let Some(cluster_by) = &select.cluster_by {
4327            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
4328        }
4329
4330        // SORT BY (Hive/Spark - comes before ORDER BY)
4331        if let Some(sort_by) = &select.sort_by {
4332            self.write_order_clause("SORT BY", &sort_by.expressions)?;
4333        }
4334
4335        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
4336        if let Some(order_by) = &select.order_by {
4337            if self.config.pretty {
4338                // Output leading comments on their own lines before ORDER BY
4339                for comment in &order_by.comments {
4340                    self.write_newline();
4341                    self.write_indent();
4342                    self.write_formatted_comment(comment);
4343                }
4344            } else {
4345                for comment in &order_by.comments {
4346                    self.write_space();
4347                    self.write_formatted_comment(comment);
4348                }
4349            }
4350            let keyword = if order_by.siblings {
4351                "ORDER SIBLINGS BY"
4352            } else {
4353                "ORDER BY"
4354            };
4355            self.write_order_clause(keyword, &order_by.expressions)?;
4356        }
4357
4358        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
4359        if select.order_by.is_none()
4360            && select.fetch.is_some()
4361            && matches!(
4362                self.config.dialect,
4363                Some(DialectType::TSQL) | Some(DialectType::Fabric)
4364            )
4365        {
4366            if self.config.pretty {
4367                self.write_newline();
4368                self.write_indent();
4369            } else {
4370                self.write_space();
4371            }
4372            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
4373        }
4374
4375        // LIMIT and OFFSET
4376        // PostgreSQL and others use: LIMIT count OFFSET offset
4377        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
4378        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
4379        let is_presto_like = matches!(
4380            self.config.dialect,
4381            Some(DialectType::Presto) | Some(DialectType::Trino)
4382        );
4383
4384        if is_presto_like && select.offset.is_some() {
4385            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
4386            if let Some(offset) = &select.offset {
4387                if self.config.pretty {
4388                    self.write_newline();
4389                    self.write_indent();
4390                } else {
4391                    self.write_space();
4392                }
4393                self.write_keyword("OFFSET");
4394                self.write_space();
4395                self.write_limit_expr(&offset.this)?;
4396                if offset.rows == Some(true) {
4397                    self.write_space();
4398                    self.write_keyword("ROWS");
4399                }
4400            }
4401            if let Some(limit) = &select.limit {
4402                if self.config.pretty {
4403                    self.write_newline();
4404                    self.write_indent();
4405                } else {
4406                    self.write_space();
4407                }
4408                self.write_keyword("LIMIT");
4409                self.write_space();
4410                self.write_limit_expr(&limit.this)?;
4411                if limit.percent {
4412                    self.write_space();
4413                    self.write_keyword("PERCENT");
4414                }
4415                // Emit any comments that were captured from before the LIMIT keyword
4416                for comment in &limit.comments {
4417                    self.write(" ");
4418                    self.write_formatted_comment(comment);
4419                }
4420            }
4421        } else {
4422            // Check if FETCH will be converted to LIMIT (used for ordering)
4423            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
4424                !fetch.percent
4425                    && !fetch.with_ties
4426                    && fetch.count.is_some()
4427                    && matches!(
4428                        self.config.dialect,
4429                        Some(DialectType::Spark)
4430                            | Some(DialectType::Hive)
4431                            | Some(DialectType::DuckDB)
4432                            | Some(DialectType::SQLite)
4433                            | Some(DialectType::MySQL)
4434                            | Some(DialectType::BigQuery)
4435                            | Some(DialectType::Databricks)
4436                            | Some(DialectType::StarRocks)
4437                            | Some(DialectType::Doris)
4438                            | Some(DialectType::Athena)
4439                            | Some(DialectType::ClickHouse)
4440                            | Some(DialectType::Redshift)
4441                    )
4442            });
4443
4444            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
4445            if let Some(limit) = &select.limit {
4446                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
4447                if !matches!(self.config.dialect, Some(DialectType::TSQL)) {
4448                    if self.config.pretty {
4449                        self.write_newline();
4450                        self.write_indent();
4451                    } else {
4452                        self.write_space();
4453                    }
4454                    self.write_keyword("LIMIT");
4455                    self.write_space();
4456                    self.write_limit_expr(&limit.this)?;
4457                    if limit.percent {
4458                        self.write_space();
4459                        self.write_keyword("PERCENT");
4460                    }
4461                    // Emit any comments that were captured from before the LIMIT keyword
4462                    for comment in &limit.comments {
4463                        self.write(" ");
4464                        self.write_formatted_comment(comment);
4465                    }
4466                }
4467            }
4468
4469            // Convert TOP to LIMIT for non-TOP dialects
4470            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
4471                if let Some(top) = &select.top {
4472                    if !top.percent && !top.with_ties {
4473                        if self.config.pretty {
4474                            self.write_newline();
4475                            self.write_indent();
4476                        } else {
4477                            self.write_space();
4478                        }
4479                        self.write_keyword("LIMIT");
4480                        self.write_space();
4481                        self.generate_expression(&top.this)?;
4482                    }
4483                }
4484            }
4485
4486            // If FETCH will be converted to LIMIT and there's also OFFSET,
4487            // emit LIMIT from FETCH BEFORE the OFFSET
4488            if fetch_as_limit && select.offset.is_some() {
4489                if let Some(fetch) = &select.fetch {
4490                    if self.config.pretty {
4491                        self.write_newline();
4492                        self.write_indent();
4493                    } else {
4494                        self.write_space();
4495                    }
4496                    self.write_keyword("LIMIT");
4497                    self.write_space();
4498                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4499                }
4500            }
4501
4502            // OFFSET
4503            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
4504            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
4505            if let Some(offset) = &select.offset {
4506                if self.config.pretty {
4507                    self.write_newline();
4508                    self.write_indent();
4509                } else {
4510                    self.write_space();
4511                }
4512                if matches!(self.config.dialect, Some(DialectType::TSQL)) {
4513                    // SQL Server 2012+ OFFSET ... FETCH syntax
4514                    self.write_keyword("OFFSET");
4515                    self.write_space();
4516                    self.write_limit_expr(&offset.this)?;
4517                    self.write_space();
4518                    self.write_keyword("ROWS");
4519                    // If there was a LIMIT, use FETCH NEXT ... ROWS ONLY
4520                    if let Some(limit) = &select.limit {
4521                        self.write_space();
4522                        self.write_keyword("FETCH NEXT");
4523                        self.write_space();
4524                        self.write_limit_expr(&limit.this)?;
4525                        self.write_space();
4526                        self.write_keyword("ROWS ONLY");
4527                    }
4528                } else {
4529                    self.write_keyword("OFFSET");
4530                    self.write_space();
4531                    self.write_limit_expr(&offset.this)?;
4532                    // Output ROWS keyword if it was in the original SQL
4533                    if offset.rows == Some(true) {
4534                        self.write_space();
4535                        self.write_keyword("ROWS");
4536                    }
4537                }
4538            }
4539        }
4540
4541        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
4542        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4543            if let Some(limit_by) = &select.limit_by {
4544                if !limit_by.is_empty() {
4545                    self.write_space();
4546                    self.write_keyword("BY");
4547                    self.write_space();
4548                    for (i, expr) in limit_by.iter().enumerate() {
4549                        if i > 0 {
4550                            self.write(", ");
4551                        }
4552                        self.generate_expression(expr)?;
4553                    }
4554                }
4555            }
4556        }
4557
4558        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
4559        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4560            if let Some(settings) = &select.settings {
4561                if self.config.pretty {
4562                    self.write_newline();
4563                    self.write_indent();
4564                } else {
4565                    self.write_space();
4566                }
4567                self.write_keyword("SETTINGS");
4568                self.write_space();
4569                for (i, expr) in settings.iter().enumerate() {
4570                    if i > 0 {
4571                        self.write(", ");
4572                    }
4573                    self.generate_expression(expr)?;
4574                }
4575            }
4576
4577            if let Some(format_expr) = &select.format {
4578                if self.config.pretty {
4579                    self.write_newline();
4580                    self.write_indent();
4581                } else {
4582                    self.write_space();
4583                }
4584                self.write_keyword("FORMAT");
4585                self.write_space();
4586                self.generate_expression(format_expr)?;
4587            }
4588        }
4589
4590        // FETCH FIRST/NEXT
4591        if let Some(fetch) = &select.fetch {
4592            // Check if we already emitted LIMIT from FETCH before OFFSET
4593            let fetch_already_as_limit = select.offset.is_some()
4594                && !fetch.percent
4595                && !fetch.with_ties
4596                && fetch.count.is_some()
4597                && matches!(
4598                    self.config.dialect,
4599                    Some(DialectType::Spark)
4600                        | Some(DialectType::Hive)
4601                        | Some(DialectType::DuckDB)
4602                        | Some(DialectType::SQLite)
4603                        | Some(DialectType::MySQL)
4604                        | Some(DialectType::BigQuery)
4605                        | Some(DialectType::Databricks)
4606                        | Some(DialectType::StarRocks)
4607                        | Some(DialectType::Doris)
4608                        | Some(DialectType::Athena)
4609                        | Some(DialectType::ClickHouse)
4610                        | Some(DialectType::Redshift)
4611                );
4612
4613            if fetch_already_as_limit {
4614                // Already emitted as LIMIT before OFFSET, skip
4615            } else {
4616                if self.config.pretty {
4617                    self.write_newline();
4618                    self.write_indent();
4619                } else {
4620                    self.write_space();
4621                }
4622
4623                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
4624                let use_limit = !fetch.percent
4625                    && !fetch.with_ties
4626                    && fetch.count.is_some()
4627                    && matches!(
4628                        self.config.dialect,
4629                        Some(DialectType::Spark)
4630                            | Some(DialectType::Hive)
4631                            | Some(DialectType::DuckDB)
4632                            | Some(DialectType::SQLite)
4633                            | Some(DialectType::MySQL)
4634                            | Some(DialectType::BigQuery)
4635                            | Some(DialectType::Databricks)
4636                            | Some(DialectType::StarRocks)
4637                            | Some(DialectType::Doris)
4638                            | Some(DialectType::Athena)
4639                            | Some(DialectType::ClickHouse)
4640                            | Some(DialectType::Redshift)
4641                    );
4642
4643                if use_limit {
4644                    self.write_keyword("LIMIT");
4645                    self.write_space();
4646                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4647                } else {
4648                    self.write_keyword("FETCH");
4649                    self.write_space();
4650                    self.write_keyword(&fetch.direction);
4651                    if let Some(ref count) = fetch.count {
4652                        self.write_space();
4653                        self.generate_expression(count)?;
4654                    }
4655                    if fetch.percent {
4656                        self.write_space();
4657                        self.write_keyword("PERCENT");
4658                    }
4659                    if fetch.rows {
4660                        self.write_space();
4661                        self.write_keyword("ROWS");
4662                    }
4663                    if fetch.with_ties {
4664                        self.write_space();
4665                        self.write_keyword("WITH TIES");
4666                    } else {
4667                        self.write_space();
4668                        self.write_keyword("ONLY");
4669                    }
4670                }
4671            } // close fetch_already_as_limit else
4672        }
4673
4674        // SAMPLE / TABLESAMPLE
4675        if let Some(sample) = &select.sample {
4676            use crate::dialects::DialectType;
4677            if self.config.pretty {
4678                self.write_newline();
4679            } else {
4680                self.write_space();
4681            }
4682
4683            if sample.is_using_sample {
4684                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
4685                self.write_keyword("USING SAMPLE");
4686                self.generate_sample_body(sample)?;
4687            } else {
4688                self.write_keyword("TABLESAMPLE");
4689
4690                // Snowflake defaults to BERNOULLI when no explicit method is given
4691                let snowflake_bernoulli =
4692                    matches!(self.config.dialect, Some(DialectType::Snowflake))
4693                        && !sample.explicit_method;
4694                if snowflake_bernoulli {
4695                    self.write_space();
4696                    self.write_keyword("BERNOULLI");
4697                }
4698
4699                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
4700                if matches!(sample.method, SampleMethod::Bucket) {
4701                    self.write_space();
4702                    self.write("(");
4703                    self.write_keyword("BUCKET");
4704                    self.write_space();
4705                    if let Some(ref num) = sample.bucket_numerator {
4706                        self.generate_expression(num)?;
4707                    }
4708                    self.write_space();
4709                    self.write_keyword("OUT OF");
4710                    self.write_space();
4711                    if let Some(ref denom) = sample.bucket_denominator {
4712                        self.generate_expression(denom)?;
4713                    }
4714                    if let Some(ref field) = sample.bucket_field {
4715                        self.write_space();
4716                        self.write_keyword("ON");
4717                        self.write_space();
4718                        self.generate_expression(field)?;
4719                    }
4720                    self.write(")");
4721                } else if sample.unit_after_size {
4722                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
4723                    if sample.explicit_method && sample.method_before_size {
4724                        self.write_space();
4725                        match sample.method {
4726                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4727                            SampleMethod::System => self.write_keyword("SYSTEM"),
4728                            SampleMethod::Block => self.write_keyword("BLOCK"),
4729                            SampleMethod::Row => self.write_keyword("ROW"),
4730                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4731                            _ => {}
4732                        }
4733                    }
4734                    self.write(" (");
4735                    self.generate_expression(&sample.size)?;
4736                    self.write_space();
4737                    match sample.method {
4738                        SampleMethod::Percent => self.write_keyword("PERCENT"),
4739                        SampleMethod::Row => self.write_keyword("ROWS"),
4740                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
4741                        _ => {
4742                            self.write_keyword("PERCENT");
4743                        }
4744                    }
4745                    self.write(")");
4746                } else {
4747                    // Syntax: TABLESAMPLE METHOD (size)
4748                    self.write_space();
4749                    match sample.method {
4750                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4751                        SampleMethod::System => self.write_keyword("SYSTEM"),
4752                        SampleMethod::Block => self.write_keyword("BLOCK"),
4753                        SampleMethod::Row => self.write_keyword("ROW"),
4754                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
4755                        SampleMethod::Bucket => {}
4756                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4757                    }
4758                    self.write(" (");
4759                    self.generate_expression(&sample.size)?;
4760                    if matches!(sample.method, SampleMethod::Percent) {
4761                        self.write_space();
4762                        self.write_keyword("PERCENT");
4763                    }
4764                    self.write(")");
4765                }
4766            }
4767
4768            if let Some(seed) = &sample.seed {
4769                self.write_space();
4770                // Databricks/Spark use REPEATABLE, not SEED
4771                let use_seed = sample.use_seed_keyword
4772                    && !matches!(
4773                        self.config.dialect,
4774                        Some(crate::dialects::DialectType::Databricks)
4775                            | Some(crate::dialects::DialectType::Spark)
4776                    );
4777                if use_seed {
4778                    self.write_keyword("SEED");
4779                } else {
4780                    self.write_keyword("REPEATABLE");
4781                }
4782                self.write(" (");
4783                self.generate_expression(seed)?;
4784                self.write(")");
4785            }
4786        }
4787
4788        // FOR UPDATE/SHARE locks
4789        // Skip locking clauses for dialects that don't support them
4790        if self.config.locking_reads_supported {
4791            for lock in &select.locks {
4792                if self.config.pretty {
4793                    self.write_newline();
4794                    self.write_indent();
4795                } else {
4796                    self.write_space();
4797                }
4798                self.generate_lock(lock)?;
4799            }
4800        }
4801
4802        // FOR XML clause (T-SQL)
4803        if !select.for_xml.is_empty() {
4804            if self.config.pretty {
4805                self.write_newline();
4806                self.write_indent();
4807            } else {
4808                self.write_space();
4809            }
4810            self.write_keyword("FOR XML");
4811            for (i, opt) in select.for_xml.iter().enumerate() {
4812                if self.config.pretty {
4813                    if i > 0 {
4814                        self.write(",");
4815                    }
4816                    self.write_newline();
4817                    self.write_indent();
4818                    self.write("  "); // extra indent for options
4819                } else {
4820                    if i > 0 {
4821                        self.write(",");
4822                    }
4823                    self.write_space();
4824                }
4825                self.generate_for_xml_option(opt)?;
4826            }
4827        }
4828
4829        // TSQL: OPTION clause
4830        if let Some(ref option) = select.option {
4831            if matches!(
4832                self.config.dialect,
4833                Some(crate::dialects::DialectType::TSQL)
4834                    | Some(crate::dialects::DialectType::Fabric)
4835            ) {
4836                self.write_space();
4837                self.write(option);
4838            }
4839        }
4840
4841        Ok(())
4842    }
4843
4844    /// Generate a single FOR XML option
4845    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
4846        match opt {
4847            Expression::QueryOption(qo) => {
4848                // Extract the option name from Var
4849                if let Expression::Var(var) = &*qo.this {
4850                    self.write(&var.this);
4851                } else {
4852                    self.generate_expression(&qo.this)?;
4853                }
4854                // If there's an expression (like PATH('element')), output it in parens
4855                if let Some(expr) = &qo.expression {
4856                    self.write("(");
4857                    self.generate_expression(expr)?;
4858                    self.write(")");
4859                }
4860            }
4861            _ => {
4862                self.generate_expression(opt)?;
4863            }
4864        }
4865        Ok(())
4866    }
4867
4868    fn generate_with(&mut self, with: &With) -> Result<()> {
4869        use crate::dialects::DialectType;
4870
4871        // Output leading comments before WITH
4872        for comment in &with.leading_comments {
4873            self.write_formatted_comment(comment);
4874            self.write(" ");
4875        }
4876        self.write_keyword("WITH");
4877        if with.recursive && self.config.cte_recursive_keyword_required {
4878            self.write_space();
4879            self.write_keyword("RECURSIVE");
4880        }
4881        self.write_space();
4882
4883        // BigQuery doesn't support column aliases in CTE definitions
4884        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
4885
4886        for (i, cte) in with.ctes.iter().enumerate() {
4887            if i > 0 {
4888                self.write(",");
4889                if self.config.pretty {
4890                    self.write_space();
4891                } else {
4892                    self.write(" ");
4893                }
4894            }
4895            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
4896                self.generate_expression(&cte.this)?;
4897                self.write_space();
4898                self.write_keyword("AS");
4899                self.write_space();
4900                self.generate_identifier(&cte.alias)?;
4901                continue;
4902            }
4903            self.generate_identifier(&cte.alias)?;
4904            // Output CTE comments after alias name, before AS
4905            for comment in &cte.comments {
4906                self.write_space();
4907                self.write_formatted_comment(comment);
4908            }
4909            if !cte.columns.is_empty() && !skip_cte_columns {
4910                self.write("(");
4911                for (j, col) in cte.columns.iter().enumerate() {
4912                    if j > 0 {
4913                        self.write(", ");
4914                    }
4915                    self.generate_identifier(col)?;
4916                }
4917                self.write(")");
4918            }
4919            // USING KEY (columns) for DuckDB recursive CTEs
4920            if !cte.key_expressions.is_empty() {
4921                self.write_space();
4922                self.write_keyword("USING KEY");
4923                self.write(" (");
4924                for (i, key) in cte.key_expressions.iter().enumerate() {
4925                    if i > 0 {
4926                        self.write(", ");
4927                    }
4928                    self.generate_identifier(key)?;
4929                }
4930                self.write(")");
4931            }
4932            self.write_space();
4933            self.write_keyword("AS");
4934            // MATERIALIZED / NOT MATERIALIZED
4935            if let Some(materialized) = cte.materialized {
4936                self.write_space();
4937                if materialized {
4938                    self.write_keyword("MATERIALIZED");
4939                } else {
4940                    self.write_keyword("NOT MATERIALIZED");
4941                }
4942            }
4943            self.write(" (");
4944            if self.config.pretty {
4945                self.write_newline();
4946                self.indent_level += 1;
4947                self.write_indent();
4948            }
4949            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
4950            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
4951            let wrap_values_in_select = matches!(
4952                self.config.dialect,
4953                Some(DialectType::Spark) | Some(DialectType::Databricks)
4954            ) && matches!(&cte.this, Expression::Values(_));
4955
4956            if wrap_values_in_select {
4957                self.write_keyword("SELECT");
4958                self.write(" * ");
4959                self.write_keyword("FROM");
4960                self.write_space();
4961            }
4962            self.generate_expression(&cte.this)?;
4963            if self.config.pretty {
4964                self.write_newline();
4965                self.indent_level -= 1;
4966                self.write_indent();
4967            }
4968            self.write(")");
4969        }
4970
4971        // Generate SEARCH/CYCLE clause if present
4972        if let Some(search) = &with.search {
4973            self.write_space();
4974            self.generate_expression(search)?;
4975        }
4976
4977        Ok(())
4978    }
4979
4980    /// Generate joins with proper nesting structure for pretty printing.
4981    /// Deferred-condition joins "own" the non-deferred joins that follow them
4982    /// within the same nesting_group.
4983    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
4984        let mut i = 0;
4985        while i < joins.len() {
4986            if joins[i].deferred_condition {
4987                let parent_group = joins[i].nesting_group;
4988
4989                // This join owns the following non-deferred joins in the same nesting_group
4990                // First output the join keyword and table (without condition)
4991                self.generate_join_without_condition(&joins[i])?;
4992
4993                // Find the range of child joins: same nesting_group and not deferred
4994                let child_start = i + 1;
4995                let mut child_end = child_start;
4996                while child_end < joins.len()
4997                    && !joins[child_end].deferred_condition
4998                    && joins[child_end].nesting_group == parent_group
4999                {
5000                    child_end += 1;
5001                }
5002
5003                // Output child joins with extra indentation
5004                if child_start < child_end {
5005                    self.indent_level += 1;
5006                    for j in child_start..child_end {
5007                        self.generate_join(&joins[j])?;
5008                    }
5009                    self.indent_level -= 1;
5010                }
5011
5012                // Output the deferred condition at the parent level
5013                self.generate_join_condition(&joins[i])?;
5014
5015                i = child_end;
5016            } else {
5017                // Regular join (no nesting)
5018                self.generate_join(&joins[i])?;
5019                i += 1;
5020            }
5021        }
5022        Ok(())
5023    }
5024
5025    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5026    /// Used for deferred-condition joins where the condition is output after child joins.
5027    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5028        // Save and temporarily clear the condition to prevent generate_join from outputting it
5029        // We achieve this by creating a modified copy
5030        let mut join_copy = join.clone();
5031        join_copy.on = None;
5032        join_copy.using = Vec::new();
5033        join_copy.deferred_condition = false;
5034        self.generate_join(&join_copy)
5035    }
5036
5037    fn generate_join(&mut self, join: &Join) -> Result<()> {
5038        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5039        if join.kind == JoinKind::Implicit {
5040            self.write(",");
5041            if self.config.pretty {
5042                self.write_newline();
5043                self.write_indent();
5044            } else {
5045                self.write_space();
5046            }
5047            self.generate_expression(&join.this)?;
5048            return Ok(());
5049        }
5050
5051        if self.config.pretty {
5052            self.write_newline();
5053            self.write_indent();
5054        } else {
5055            self.write_space();
5056        }
5057
5058        // Helper: format hint suffix (e.g., " LOOP" or "")
5059        // Only include join hints for dialects that support them
5060        let hint_str = if self.config.join_hints {
5061            join.join_hint
5062                .as_ref()
5063                .map(|h| format!(" {}", h))
5064                .unwrap_or_default()
5065        } else {
5066            String::new()
5067        };
5068
5069        let clickhouse_join_keyword =
5070            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5071                if let Some(hint) = &join.join_hint {
5072                    let mut global = false;
5073                    let mut strictness: Option<&'static str> = None;
5074                    for part in hint.split_whitespace() {
5075                        match part.to_uppercase().as_str() {
5076                            "GLOBAL" => global = true,
5077                            "ANY" => strictness = Some("ANY"),
5078                            "ASOF" => strictness = Some("ASOF"),
5079                            "SEMI" => strictness = Some("SEMI"),
5080                            "ANTI" => strictness = Some("ANTI"),
5081                            _ => {}
5082                        }
5083                    }
5084
5085                    if global || strictness.is_some() {
5086                        let join_type = match join.kind {
5087                            JoinKind::Left => {
5088                                if join.use_outer_keyword {
5089                                    "LEFT OUTER"
5090                                } else if join.use_inner_keyword {
5091                                    "LEFT INNER"
5092                                } else {
5093                                    "LEFT"
5094                                }
5095                            }
5096                            JoinKind::Right => {
5097                                if join.use_outer_keyword {
5098                                    "RIGHT OUTER"
5099                                } else if join.use_inner_keyword {
5100                                    "RIGHT INNER"
5101                                } else {
5102                                    "RIGHT"
5103                                }
5104                            }
5105                            JoinKind::Full => {
5106                                if join.use_outer_keyword {
5107                                    "FULL OUTER"
5108                                } else {
5109                                    "FULL"
5110                                }
5111                            }
5112                            JoinKind::Inner => {
5113                                if join.use_inner_keyword {
5114                                    "INNER"
5115                                } else {
5116                                    ""
5117                                }
5118                            }
5119                            _ => "",
5120                        };
5121
5122                        let mut parts = Vec::new();
5123                        if global {
5124                            parts.push("GLOBAL");
5125                        }
5126                        if !join_type.is_empty() {
5127                            parts.push(join_type);
5128                        }
5129                        if let Some(strict) = strictness {
5130                            parts.push(strict);
5131                        }
5132                        parts.push("JOIN");
5133                        Some(parts.join(" "))
5134                    } else {
5135                        None
5136                    }
5137                } else {
5138                    None
5139                }
5140            } else {
5141                None
5142            };
5143
5144        // Output any comments associated with this join
5145        // In pretty mode, comments go on their own line before the join keyword
5146        // In non-pretty mode, comments go inline before the join keyword
5147        if !join.comments.is_empty() {
5148            if self.config.pretty {
5149                // In pretty mode, go back before the newline+indent we just wrote
5150                // and output comments on their own lines
5151                // We need to output comments BEFORE the join keyword on separate lines
5152                // Trim the trailing newline+indent we already wrote
5153                let trimmed = self.output.trim_end().len();
5154                self.output.truncate(trimmed);
5155                for comment in &join.comments {
5156                    self.write_newline();
5157                    self.write_indent();
5158                    self.write_formatted_comment(comment);
5159                }
5160                self.write_newline();
5161                self.write_indent();
5162            } else {
5163                for comment in &join.comments {
5164                    self.write_formatted_comment(comment);
5165                    self.write_space();
5166                }
5167            }
5168        }
5169
5170        let directed_str = if join.directed { " DIRECTED" } else { "" };
5171
5172        if let Some(keyword) = clickhouse_join_keyword {
5173            self.write_keyword(&keyword);
5174        } else {
5175            match join.kind {
5176                JoinKind::Inner => {
5177                    if join.use_inner_keyword {
5178                        self.write_keyword(&format!("INNER{}{} JOIN", hint_str, directed_str));
5179                    } else {
5180                        self.write_keyword(&format!(
5181                            "{}{}JOIN",
5182                            if hint_str.is_empty() {
5183                                String::new()
5184                            } else {
5185                                format!("{} ", hint_str.trim())
5186                            },
5187                            if directed_str.is_empty() {
5188                                ""
5189                            } else {
5190                                "DIRECTED "
5191                            }
5192                        ));
5193                    }
5194                }
5195                JoinKind::Left => {
5196                    if join.use_outer_keyword {
5197                        self.write_keyword(&format!("LEFT OUTER{}{} JOIN", hint_str, directed_str));
5198                    } else if join.use_inner_keyword {
5199                        self.write_keyword(&format!("LEFT INNER{}{} JOIN", hint_str, directed_str));
5200                    } else {
5201                        self.write_keyword(&format!("LEFT{}{} JOIN", hint_str, directed_str));
5202                    }
5203                }
5204                JoinKind::Right => {
5205                    if join.use_outer_keyword {
5206                        self.write_keyword(&format!(
5207                            "RIGHT OUTER{}{} JOIN",
5208                            hint_str, directed_str
5209                        ));
5210                    } else if join.use_inner_keyword {
5211                        self.write_keyword(&format!(
5212                            "RIGHT INNER{}{} JOIN",
5213                            hint_str, directed_str
5214                        ));
5215                    } else {
5216                        self.write_keyword(&format!("RIGHT{}{} JOIN", hint_str, directed_str));
5217                    }
5218                }
5219                JoinKind::Full => {
5220                    if join.use_outer_keyword {
5221                        self.write_keyword(&format!("FULL OUTER{}{} JOIN", hint_str, directed_str));
5222                    } else {
5223                        self.write_keyword(&format!("FULL{}{} JOIN", hint_str, directed_str));
5224                    }
5225                }
5226                JoinKind::Outer => self.write_keyword(&format!("OUTER{} JOIN", directed_str)),
5227                JoinKind::Cross => self.write_keyword(&format!("CROSS{} JOIN", directed_str)),
5228                JoinKind::Natural => {
5229                    if join.use_inner_keyword {
5230                        self.write_keyword(&format!("NATURAL INNER{} JOIN", directed_str));
5231                    } else {
5232                        self.write_keyword(&format!("NATURAL{} JOIN", directed_str));
5233                    }
5234                }
5235                JoinKind::NaturalLeft => {
5236                    if join.use_outer_keyword {
5237                        self.write_keyword(&format!("NATURAL LEFT OUTER{} JOIN", directed_str));
5238                    } else {
5239                        self.write_keyword(&format!("NATURAL LEFT{} JOIN", directed_str));
5240                    }
5241                }
5242                JoinKind::NaturalRight => {
5243                    if join.use_outer_keyword {
5244                        self.write_keyword(&format!("NATURAL RIGHT OUTER{} JOIN", directed_str));
5245                    } else {
5246                        self.write_keyword(&format!("NATURAL RIGHT{} JOIN", directed_str));
5247                    }
5248                }
5249                JoinKind::NaturalFull => {
5250                    if join.use_outer_keyword {
5251                        self.write_keyword(&format!("NATURAL FULL OUTER{} JOIN", directed_str));
5252                    } else {
5253                        self.write_keyword(&format!("NATURAL FULL{} JOIN", directed_str));
5254                    }
5255                }
5256                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
5257                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
5258                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
5259                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
5260                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
5261                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
5262                JoinKind::CrossApply => {
5263                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL dialects
5264                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5265                        self.write_keyword("CROSS APPLY");
5266                    } else {
5267                        self.write_keyword("INNER JOIN LATERAL");
5268                    }
5269                }
5270                JoinKind::OuterApply => {
5271                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL dialects
5272                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5273                        self.write_keyword("OUTER APPLY");
5274                    } else {
5275                        self.write_keyword("LEFT JOIN LATERAL");
5276                    }
5277                }
5278                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
5279                JoinKind::AsOfLeft => {
5280                    if join.use_outer_keyword {
5281                        self.write_keyword("ASOF LEFT OUTER JOIN");
5282                    } else {
5283                        self.write_keyword("ASOF LEFT JOIN");
5284                    }
5285                }
5286                JoinKind::AsOfRight => {
5287                    if join.use_outer_keyword {
5288                        self.write_keyword("ASOF RIGHT OUTER JOIN");
5289                    } else {
5290                        self.write_keyword("ASOF RIGHT JOIN");
5291                    }
5292                }
5293                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
5294                JoinKind::LeftLateral => {
5295                    if join.use_outer_keyword {
5296                        self.write_keyword("LEFT OUTER LATERAL JOIN");
5297                    } else {
5298                        self.write_keyword("LEFT LATERAL JOIN");
5299                    }
5300                }
5301                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
5302                JoinKind::Implicit => {
5303                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
5304                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
5305                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
5306                    use crate::dialects::DialectType;
5307                    let is_cj_dialect = matches!(
5308                        self.config.dialect,
5309                        Some(DialectType::BigQuery)
5310                            | Some(DialectType::Hive)
5311                            | Some(DialectType::Spark)
5312                            | Some(DialectType::Databricks)
5313                    );
5314                    let source_is_same = self.config.source_dialect.is_some()
5315                        && self.config.source_dialect == self.config.dialect;
5316                    let source_is_cj = matches!(
5317                        self.config.source_dialect,
5318                        Some(DialectType::BigQuery)
5319                            | Some(DialectType::Hive)
5320                            | Some(DialectType::Spark)
5321                            | Some(DialectType::Databricks)
5322                    );
5323                    if is_cj_dialect
5324                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
5325                    {
5326                        self.write_keyword("CROSS JOIN");
5327                    } else {
5328                        // Implicit join uses comma: FROM a, b
5329                        // We already wrote a space before the match, so replace with comma
5330                        // by removing trailing space and writing ", "
5331                        self.output.truncate(self.output.trim_end().len());
5332                        self.write(",");
5333                    }
5334                }
5335                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
5336                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
5337                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
5338            }
5339        }
5340
5341        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
5342        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
5343            self.write_space();
5344            match &join.this {
5345                Expression::Tuple(t) => {
5346                    for (i, item) in t.expressions.iter().enumerate() {
5347                        if i > 0 {
5348                            self.write(", ");
5349                        }
5350                        self.generate_expression(item)?;
5351                    }
5352                }
5353                other => {
5354                    self.generate_expression(other)?;
5355                }
5356            }
5357        } else {
5358            self.write_space();
5359            self.generate_expression(&join.this)?;
5360        }
5361
5362        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
5363        if !join.deferred_condition {
5364            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
5365            if let Some(match_cond) = &join.match_condition {
5366                self.write_space();
5367                self.write_keyword("MATCH_CONDITION");
5368                self.write(" (");
5369                self.generate_expression(match_cond)?;
5370                self.write(")");
5371            }
5372
5373            if let Some(on) = &join.on {
5374                if self.config.pretty {
5375                    self.write_newline();
5376                    self.indent_level += 1;
5377                    self.write_indent();
5378                    self.write_keyword("ON");
5379                    self.write_space();
5380                    self.generate_join_on_condition(on)?;
5381                    self.indent_level -= 1;
5382                } else {
5383                    self.write_space();
5384                    self.write_keyword("ON");
5385                    self.write_space();
5386                    self.generate_expression(on)?;
5387                }
5388            }
5389
5390            if !join.using.is_empty() {
5391                if self.config.pretty {
5392                    self.write_newline();
5393                    self.indent_level += 1;
5394                    self.write_indent();
5395                    self.write_keyword("USING");
5396                    self.write(" (");
5397                    for (i, col) in join.using.iter().enumerate() {
5398                        if i > 0 {
5399                            self.write(", ");
5400                        }
5401                        self.generate_identifier(col)?;
5402                    }
5403                    self.write(")");
5404                    self.indent_level -= 1;
5405                } else {
5406                    self.write_space();
5407                    self.write_keyword("USING");
5408                    self.write(" (");
5409                    for (i, col) in join.using.iter().enumerate() {
5410                        if i > 0 {
5411                            self.write(", ");
5412                        }
5413                        self.generate_identifier(col)?;
5414                    }
5415                    self.write(")");
5416                }
5417            }
5418        }
5419
5420        // Generate PIVOT/UNPIVOT expressions that follow this join
5421        for pivot in &join.pivots {
5422            self.write_space();
5423            self.generate_expression(pivot)?;
5424        }
5425
5426        Ok(())
5427    }
5428
5429    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
5430    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
5431        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
5432        if let Some(match_cond) = &join.match_condition {
5433            self.write_space();
5434            self.write_keyword("MATCH_CONDITION");
5435            self.write(" (");
5436            self.generate_expression(match_cond)?;
5437            self.write(")");
5438        }
5439
5440        if let Some(on) = &join.on {
5441            if self.config.pretty {
5442                self.write_newline();
5443                self.indent_level += 1;
5444                self.write_indent();
5445                self.write_keyword("ON");
5446                self.write_space();
5447                // In pretty mode, split AND conditions onto separate lines
5448                self.generate_join_on_condition(on)?;
5449                self.indent_level -= 1;
5450            } else {
5451                self.write_space();
5452                self.write_keyword("ON");
5453                self.write_space();
5454                self.generate_expression(on)?;
5455            }
5456        }
5457
5458        if !join.using.is_empty() {
5459            if self.config.pretty {
5460                self.write_newline();
5461                self.indent_level += 1;
5462                self.write_indent();
5463                self.write_keyword("USING");
5464                self.write(" (");
5465                for (i, col) in join.using.iter().enumerate() {
5466                    if i > 0 {
5467                        self.write(", ");
5468                    }
5469                    self.generate_identifier(col)?;
5470                }
5471                self.write(")");
5472                self.indent_level -= 1;
5473            } else {
5474                self.write_space();
5475                self.write_keyword("USING");
5476                self.write(" (");
5477                for (i, col) in join.using.iter().enumerate() {
5478                    if i > 0 {
5479                        self.write(", ");
5480                    }
5481                    self.generate_identifier(col)?;
5482                }
5483                self.write(")");
5484            }
5485        }
5486
5487        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
5488        for pivot in &join.pivots {
5489            self.write_space();
5490            self.generate_expression(pivot)?;
5491        }
5492
5493        Ok(())
5494    }
5495
5496    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
5497    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
5498        if let Expression::And(and_op) = expr {
5499            if let Some(conditions) = self.flatten_connector_terms(and_op, ConnectorOperator::And) {
5500                self.generate_expression(conditions[0])?;
5501                for condition in conditions.iter().skip(1) {
5502                    self.write_newline();
5503                    self.write_indent();
5504                    self.write_keyword("AND");
5505                    self.write_space();
5506                    self.generate_expression(condition)?;
5507                }
5508                return Ok(());
5509            }
5510        }
5511
5512        self.generate_expression(expr)
5513    }
5514
5515    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
5516        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
5517        self.write("(");
5518        self.generate_expression(&jt.left)?;
5519
5520        // Generate all joins
5521        for join in &jt.joins {
5522            self.generate_join(join)?;
5523        }
5524
5525        // Generate LATERAL VIEW clauses (Hive/Spark)
5526        for lv in &jt.lateral_views {
5527            self.generate_lateral_view(lv)?;
5528        }
5529
5530        self.write(")");
5531
5532        // Alias
5533        if let Some(alias) = &jt.alias {
5534            self.write_space();
5535            self.write_keyword("AS");
5536            self.write_space();
5537            self.generate_identifier(alias)?;
5538        }
5539
5540        Ok(())
5541    }
5542
5543    fn generate_lateral_view(&mut self, lv: &LateralView) -> Result<()> {
5544        use crate::dialects::DialectType;
5545
5546        if self.config.pretty {
5547            self.write_newline();
5548            self.write_indent();
5549        } else {
5550            self.write_space();
5551        }
5552
5553        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
5554        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
5555        let use_lateral_join = matches!(
5556            self.config.dialect,
5557            Some(DialectType::PostgreSQL)
5558                | Some(DialectType::DuckDB)
5559                | Some(DialectType::Snowflake)
5560                | Some(DialectType::TSQL)
5561                | Some(DialectType::Presto)
5562                | Some(DialectType::Trino)
5563                | Some(DialectType::Athena)
5564        );
5565
5566        // Check if target dialect should use UNNEST instead of EXPLODE
5567        let use_unnest = matches!(
5568            self.config.dialect,
5569            Some(DialectType::DuckDB)
5570                | Some(DialectType::Presto)
5571                | Some(DialectType::Trino)
5572                | Some(DialectType::Athena)
5573        );
5574
5575        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
5576        let (is_posexplode, func_args) = match &lv.this {
5577            Expression::Explode(uf) => {
5578                // Expression::Explode is the dedicated EXPLODE expression type
5579                (false, vec![uf.this.clone()])
5580            }
5581            Expression::Unnest(uf) => {
5582                let mut args = vec![uf.this.clone()];
5583                args.extend(uf.expressions.clone());
5584                (false, args)
5585            }
5586            Expression::Function(func) => {
5587                let name = func.name.to_uppercase();
5588                if name == "POSEXPLODE" || name == "POSEXPLODE_OUTER" {
5589                    (true, func.args.clone())
5590                } else if name == "EXPLODE" || name == "EXPLODE_OUTER" || name == "INLINE" {
5591                    (false, func.args.clone())
5592                } else {
5593                    (false, vec![])
5594                }
5595            }
5596            _ => (false, vec![]),
5597        };
5598
5599        if use_lateral_join {
5600            // Convert to CROSS JOIN for PostgreSQL-like dialects
5601            if lv.outer {
5602                self.write_keyword("LEFT JOIN LATERAL");
5603            } else {
5604                self.write_keyword("CROSS JOIN");
5605            }
5606            self.write_space();
5607
5608            if use_unnest && !func_args.is_empty() {
5609                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
5610                // For DuckDB, also convert ARRAY(y) -> [y]
5611                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
5612                    // DuckDB: ARRAY(y) -> [y]
5613                    func_args
5614                        .iter()
5615                        .map(|a| {
5616                            if let Expression::Function(ref f) = a {
5617                                if f.name.to_uppercase() == "ARRAY" && f.args.len() == 1 {
5618                                    return Expression::ArrayFunc(Box::new(
5619                                        crate::expressions::ArrayConstructor {
5620                                            expressions: f.args.clone(),
5621                                            bracket_notation: true,
5622                                            use_list_keyword: false,
5623                                        },
5624                                    ));
5625                                }
5626                            }
5627                            a.clone()
5628                        })
5629                        .collect::<Vec<_>>()
5630                } else if matches!(
5631                    self.config.dialect,
5632                    Some(DialectType::Presto)
5633                        | Some(DialectType::Trino)
5634                        | Some(DialectType::Athena)
5635                ) {
5636                    // Presto: ARRAY(y) -> ARRAY[y]
5637                    func_args
5638                        .iter()
5639                        .map(|a| {
5640                            if let Expression::Function(ref f) = a {
5641                                if f.name.to_uppercase() == "ARRAY" && f.args.len() >= 1 {
5642                                    return Expression::ArrayFunc(Box::new(
5643                                        crate::expressions::ArrayConstructor {
5644                                            expressions: f.args.clone(),
5645                                            bracket_notation: true,
5646                                            use_list_keyword: false,
5647                                        },
5648                                    ));
5649                                }
5650                            }
5651                            a.clone()
5652                        })
5653                        .collect::<Vec<_>>()
5654                } else {
5655                    func_args
5656                };
5657
5658                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
5659                if is_posexplode {
5660                    self.write_keyword("LATERAL");
5661                    self.write(" (");
5662                    self.write_keyword("SELECT");
5663                    self.write_space();
5664
5665                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
5666                    // column_aliases[0] is the position column, rest are data columns
5667                    let pos_alias = if !lv.column_aliases.is_empty() {
5668                        lv.column_aliases[0].clone()
5669                    } else {
5670                        Identifier::new("pos")
5671                    };
5672                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
5673                        lv.column_aliases[1..].to_vec()
5674                    } else {
5675                        vec![Identifier::new("col")]
5676                    };
5677
5678                    // pos - 1 AS pos
5679                    self.generate_identifier(&pos_alias)?;
5680                    self.write(" - 1");
5681                    self.write_space();
5682                    self.write_keyword("AS");
5683                    self.write_space();
5684                    self.generate_identifier(&pos_alias)?;
5685
5686                    // , col [, key, value ...]
5687                    for data_col in &data_aliases {
5688                        self.write(", ");
5689                        self.generate_identifier(data_col)?;
5690                    }
5691
5692                    self.write_space();
5693                    self.write_keyword("FROM");
5694                    self.write_space();
5695                    self.write_keyword("UNNEST");
5696                    self.write("(");
5697                    for (i, arg) in unnest_args.iter().enumerate() {
5698                        if i > 0 {
5699                            self.write(", ");
5700                        }
5701                        self.generate_expression(arg)?;
5702                    }
5703                    self.write(")");
5704                    self.write_space();
5705                    self.write_keyword("WITH ORDINALITY");
5706                    self.write_space();
5707                    self.write_keyword("AS");
5708                    self.write_space();
5709
5710                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
5711                    let table_alias_ident = lv
5712                        .table_alias
5713                        .clone()
5714                        .unwrap_or_else(|| Identifier::new("t"));
5715                    self.generate_identifier(&table_alias_ident)?;
5716                    self.write("(");
5717                    for (i, data_col) in data_aliases.iter().enumerate() {
5718                        if i > 0 {
5719                            self.write(", ");
5720                        }
5721                        self.generate_identifier(data_col)?;
5722                    }
5723                    self.write(", ");
5724                    self.generate_identifier(&pos_alias)?;
5725                    self.write("))");
5726                } else {
5727                    self.write_keyword("UNNEST");
5728                    self.write("(");
5729                    for (i, arg) in unnest_args.iter().enumerate() {
5730                        if i > 0 {
5731                            self.write(", ");
5732                        }
5733                        self.generate_expression(arg)?;
5734                    }
5735                    self.write(")");
5736
5737                    // Add table and column aliases for non-POSEXPLODE
5738                    if let Some(alias) = &lv.table_alias {
5739                        self.write_space();
5740                        self.write_keyword("AS");
5741                        self.write_space();
5742                        self.generate_identifier(alias)?;
5743                        if !lv.column_aliases.is_empty() {
5744                            self.write("(");
5745                            for (i, col) in lv.column_aliases.iter().enumerate() {
5746                                if i > 0 {
5747                                    self.write(", ");
5748                                }
5749                                self.generate_identifier(col)?;
5750                            }
5751                            self.write(")");
5752                        }
5753                    } else if !lv.column_aliases.is_empty() {
5754                        self.write_space();
5755                        self.write_keyword("AS");
5756                        self.write(" t(");
5757                        for (i, col) in lv.column_aliases.iter().enumerate() {
5758                            if i > 0 {
5759                                self.write(", ");
5760                            }
5761                            self.generate_identifier(col)?;
5762                        }
5763                        self.write(")");
5764                    }
5765                }
5766            } else {
5767                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
5768                if !lv.outer {
5769                    self.write_keyword("LATERAL");
5770                    self.write_space();
5771                }
5772                self.generate_expression(&lv.this)?;
5773
5774                // Add table and column aliases
5775                if let Some(alias) = &lv.table_alias {
5776                    self.write_space();
5777                    self.write_keyword("AS");
5778                    self.write_space();
5779                    self.generate_identifier(alias)?;
5780                    if !lv.column_aliases.is_empty() {
5781                        self.write("(");
5782                        for (i, col) in lv.column_aliases.iter().enumerate() {
5783                            if i > 0 {
5784                                self.write(", ");
5785                            }
5786                            self.generate_identifier(col)?;
5787                        }
5788                        self.write(")");
5789                    }
5790                } else if !lv.column_aliases.is_empty() {
5791                    self.write_space();
5792                    self.write_keyword("AS");
5793                    self.write(" t(");
5794                    for (i, col) in lv.column_aliases.iter().enumerate() {
5795                        if i > 0 {
5796                            self.write(", ");
5797                        }
5798                        self.generate_identifier(col)?;
5799                    }
5800                    self.write(")");
5801                }
5802            }
5803
5804            // For LEFT JOIN LATERAL, need ON TRUE
5805            if lv.outer {
5806                self.write_space();
5807                self.write_keyword("ON TRUE");
5808            }
5809        } else {
5810            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
5811            self.write_keyword("LATERAL VIEW");
5812            if lv.outer {
5813                self.write_space();
5814                self.write_keyword("OUTER");
5815            }
5816            if self.config.pretty {
5817                self.write_newline();
5818                self.write_indent();
5819            } else {
5820                self.write_space();
5821            }
5822            self.generate_expression(&lv.this)?;
5823
5824            // Table alias
5825            if let Some(alias) = &lv.table_alias {
5826                self.write_space();
5827                self.generate_identifier(alias)?;
5828            }
5829
5830            // Column aliases
5831            if !lv.column_aliases.is_empty() {
5832                self.write_space();
5833                self.write_keyword("AS");
5834                self.write_space();
5835                for (i, col) in lv.column_aliases.iter().enumerate() {
5836                    if i > 0 {
5837                        self.write(", ");
5838                    }
5839                    self.generate_identifier(col)?;
5840                }
5841            }
5842        }
5843
5844        Ok(())
5845    }
5846
5847    fn generate_union(&mut self, union: &Union) -> Result<()> {
5848        // WITH clause
5849        if let Some(with) = &union.with {
5850            self.generate_with(with)?;
5851            self.write_space();
5852        }
5853        self.generate_expression(&union.left)?;
5854        if self.config.pretty {
5855            self.write_newline();
5856            self.write_indent();
5857        } else {
5858            self.write_space();
5859        }
5860
5861        // BigQuery set operation modifiers: [side] [kind] UNION
5862        if let Some(side) = &union.side {
5863            self.write_keyword(side);
5864            self.write_space();
5865        }
5866        if let Some(kind) = &union.kind {
5867            self.write_keyword(kind);
5868            self.write_space();
5869        }
5870
5871        self.write_keyword("UNION");
5872        if union.all {
5873            self.write_space();
5874            self.write_keyword("ALL");
5875        } else if union.distinct {
5876            self.write_space();
5877            self.write_keyword("DISTINCT");
5878        }
5879
5880        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
5881        // DuckDB: BY NAME
5882        if union.corresponding || union.by_name {
5883            self.write_space();
5884            self.write_keyword("BY NAME");
5885        }
5886        if !union.on_columns.is_empty() {
5887            self.write_space();
5888            self.write_keyword("ON");
5889            self.write(" (");
5890            for (i, col) in union.on_columns.iter().enumerate() {
5891                if i > 0 {
5892                    self.write(", ");
5893                }
5894                self.generate_expression(col)?;
5895            }
5896            self.write(")");
5897        }
5898
5899        if self.config.pretty {
5900            self.write_newline();
5901            self.write_indent();
5902        } else {
5903            self.write_space();
5904        }
5905        self.generate_expression(&union.right)?;
5906        // ORDER BY, LIMIT, OFFSET for the set operation
5907        if let Some(order_by) = &union.order_by {
5908            if self.config.pretty {
5909                self.write_newline();
5910            } else {
5911                self.write_space();
5912            }
5913            self.write_keyword("ORDER BY");
5914            self.write_space();
5915            for (i, ordered) in order_by.expressions.iter().enumerate() {
5916                if i > 0 {
5917                    self.write(", ");
5918                }
5919                self.generate_ordered(ordered)?;
5920            }
5921        }
5922        if let Some(limit) = &union.limit {
5923            if self.config.pretty {
5924                self.write_newline();
5925            } else {
5926                self.write_space();
5927            }
5928            self.write_keyword("LIMIT");
5929            self.write_space();
5930            self.generate_expression(limit)?;
5931        }
5932        if let Some(offset) = &union.offset {
5933            if self.config.pretty {
5934                self.write_newline();
5935            } else {
5936                self.write_space();
5937            }
5938            self.write_keyword("OFFSET");
5939            self.write_space();
5940            self.generate_expression(offset)?;
5941        }
5942        // DISTRIBUTE BY (Hive/Spark)
5943        if let Some(distribute_by) = &union.distribute_by {
5944            self.write_space();
5945            self.write_keyword("DISTRIBUTE BY");
5946            self.write_space();
5947            for (i, expr) in distribute_by.expressions.iter().enumerate() {
5948                if i > 0 {
5949                    self.write(", ");
5950                }
5951                self.generate_expression(expr)?;
5952            }
5953        }
5954        // SORT BY (Hive/Spark)
5955        if let Some(sort_by) = &union.sort_by {
5956            self.write_space();
5957            self.write_keyword("SORT BY");
5958            self.write_space();
5959            for (i, ord) in sort_by.expressions.iter().enumerate() {
5960                if i > 0 {
5961                    self.write(", ");
5962                }
5963                self.generate_ordered(ord)?;
5964            }
5965        }
5966        // CLUSTER BY (Hive/Spark)
5967        if let Some(cluster_by) = &union.cluster_by {
5968            self.write_space();
5969            self.write_keyword("CLUSTER BY");
5970            self.write_space();
5971            for (i, ord) in cluster_by.expressions.iter().enumerate() {
5972                if i > 0 {
5973                    self.write(", ");
5974                }
5975                self.generate_ordered(ord)?;
5976            }
5977        }
5978        Ok(())
5979    }
5980
5981    fn generate_intersect(&mut self, intersect: &Intersect) -> Result<()> {
5982        // WITH clause
5983        if let Some(with) = &intersect.with {
5984            self.generate_with(with)?;
5985            self.write_space();
5986        }
5987        self.generate_expression(&intersect.left)?;
5988        if self.config.pretty {
5989            self.write_newline();
5990            self.write_indent();
5991        } else {
5992            self.write_space();
5993        }
5994
5995        // BigQuery set operation modifiers: [side] [kind] INTERSECT
5996        if let Some(side) = &intersect.side {
5997            self.write_keyword(side);
5998            self.write_space();
5999        }
6000        if let Some(kind) = &intersect.kind {
6001            self.write_keyword(kind);
6002            self.write_space();
6003        }
6004
6005        self.write_keyword("INTERSECT");
6006        if intersect.all {
6007            self.write_space();
6008            self.write_keyword("ALL");
6009        } else if intersect.distinct {
6010            self.write_space();
6011            self.write_keyword("DISTINCT");
6012        }
6013
6014        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6015        // DuckDB: BY NAME
6016        if intersect.corresponding || intersect.by_name {
6017            self.write_space();
6018            self.write_keyword("BY NAME");
6019        }
6020        if !intersect.on_columns.is_empty() {
6021            self.write_space();
6022            self.write_keyword("ON");
6023            self.write(" (");
6024            for (i, col) in intersect.on_columns.iter().enumerate() {
6025                if i > 0 {
6026                    self.write(", ");
6027                }
6028                self.generate_expression(col)?;
6029            }
6030            self.write(")");
6031        }
6032
6033        if self.config.pretty {
6034            self.write_newline();
6035            self.write_indent();
6036        } else {
6037            self.write_space();
6038        }
6039        self.generate_expression(&intersect.right)?;
6040        // ORDER BY, LIMIT, OFFSET for the set operation
6041        if let Some(order_by) = &intersect.order_by {
6042            if self.config.pretty {
6043                self.write_newline();
6044            } else {
6045                self.write_space();
6046            }
6047            self.write_keyword("ORDER BY");
6048            self.write_space();
6049            for (i, ordered) in order_by.expressions.iter().enumerate() {
6050                if i > 0 {
6051                    self.write(", ");
6052                }
6053                self.generate_ordered(ordered)?;
6054            }
6055        }
6056        if let Some(limit) = &intersect.limit {
6057            if self.config.pretty {
6058                self.write_newline();
6059            } else {
6060                self.write_space();
6061            }
6062            self.write_keyword("LIMIT");
6063            self.write_space();
6064            self.generate_expression(limit)?;
6065        }
6066        if let Some(offset) = &intersect.offset {
6067            if self.config.pretty {
6068                self.write_newline();
6069            } else {
6070                self.write_space();
6071            }
6072            self.write_keyword("OFFSET");
6073            self.write_space();
6074            self.generate_expression(offset)?;
6075        }
6076        // DISTRIBUTE BY (Hive/Spark)
6077        if let Some(distribute_by) = &intersect.distribute_by {
6078            self.write_space();
6079            self.write_keyword("DISTRIBUTE BY");
6080            self.write_space();
6081            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6082                if i > 0 {
6083                    self.write(", ");
6084                }
6085                self.generate_expression(expr)?;
6086            }
6087        }
6088        // SORT BY (Hive/Spark)
6089        if let Some(sort_by) = &intersect.sort_by {
6090            self.write_space();
6091            self.write_keyword("SORT BY");
6092            self.write_space();
6093            for (i, ord) in sort_by.expressions.iter().enumerate() {
6094                if i > 0 {
6095                    self.write(", ");
6096                }
6097                self.generate_ordered(ord)?;
6098            }
6099        }
6100        // CLUSTER BY (Hive/Spark)
6101        if let Some(cluster_by) = &intersect.cluster_by {
6102            self.write_space();
6103            self.write_keyword("CLUSTER BY");
6104            self.write_space();
6105            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6106                if i > 0 {
6107                    self.write(", ");
6108                }
6109                self.generate_ordered(ord)?;
6110            }
6111        }
6112        Ok(())
6113    }
6114
6115    fn generate_except(&mut self, except: &Except) -> Result<()> {
6116        use crate::dialects::DialectType;
6117
6118        // WITH clause
6119        if let Some(with) = &except.with {
6120            self.generate_with(with)?;
6121            self.write_space();
6122        }
6123
6124        self.generate_expression(&except.left)?;
6125        if self.config.pretty {
6126            self.write_newline();
6127            self.write_indent();
6128        } else {
6129            self.write_space();
6130        }
6131
6132        // BigQuery set operation modifiers: [side] [kind] EXCEPT
6133        if let Some(side) = &except.side {
6134            self.write_keyword(side);
6135            self.write_space();
6136        }
6137        if let Some(kind) = &except.kind {
6138            self.write_keyword(kind);
6139            self.write_space();
6140        }
6141
6142        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
6143        match self.config.dialect {
6144            Some(DialectType::Oracle) if !except.all => {
6145                self.write_keyword("MINUS");
6146            }
6147            Some(DialectType::ClickHouse) => {
6148                // ClickHouse: drop ALL from EXCEPT ALL
6149                self.write_keyword("EXCEPT");
6150                if except.distinct {
6151                    self.write_space();
6152                    self.write_keyword("DISTINCT");
6153                }
6154            }
6155            Some(DialectType::BigQuery) => {
6156                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
6157                self.write_keyword("EXCEPT");
6158                if except.all {
6159                    self.write_space();
6160                    self.write_keyword("ALL");
6161                } else {
6162                    self.write_space();
6163                    self.write_keyword("DISTINCT");
6164                }
6165            }
6166            _ => {
6167                self.write_keyword("EXCEPT");
6168                if except.all {
6169                    self.write_space();
6170                    self.write_keyword("ALL");
6171                } else if except.distinct {
6172                    self.write_space();
6173                    self.write_keyword("DISTINCT");
6174                }
6175            }
6176        }
6177
6178        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6179        // DuckDB: BY NAME
6180        if except.corresponding || except.by_name {
6181            self.write_space();
6182            self.write_keyword("BY NAME");
6183        }
6184        if !except.on_columns.is_empty() {
6185            self.write_space();
6186            self.write_keyword("ON");
6187            self.write(" (");
6188            for (i, col) in except.on_columns.iter().enumerate() {
6189                if i > 0 {
6190                    self.write(", ");
6191                }
6192                self.generate_expression(col)?;
6193            }
6194            self.write(")");
6195        }
6196
6197        if self.config.pretty {
6198            self.write_newline();
6199            self.write_indent();
6200        } else {
6201            self.write_space();
6202        }
6203        self.generate_expression(&except.right)?;
6204        // ORDER BY, LIMIT, OFFSET for the set operation
6205        if let Some(order_by) = &except.order_by {
6206            if self.config.pretty {
6207                self.write_newline();
6208            } else {
6209                self.write_space();
6210            }
6211            self.write_keyword("ORDER BY");
6212            self.write_space();
6213            for (i, ordered) in order_by.expressions.iter().enumerate() {
6214                if i > 0 {
6215                    self.write(", ");
6216                }
6217                self.generate_ordered(ordered)?;
6218            }
6219        }
6220        if let Some(limit) = &except.limit {
6221            if self.config.pretty {
6222                self.write_newline();
6223            } else {
6224                self.write_space();
6225            }
6226            self.write_keyword("LIMIT");
6227            self.write_space();
6228            self.generate_expression(limit)?;
6229        }
6230        if let Some(offset) = &except.offset {
6231            if self.config.pretty {
6232                self.write_newline();
6233            } else {
6234                self.write_space();
6235            }
6236            self.write_keyword("OFFSET");
6237            self.write_space();
6238            self.generate_expression(offset)?;
6239        }
6240        // DISTRIBUTE BY (Hive/Spark)
6241        if let Some(distribute_by) = &except.distribute_by {
6242            self.write_space();
6243            self.write_keyword("DISTRIBUTE BY");
6244            self.write_space();
6245            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6246                if i > 0 {
6247                    self.write(", ");
6248                }
6249                self.generate_expression(expr)?;
6250            }
6251        }
6252        // SORT BY (Hive/Spark)
6253        if let Some(sort_by) = &except.sort_by {
6254            self.write_space();
6255            self.write_keyword("SORT BY");
6256            self.write_space();
6257            for (i, ord) in sort_by.expressions.iter().enumerate() {
6258                if i > 0 {
6259                    self.write(", ");
6260                }
6261                self.generate_ordered(ord)?;
6262            }
6263        }
6264        // CLUSTER BY (Hive/Spark)
6265        if let Some(cluster_by) = &except.cluster_by {
6266            self.write_space();
6267            self.write_keyword("CLUSTER BY");
6268            self.write_space();
6269            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6270                if i > 0 {
6271                    self.write(", ");
6272                }
6273                self.generate_ordered(ord)?;
6274            }
6275        }
6276        Ok(())
6277    }
6278
6279    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
6280        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
6281        let prepend_query_cte = if insert.with.is_none() {
6282            use crate::dialects::DialectType;
6283            let should_prepend = matches!(
6284                self.config.dialect,
6285                Some(DialectType::TSQL)
6286                    | Some(DialectType::Fabric)
6287                    | Some(DialectType::Spark)
6288                    | Some(DialectType::Databricks)
6289                    | Some(DialectType::Hive)
6290            );
6291            if should_prepend {
6292                if let Some(Expression::Select(select)) = &insert.query {
6293                    select.with.clone()
6294                } else {
6295                    None
6296                }
6297            } else {
6298                None
6299            }
6300        } else {
6301            None
6302        };
6303
6304        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
6305        if let Some(with) = &insert.with {
6306            self.generate_with(with)?;
6307            self.write_space();
6308        } else if let Some(with) = &prepend_query_cte {
6309            self.generate_with(with)?;
6310            self.write_space();
6311        }
6312
6313        // Output leading comments before INSERT
6314        for comment in &insert.leading_comments {
6315            self.write_formatted_comment(comment);
6316            self.write(" ");
6317        }
6318
6319        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
6320        if let Some(dir) = &insert.directory {
6321            self.write_keyword("INSERT OVERWRITE");
6322            if dir.local {
6323                self.write_space();
6324                self.write_keyword("LOCAL");
6325            }
6326            self.write_space();
6327            self.write_keyword("DIRECTORY");
6328            self.write_space();
6329            self.write("'");
6330            self.write(&dir.path);
6331            self.write("'");
6332
6333            // ROW FORMAT clause
6334            if let Some(row_format) = &dir.row_format {
6335                self.write_space();
6336                self.write_keyword("ROW FORMAT");
6337                if row_format.delimited {
6338                    self.write_space();
6339                    self.write_keyword("DELIMITED");
6340                }
6341                if let Some(val) = &row_format.fields_terminated_by {
6342                    self.write_space();
6343                    self.write_keyword("FIELDS TERMINATED BY");
6344                    self.write_space();
6345                    self.write("'");
6346                    self.write(val);
6347                    self.write("'");
6348                }
6349                if let Some(val) = &row_format.collection_items_terminated_by {
6350                    self.write_space();
6351                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
6352                    self.write_space();
6353                    self.write("'");
6354                    self.write(val);
6355                    self.write("'");
6356                }
6357                if let Some(val) = &row_format.map_keys_terminated_by {
6358                    self.write_space();
6359                    self.write_keyword("MAP KEYS TERMINATED BY");
6360                    self.write_space();
6361                    self.write("'");
6362                    self.write(val);
6363                    self.write("'");
6364                }
6365                if let Some(val) = &row_format.lines_terminated_by {
6366                    self.write_space();
6367                    self.write_keyword("LINES TERMINATED BY");
6368                    self.write_space();
6369                    self.write("'");
6370                    self.write(val);
6371                    self.write("'");
6372                }
6373                if let Some(val) = &row_format.null_defined_as {
6374                    self.write_space();
6375                    self.write_keyword("NULL DEFINED AS");
6376                    self.write_space();
6377                    self.write("'");
6378                    self.write(val);
6379                    self.write("'");
6380                }
6381            }
6382
6383            // STORED AS clause
6384            if let Some(format) = &dir.stored_as {
6385                self.write_space();
6386                self.write_keyword("STORED AS");
6387                self.write_space();
6388                self.write_keyword(format);
6389            }
6390
6391            // Query (SELECT statement)
6392            if let Some(query) = &insert.query {
6393                self.write_space();
6394                self.generate_expression(query)?;
6395            }
6396
6397            return Ok(());
6398        }
6399
6400        if insert.is_replace {
6401            // MySQL/SQLite REPLACE INTO statement
6402            self.write_keyword("REPLACE INTO");
6403        } else if insert.overwrite {
6404            // Use dialect-specific INSERT OVERWRITE format
6405            self.write_keyword("INSERT");
6406            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6407            if let Some(ref hint) = insert.hint {
6408                self.generate_hint(hint)?;
6409            }
6410            self.write(&self.config.insert_overwrite.to_uppercase());
6411        } else if let Some(ref action) = insert.conflict_action {
6412            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
6413            self.write_keyword("INSERT OR");
6414            self.write_space();
6415            self.write_keyword(action);
6416            self.write_space();
6417            self.write_keyword("INTO");
6418        } else if insert.ignore {
6419            // MySQL INSERT IGNORE syntax
6420            self.write_keyword("INSERT IGNORE INTO");
6421        } else {
6422            self.write_keyword("INSERT");
6423            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6424            if let Some(ref hint) = insert.hint {
6425                self.generate_hint(hint)?;
6426            }
6427            self.write_space();
6428            self.write_keyword("INTO");
6429        }
6430        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
6431        if let Some(ref func) = insert.function_target {
6432            self.write_space();
6433            self.write_keyword("FUNCTION");
6434            self.write_space();
6435            self.generate_expression(func)?;
6436        } else {
6437            self.write_space();
6438            self.generate_table(&insert.table)?;
6439        }
6440
6441        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
6442        if let Some(ref alias) = insert.alias {
6443            self.write_space();
6444            if insert.alias_explicit_as {
6445                self.write_keyword("AS");
6446                self.write_space();
6447            }
6448            self.generate_identifier(alias)?;
6449        }
6450
6451        // IF EXISTS clause (Hive)
6452        if insert.if_exists {
6453            self.write_space();
6454            self.write_keyword("IF EXISTS");
6455        }
6456
6457        // REPLACE WHERE clause (Databricks)
6458        if let Some(ref replace_where) = insert.replace_where {
6459            if self.config.pretty {
6460                self.write_newline();
6461                self.write_indent();
6462            } else {
6463                self.write_space();
6464            }
6465            self.write_keyword("REPLACE WHERE");
6466            self.write_space();
6467            self.generate_expression(replace_where)?;
6468        }
6469
6470        // Generate PARTITION clause if present
6471        if !insert.partition.is_empty() {
6472            self.write_space();
6473            self.write_keyword("PARTITION");
6474            self.write("(");
6475            for (i, (col, val)) in insert.partition.iter().enumerate() {
6476                if i > 0 {
6477                    self.write(", ");
6478                }
6479                self.generate_identifier(col)?;
6480                if let Some(v) = val {
6481                    self.write(" = ");
6482                    self.generate_expression(v)?;
6483                }
6484            }
6485            self.write(")");
6486        }
6487
6488        // ClickHouse: PARTITION BY expr
6489        if let Some(ref partition_by) = insert.partition_by {
6490            self.write_space();
6491            self.write_keyword("PARTITION BY");
6492            self.write_space();
6493            self.generate_expression(partition_by)?;
6494        }
6495
6496        // ClickHouse: SETTINGS key = val, ...
6497        if !insert.settings.is_empty() {
6498            self.write_space();
6499            self.write_keyword("SETTINGS");
6500            self.write_space();
6501            for (i, setting) in insert.settings.iter().enumerate() {
6502                if i > 0 {
6503                    self.write(", ");
6504                }
6505                self.generate_expression(setting)?;
6506            }
6507        }
6508
6509        if !insert.columns.is_empty() {
6510            if insert.alias.is_some() && insert.alias_explicit_as {
6511                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
6512                self.write("(");
6513            } else {
6514                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
6515                self.write(" (");
6516            }
6517            for (i, col) in insert.columns.iter().enumerate() {
6518                if i > 0 {
6519                    self.write(", ");
6520                }
6521                self.generate_identifier(col)?;
6522            }
6523            self.write(")");
6524        }
6525
6526        // OUTPUT clause (TSQL)
6527        if let Some(ref output) = insert.output {
6528            self.generate_output_clause(output)?;
6529        }
6530
6531        // BY NAME modifier (DuckDB)
6532        if insert.by_name {
6533            self.write_space();
6534            self.write_keyword("BY NAME");
6535        }
6536
6537        if insert.default_values {
6538            self.write_space();
6539            self.write_keyword("DEFAULT VALUES");
6540        } else if let Some(query) = &insert.query {
6541            if self.config.pretty {
6542                self.write_newline();
6543            } else {
6544                self.write_space();
6545            }
6546            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
6547            if prepend_query_cte.is_some() {
6548                if let Expression::Select(select) = query {
6549                    let mut select_no_with = select.clone();
6550                    select_no_with.with = None;
6551                    self.generate_select(&select_no_with)?;
6552                } else {
6553                    self.generate_expression(query)?;
6554                }
6555            } else {
6556                self.generate_expression(query)?;
6557            }
6558        } else if !insert.values.is_empty() {
6559            if self.config.pretty {
6560                // Pretty printing: VALUES on new line, each tuple indented
6561                self.write_newline();
6562                self.write_keyword("VALUES");
6563                self.write_newline();
6564                self.indent_level += 1;
6565                for (i, row) in insert.values.iter().enumerate() {
6566                    if i > 0 {
6567                        self.write(",");
6568                        self.write_newline();
6569                    }
6570                    self.write_indent();
6571                    self.write("(");
6572                    for (j, val) in row.iter().enumerate() {
6573                        if j > 0 {
6574                            self.write(", ");
6575                        }
6576                        self.generate_expression(val)?;
6577                    }
6578                    self.write(")");
6579                }
6580                self.indent_level -= 1;
6581            } else {
6582                // Non-pretty: single line
6583                self.write_space();
6584                self.write_keyword("VALUES");
6585                for (i, row) in insert.values.iter().enumerate() {
6586                    if i > 0 {
6587                        self.write(",");
6588                    }
6589                    self.write(" (");
6590                    for (j, val) in row.iter().enumerate() {
6591                        if j > 0 {
6592                            self.write(", ");
6593                        }
6594                        self.generate_expression(val)?;
6595                    }
6596                    self.write(")");
6597                }
6598            }
6599        }
6600
6601        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
6602        if let Some(ref source) = insert.source {
6603            self.write_space();
6604            self.write_keyword("TABLE");
6605            self.write_space();
6606            self.generate_expression(source)?;
6607        }
6608
6609        // Source alias (MySQL: VALUES (...) AS new_data)
6610        if let Some(alias) = &insert.source_alias {
6611            self.write_space();
6612            self.write_keyword("AS");
6613            self.write_space();
6614            self.generate_identifier(alias)?;
6615        }
6616
6617        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
6618        if let Some(on_conflict) = &insert.on_conflict {
6619            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
6620                self.write_space();
6621                self.generate_expression(on_conflict)?;
6622            }
6623        }
6624
6625        // RETURNING clause
6626        if !insert.returning.is_empty() {
6627            self.write_space();
6628            self.write_keyword("RETURNING");
6629            self.write_space();
6630            for (i, expr) in insert.returning.iter().enumerate() {
6631                if i > 0 {
6632                    self.write(", ");
6633                }
6634                self.generate_expression(expr)?;
6635            }
6636        }
6637
6638        Ok(())
6639    }
6640
6641    fn generate_update(&mut self, update: &Update) -> Result<()> {
6642        // Output leading comments before UPDATE
6643        for comment in &update.leading_comments {
6644            self.write_formatted_comment(comment);
6645            self.write(" ");
6646        }
6647
6648        // WITH clause (CTEs)
6649        if let Some(ref with) = update.with {
6650            self.generate_with(with)?;
6651            self.write_space();
6652        }
6653
6654        self.write_keyword("UPDATE");
6655        self.write_space();
6656        self.generate_table(&update.table)?;
6657
6658        let mysql_like_update_from = matches!(
6659            self.config.dialect,
6660            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
6661        ) && update.from_clause.is_some();
6662
6663        let mut set_pairs = update.set.clone();
6664
6665        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
6666        let mut pre_set_joins = update.table_joins.clone();
6667        if mysql_like_update_from {
6668            let target_name = update
6669                .table
6670                .alias
6671                .as_ref()
6672                .map(|a| a.name.clone())
6673                .unwrap_or_else(|| update.table.name.name.clone());
6674
6675            for (col, _) in &mut set_pairs {
6676                if !col.name.contains('.') {
6677                    col.name = format!("{}.{}", target_name, col.name);
6678                }
6679            }
6680
6681            if let Some(from_clause) = &update.from_clause {
6682                for table_expr in &from_clause.expressions {
6683                    pre_set_joins.push(crate::expressions::Join {
6684                        this: table_expr.clone(),
6685                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
6686                            value: true,
6687                        })),
6688                        using: Vec::new(),
6689                        kind: crate::expressions::JoinKind::Inner,
6690                        use_inner_keyword: false,
6691                        use_outer_keyword: false,
6692                        deferred_condition: false,
6693                        join_hint: None,
6694                        match_condition: None,
6695                        pivots: Vec::new(),
6696                        comments: Vec::new(),
6697                        nesting_group: 0,
6698                        directed: false,
6699                    });
6700                }
6701            }
6702            for join in &update.from_joins {
6703                let mut join = join.clone();
6704                if join.on.is_none() && join.using.is_empty() {
6705                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
6706                        value: true,
6707                    }));
6708                }
6709                pre_set_joins.push(join);
6710            }
6711        }
6712
6713        // Extra tables for multi-table UPDATE (MySQL syntax)
6714        for extra_table in &update.extra_tables {
6715            self.write(", ");
6716            self.generate_table(extra_table)?;
6717        }
6718
6719        // JOINs attached to the table list (MySQL multi-table syntax)
6720        for join in &pre_set_joins {
6721            // generate_join already adds a leading space
6722            self.generate_join(join)?;
6723        }
6724
6725        // Teradata: FROM clause comes before SET
6726        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
6727        if teradata_from_before_set && !mysql_like_update_from {
6728            if let Some(ref from_clause) = update.from_clause {
6729                self.write_space();
6730                self.write_keyword("FROM");
6731                self.write_space();
6732                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
6733                    if i > 0 {
6734                        self.write(", ");
6735                    }
6736                    self.generate_expression(table_expr)?;
6737                }
6738            }
6739            for join in &update.from_joins {
6740                self.generate_join(join)?;
6741            }
6742        }
6743
6744        self.write_space();
6745        self.write_keyword("SET");
6746        self.write_space();
6747
6748        for (i, (col, val)) in set_pairs.iter().enumerate() {
6749            if i > 0 {
6750                self.write(", ");
6751            }
6752            self.generate_identifier(col)?;
6753            self.write(" = ");
6754            self.generate_expression(val)?;
6755        }
6756
6757        // OUTPUT clause (TSQL)
6758        if let Some(ref output) = update.output {
6759            self.generate_output_clause(output)?;
6760        }
6761
6762        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
6763        if !mysql_like_update_from && !teradata_from_before_set {
6764            if let Some(ref from_clause) = update.from_clause {
6765                self.write_space();
6766                self.write_keyword("FROM");
6767                self.write_space();
6768                // Generate each table in the FROM clause
6769                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
6770                    if i > 0 {
6771                        self.write(", ");
6772                    }
6773                    self.generate_expression(table_expr)?;
6774                }
6775            }
6776        }
6777
6778        if !mysql_like_update_from && !teradata_from_before_set {
6779            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
6780            for join in &update.from_joins {
6781                self.generate_join(join)?;
6782            }
6783        }
6784
6785        if let Some(where_clause) = &update.where_clause {
6786            self.write_space();
6787            self.write_keyword("WHERE");
6788            self.write_space();
6789            self.generate_expression(&where_clause.this)?;
6790        }
6791
6792        // RETURNING clause
6793        if !update.returning.is_empty() {
6794            self.write_space();
6795            self.write_keyword("RETURNING");
6796            self.write_space();
6797            for (i, expr) in update.returning.iter().enumerate() {
6798                if i > 0 {
6799                    self.write(", ");
6800                }
6801                self.generate_expression(expr)?;
6802            }
6803        }
6804
6805        // ORDER BY clause (MySQL)
6806        if let Some(ref order_by) = update.order_by {
6807            self.write_space();
6808            self.generate_order_by(order_by)?;
6809        }
6810
6811        // LIMIT clause (MySQL)
6812        if let Some(ref limit) = update.limit {
6813            self.write_space();
6814            self.write_keyword("LIMIT");
6815            self.write_space();
6816            self.generate_expression(limit)?;
6817        }
6818
6819        Ok(())
6820    }
6821
6822    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
6823        // Output WITH clause if present
6824        if let Some(with) = &delete.with {
6825            self.generate_with(with)?;
6826            self.write_space();
6827        }
6828
6829        // Output leading comments before DELETE
6830        for comment in &delete.leading_comments {
6831            self.write_formatted_comment(comment);
6832            self.write(" ");
6833        }
6834
6835        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
6836        if !delete.tables.is_empty() && !delete.tables_from_using {
6837            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
6838            self.write_keyword("DELETE");
6839            self.write_space();
6840            for (i, tbl) in delete.tables.iter().enumerate() {
6841                if i > 0 {
6842                    self.write(", ");
6843                }
6844                self.generate_table(tbl)?;
6845            }
6846            // TSQL: OUTPUT clause between target table and FROM
6847            if let Some(ref output) = delete.output {
6848                self.generate_output_clause(output)?;
6849            }
6850            self.write_space();
6851            self.write_keyword("FROM");
6852            self.write_space();
6853            self.generate_table(&delete.table)?;
6854        } else if !delete.tables.is_empty() && delete.tables_from_using {
6855            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
6856            self.write_keyword("DELETE FROM");
6857            self.write_space();
6858            for (i, tbl) in delete.tables.iter().enumerate() {
6859                if i > 0 {
6860                    self.write(", ");
6861                }
6862                self.generate_table(tbl)?;
6863            }
6864        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
6865            // BigQuery-style DELETE without FROM keyword
6866            self.write_keyword("DELETE");
6867            self.write_space();
6868            self.generate_table(&delete.table)?;
6869        } else {
6870            self.write_keyword("DELETE FROM");
6871            self.write_space();
6872            self.generate_table(&delete.table)?;
6873        }
6874
6875        // ClickHouse: ON CLUSTER clause
6876        if let Some(ref on_cluster) = delete.on_cluster {
6877            self.write_space();
6878            self.generate_on_cluster(on_cluster)?;
6879        }
6880
6881        // FORCE INDEX hint (MySQL)
6882        if let Some(ref idx) = delete.force_index {
6883            self.write_space();
6884            self.write_keyword("FORCE INDEX");
6885            self.write(" (");
6886            self.write(idx);
6887            self.write(")");
6888        }
6889
6890        // Optional alias
6891        if let Some(ref alias) = delete.alias {
6892            self.write_space();
6893            if delete.alias_explicit_as
6894                || matches!(self.config.dialect, Some(DialectType::BigQuery))
6895            {
6896                self.write_keyword("AS");
6897                self.write_space();
6898            }
6899            self.generate_identifier(alias)?;
6900        }
6901
6902        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
6903        if !delete.tables_from_using {
6904            for join in &delete.joins {
6905                self.generate_join(join)?;
6906            }
6907        }
6908
6909        // USING clause (PostgreSQL/DuckDB/MySQL)
6910        if !delete.using.is_empty() {
6911            self.write_space();
6912            self.write_keyword("USING");
6913            for (i, table) in delete.using.iter().enumerate() {
6914                if i > 0 {
6915                    self.write(",");
6916                }
6917                self.write_space();
6918                // Check if the table has subquery hints (DuckDB USING with subquery)
6919                if !table.hints.is_empty() && table.name.is_empty() {
6920                    // Subquery in USING: (VALUES ...) AS alias(cols)
6921                    self.generate_expression(&table.hints[0])?;
6922                    if let Some(ref alias) = table.alias {
6923                        self.write_space();
6924                        if table.alias_explicit_as {
6925                            self.write_keyword("AS");
6926                            self.write_space();
6927                        }
6928                        self.generate_identifier(alias)?;
6929                        if !table.column_aliases.is_empty() {
6930                            self.write("(");
6931                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
6932                                if j > 0 {
6933                                    self.write(", ");
6934                                }
6935                                self.generate_identifier(col_alias)?;
6936                            }
6937                            self.write(")");
6938                        }
6939                    }
6940                } else {
6941                    self.generate_table(table)?;
6942                }
6943            }
6944        }
6945
6946        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
6947        if delete.tables_from_using {
6948            for join in &delete.joins {
6949                self.generate_join(join)?;
6950            }
6951        }
6952
6953        // OUTPUT clause (TSQL) - only if not already emitted in the early position
6954        let output_already_emitted =
6955            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
6956        if !output_already_emitted {
6957            if let Some(ref output) = delete.output {
6958                self.generate_output_clause(output)?;
6959            }
6960        }
6961
6962        if let Some(where_clause) = &delete.where_clause {
6963            self.write_space();
6964            self.write_keyword("WHERE");
6965            self.write_space();
6966            self.generate_expression(&where_clause.this)?;
6967        }
6968
6969        // ORDER BY clause (MySQL)
6970        if let Some(ref order_by) = delete.order_by {
6971            self.write_space();
6972            self.generate_order_by(order_by)?;
6973        }
6974
6975        // LIMIT clause (MySQL)
6976        if let Some(ref limit) = delete.limit {
6977            self.write_space();
6978            self.write_keyword("LIMIT");
6979            self.write_space();
6980            self.generate_expression(limit)?;
6981        }
6982
6983        // RETURNING clause (PostgreSQL)
6984        if !delete.returning.is_empty() {
6985            self.write_space();
6986            self.write_keyword("RETURNING");
6987            self.write_space();
6988            for (i, expr) in delete.returning.iter().enumerate() {
6989                if i > 0 {
6990                    self.write(", ");
6991                }
6992                self.generate_expression(expr)?;
6993            }
6994        }
6995
6996        Ok(())
6997    }
6998
6999    // ==================== DDL Generation ====================
7000
7001    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
7002        // Athena: Determine if this is Hive-style DDL or Trino-style DML
7003        // CREATE TABLE AS SELECT uses Trino (double quotes)
7004        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
7005        let saved_athena_hive_context = self.athena_hive_context;
7006        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
7007        if matches!(
7008            self.config.dialect,
7009            Some(crate::dialects::DialectType::Athena)
7010        ) {
7011            // Use Hive context if:
7012            // 1. It's an EXTERNAL table, OR
7013            // 2. There's no AS SELECT clause
7014            let is_external = ct
7015                .table_modifier
7016                .as_ref()
7017                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
7018                .unwrap_or(false);
7019            let has_as_select = ct.as_select.is_some();
7020            self.athena_hive_context = is_external || !has_as_select;
7021        }
7022
7023        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
7024        if matches!(
7025            self.config.dialect,
7026            Some(crate::dialects::DialectType::TSQL)
7027        ) {
7028            if let Some(ref query) = ct.as_select {
7029                // Output WITH CTE clause if present
7030                if let Some(with_cte) = &ct.with_cte {
7031                    self.generate_with(with_cte)?;
7032                    self.write_space();
7033                }
7034
7035                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
7036                self.write_keyword("SELECT");
7037                self.write(" * ");
7038                self.write_keyword("INTO");
7039                self.write_space();
7040
7041                // If temporary, prefix with # for TSQL temp table
7042                if ct.temporary {
7043                    self.write("#");
7044                }
7045                self.generate_table(&ct.name)?;
7046
7047                self.write_space();
7048                self.write_keyword("FROM");
7049                self.write(" (");
7050                // For TSQL, add aliases to select columns to preserve column names
7051                let aliased_query = Self::add_column_aliases_to_query(query.clone());
7052                self.generate_expression(&aliased_query)?;
7053                self.write(") ");
7054                self.write_keyword("AS");
7055                self.write(" temp");
7056                return Ok(());
7057            }
7058        }
7059
7060        // Output WITH CTE clause if present
7061        if let Some(with_cte) = &ct.with_cte {
7062            self.generate_with(with_cte)?;
7063            self.write_space();
7064        }
7065
7066        // Output leading comments before CREATE
7067        for comment in &ct.leading_comments {
7068            self.write_formatted_comment(comment);
7069            self.write(" ");
7070        }
7071        self.write_keyword("CREATE");
7072
7073        if ct.or_replace {
7074            self.write_space();
7075            self.write_keyword("OR REPLACE");
7076        }
7077
7078        if ct.temporary {
7079            self.write_space();
7080            // Oracle uses GLOBAL TEMPORARY TABLE syntax
7081            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
7082                self.write_keyword("GLOBAL TEMPORARY");
7083            } else {
7084                self.write_keyword("TEMPORARY");
7085            }
7086        }
7087
7088        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
7089        let is_dictionary = ct
7090            .table_modifier
7091            .as_ref()
7092            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
7093            .unwrap_or(false);
7094        if let Some(ref modifier) = ct.table_modifier {
7095            // TRANSIENT is Snowflake-specific - skip for other dialects
7096            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
7097                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
7098            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
7099            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
7100                || modifier.eq_ignore_ascii_case("SET")
7101                || modifier.eq_ignore_ascii_case("MULTISET")
7102                || modifier.to_uppercase().contains("VOLATILE")
7103                || modifier.to_uppercase().starts_with("SET ")
7104                || modifier.to_uppercase().starts_with("MULTISET ");
7105            let skip_teradata =
7106                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
7107            if !skip_transient && !skip_teradata {
7108                self.write_space();
7109                self.write_keyword(modifier);
7110            }
7111        }
7112
7113        if !is_dictionary {
7114            self.write_space();
7115            self.write_keyword("TABLE");
7116        }
7117
7118        if ct.if_not_exists {
7119            self.write_space();
7120            self.write_keyword("IF NOT EXISTS");
7121        }
7122
7123        self.write_space();
7124        self.generate_table(&ct.name)?;
7125
7126        // ClickHouse: ON CLUSTER clause
7127        if let Some(ref on_cluster) = ct.on_cluster {
7128            self.write_space();
7129            self.generate_on_cluster(on_cluster)?;
7130        }
7131
7132        // Teradata: options after table name before column list (comma-separated)
7133        if matches!(
7134            self.config.dialect,
7135            Some(crate::dialects::DialectType::Teradata)
7136        ) && !ct.teradata_post_name_options.is_empty()
7137        {
7138            for opt in &ct.teradata_post_name_options {
7139                self.write(", ");
7140                self.write(opt);
7141            }
7142        }
7143
7144        // Snowflake: COPY GRANTS clause
7145        if ct.copy_grants {
7146            self.write_space();
7147            self.write_keyword("COPY GRANTS");
7148        }
7149
7150        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
7151        if let Some(ref using_template) = ct.using_template {
7152            self.write_space();
7153            self.write_keyword("USING TEMPLATE");
7154            self.write_space();
7155            self.generate_expression(using_template)?;
7156            return Ok(());
7157        }
7158
7159        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
7160        if let Some(ref clone_source) = ct.clone_source {
7161            self.write_space();
7162            if ct.is_copy && self.config.supports_table_copy {
7163                // BigQuery uses COPY
7164                self.write_keyword("COPY");
7165            } else if ct.shallow_clone {
7166                self.write_keyword("SHALLOW CLONE");
7167            } else {
7168                self.write_keyword("CLONE");
7169            }
7170            self.write_space();
7171            self.generate_table(clone_source)?;
7172            // Generate AT/BEFORE time travel clause (stored as Raw expression)
7173            if let Some(ref at_clause) = ct.clone_at_clause {
7174                self.write_space();
7175                self.generate_expression(at_clause)?;
7176            }
7177            return Ok(());
7178        }
7179
7180        // Handle PARTITION OF property
7181        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
7182        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
7183        if let Some(ref partition_of) = ct.partition_of {
7184            self.write_space();
7185
7186            // Extract the PartitionedOfProperty parts to generate them separately
7187            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
7188                // Output: PARTITION OF <table>
7189                self.write_keyword("PARTITION OF");
7190                self.write_space();
7191                self.generate_expression(&pop.this)?;
7192
7193                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
7194                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7195                    self.write(" (");
7196                    let mut first = true;
7197                    for col in &ct.columns {
7198                        if !first {
7199                            self.write(", ");
7200                        }
7201                        first = false;
7202                        self.generate_column_def(col)?;
7203                    }
7204                    for constraint in &ct.constraints {
7205                        if !first {
7206                            self.write(", ");
7207                        }
7208                        first = false;
7209                        self.generate_table_constraint(constraint)?;
7210                    }
7211                    self.write(")");
7212                }
7213
7214                // Output partition bound spec: FOR VALUES ... or DEFAULT
7215                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
7216                    self.write_space();
7217                    self.write_keyword("FOR VALUES");
7218                    self.write_space();
7219                    self.generate_expression(&pop.expression)?;
7220                } else {
7221                    self.write_space();
7222                    self.write_keyword("DEFAULT");
7223                }
7224            } else {
7225                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
7226                self.generate_expression(partition_of)?;
7227
7228                // Output columns/constraints if present
7229                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7230                    self.write(" (");
7231                    let mut first = true;
7232                    for col in &ct.columns {
7233                        if !first {
7234                            self.write(", ");
7235                        }
7236                        first = false;
7237                        self.generate_column_def(col)?;
7238                    }
7239                    for constraint in &ct.constraints {
7240                        if !first {
7241                            self.write(", ");
7242                        }
7243                        first = false;
7244                        self.generate_table_constraint(constraint)?;
7245                    }
7246                    self.write(")");
7247                }
7248            }
7249
7250            // Output table properties (e.g., PARTITION BY RANGE(population))
7251            for prop in &ct.properties {
7252                self.write_space();
7253                self.generate_expression(prop)?;
7254            }
7255
7256            return Ok(());
7257        }
7258
7259        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
7260        // This matches Python sqlglot's behavior for SQLite dialect
7261        self.sqlite_inline_pk_columns.clear();
7262        if matches!(
7263            self.config.dialect,
7264            Some(crate::dialects::DialectType::SQLite)
7265        ) {
7266            for constraint in &ct.constraints {
7267                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7268                    // Only inline if: single column, no constraint name, and column exists in table
7269                    if columns.len() == 1 && name.is_none() {
7270                        let pk_col_name = columns[0].name.to_lowercase();
7271                        // Check if this column exists in the table
7272                        if ct
7273                            .columns
7274                            .iter()
7275                            .any(|c| c.name.name.to_lowercase() == pk_col_name)
7276                        {
7277                            self.sqlite_inline_pk_columns.insert(pk_col_name);
7278                        }
7279                    }
7280                }
7281            }
7282        }
7283
7284        // Output columns if present (even for CTAS with columns)
7285        if !ct.columns.is_empty() {
7286            if self.config.pretty {
7287                // Pretty print: each column on new line
7288                self.write(" (");
7289                self.write_newline();
7290                self.indent_level += 1;
7291                for (i, col) in ct.columns.iter().enumerate() {
7292                    if i > 0 {
7293                        self.write(",");
7294                        self.write_newline();
7295                    }
7296                    self.write_indent();
7297                    self.generate_column_def(col)?;
7298                }
7299                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7300                for constraint in &ct.constraints {
7301                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7302                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7303                        if columns.len() == 1
7304                            && name.is_none()
7305                            && self
7306                                .sqlite_inline_pk_columns
7307                                .contains(&columns[0].name.to_lowercase())
7308                        {
7309                            continue;
7310                        }
7311                    }
7312                    self.write(",");
7313                    self.write_newline();
7314                    self.write_indent();
7315                    self.generate_table_constraint(constraint)?;
7316                }
7317                self.indent_level -= 1;
7318                self.write_newline();
7319                self.write(")");
7320            } else {
7321                self.write(" (");
7322                for (i, col) in ct.columns.iter().enumerate() {
7323                    if i > 0 {
7324                        self.write(", ");
7325                    }
7326                    self.generate_column_def(col)?;
7327                }
7328                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7329                let mut first_constraint = true;
7330                for constraint in &ct.constraints {
7331                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7332                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7333                        if columns.len() == 1
7334                            && name.is_none()
7335                            && self
7336                                .sqlite_inline_pk_columns
7337                                .contains(&columns[0].name.to_lowercase())
7338                        {
7339                            continue;
7340                        }
7341                    }
7342                    if first_constraint {
7343                        self.write(", ");
7344                        first_constraint = false;
7345                    } else {
7346                        self.write(", ");
7347                    }
7348                    self.generate_table_constraint(constraint)?;
7349                }
7350                self.write(")");
7351            }
7352        } else if !ct.constraints.is_empty() {
7353            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
7354            let has_like_only = ct
7355                .constraints
7356                .iter()
7357                .all(|c| matches!(c, TableConstraint::Like { .. }));
7358            let has_tags_only = ct
7359                .constraints
7360                .iter()
7361                .all(|c| matches!(c, TableConstraint::Tags(_)));
7362            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
7363            // Most dialects: CREATE TABLE A LIKE B (no parens)
7364            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
7365            let is_pg_like = matches!(
7366                self.config.dialect,
7367                Some(crate::dialects::DialectType::PostgreSQL)
7368                    | Some(crate::dialects::DialectType::CockroachDB)
7369                    | Some(crate::dialects::DialectType::Materialize)
7370                    | Some(crate::dialects::DialectType::RisingWave)
7371                    | Some(crate::dialects::DialectType::Redshift)
7372                    | Some(crate::dialects::DialectType::Presto)
7373                    | Some(crate::dialects::DialectType::Trino)
7374                    | Some(crate::dialects::DialectType::Athena)
7375            );
7376            let use_parens = if has_like_only {
7377                is_pg_like
7378            } else {
7379                !has_tags_only
7380            };
7381            if self.config.pretty && use_parens {
7382                self.write(" (");
7383                self.write_newline();
7384                self.indent_level += 1;
7385                for (i, constraint) in ct.constraints.iter().enumerate() {
7386                    if i > 0 {
7387                        self.write(",");
7388                        self.write_newline();
7389                    }
7390                    self.write_indent();
7391                    self.generate_table_constraint(constraint)?;
7392                }
7393                self.indent_level -= 1;
7394                self.write_newline();
7395                self.write(")");
7396            } else {
7397                if use_parens {
7398                    self.write(" (");
7399                } else {
7400                    self.write_space();
7401                }
7402                for (i, constraint) in ct.constraints.iter().enumerate() {
7403                    if i > 0 {
7404                        self.write(", ");
7405                    }
7406                    self.generate_table_constraint(constraint)?;
7407                }
7408                if use_parens {
7409                    self.write(")");
7410                }
7411            }
7412        }
7413
7414        // TSQL ON filegroup or ON filegroup (partition_column) clause
7415        if let Some(ref on_prop) = ct.on_property {
7416            self.write(" ");
7417            self.write_keyword("ON");
7418            self.write(" ");
7419            self.generate_expression(&on_prop.this)?;
7420        }
7421
7422        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
7423        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
7424        if !is_clickhouse {
7425            for prop in &ct.properties {
7426                if let Expression::SchemaCommentProperty(_) = prop {
7427                    if self.config.pretty {
7428                        self.write_newline();
7429                    } else {
7430                        self.write_space();
7431                    }
7432                    self.generate_expression(prop)?;
7433                }
7434            }
7435        }
7436
7437        // WITH properties (output after columns if columns exist, otherwise before AS)
7438        if !ct.with_properties.is_empty() {
7439            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
7440            let is_snowflake_special_table = matches!(
7441                self.config.dialect,
7442                Some(crate::dialects::DialectType::Snowflake)
7443            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
7444                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
7445            if is_snowflake_special_table {
7446                for (key, value) in &ct.with_properties {
7447                    self.write_space();
7448                    self.write(key);
7449                    self.write("=");
7450                    self.write(value);
7451                }
7452            } else if self.config.pretty {
7453                self.write_newline();
7454                self.write_keyword("WITH");
7455                self.write(" (");
7456                self.write_newline();
7457                self.indent_level += 1;
7458                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
7459                    if i > 0 {
7460                        self.write(",");
7461                        self.write_newline();
7462                    }
7463                    self.write_indent();
7464                    self.write(key);
7465                    self.write("=");
7466                    self.write(value);
7467                }
7468                self.indent_level -= 1;
7469                self.write_newline();
7470                self.write(")");
7471            } else {
7472                self.write_space();
7473                self.write_keyword("WITH");
7474                self.write(" (");
7475                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
7476                    if i > 0 {
7477                        self.write(", ");
7478                    }
7479                    self.write(key);
7480                    self.write("=");
7481                    self.write(value);
7482                }
7483                self.write(")");
7484            }
7485        }
7486
7487        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
7488            if is_clickhouse && ct.as_select.is_some() {
7489                let mut pre = Vec::new();
7490                let mut post = Vec::new();
7491                for prop in &ct.properties {
7492                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
7493                        post.push(prop);
7494                    } else {
7495                        pre.push(prop);
7496                    }
7497                }
7498                (pre, post)
7499            } else {
7500                (ct.properties.iter().collect(), Vec::new())
7501            };
7502
7503        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
7504        for prop in pre_as_properties {
7505            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
7506            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
7507                continue;
7508            }
7509            if self.config.pretty {
7510                self.write_newline();
7511            } else {
7512                self.write_space();
7513            }
7514            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
7515            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
7516            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
7517            if let Expression::Properties(props) = prop {
7518                let is_hive_dialect = matches!(
7519                    self.config.dialect,
7520                    Some(crate::dialects::DialectType::Hive)
7521                        | Some(crate::dialects::DialectType::Spark)
7522                        | Some(crate::dialects::DialectType::Databricks)
7523                        | Some(crate::dialects::DialectType::Athena)
7524                );
7525                let is_doris_starrocks = matches!(
7526                    self.config.dialect,
7527                    Some(crate::dialects::DialectType::Doris)
7528                        | Some(crate::dialects::DialectType::StarRocks)
7529                );
7530                if is_hive_dialect {
7531                    self.generate_tblproperties_clause(&props.expressions)?;
7532                } else if is_doris_starrocks {
7533                    self.generate_properties_clause(&props.expressions)?;
7534                } else {
7535                    self.generate_options_clause(&props.expressions)?;
7536                }
7537            } else {
7538                self.generate_expression(prop)?;
7539            }
7540        }
7541
7542        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
7543        for prop in &ct.post_table_properties {
7544            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
7545                self.write(" WITH(");
7546                self.generate_system_versioning_content(svp)?;
7547                self.write(")");
7548            } else if let Expression::Properties(props) = prop {
7549                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
7550                let is_doris_starrocks = matches!(
7551                    self.config.dialect,
7552                    Some(crate::dialects::DialectType::Doris)
7553                        | Some(crate::dialects::DialectType::StarRocks)
7554                );
7555                self.write_space();
7556                if is_doris_starrocks {
7557                    self.generate_properties_clause(&props.expressions)?;
7558                } else {
7559                    self.generate_options_clause(&props.expressions)?;
7560                }
7561            } else {
7562                self.write_space();
7563                self.generate_expression(prop)?;
7564            }
7565        }
7566
7567        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
7568        // Only output for StarRocks target
7569        if let Some(ref rollup) = ct.rollup {
7570            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
7571                self.write_space();
7572                self.generate_rollup_property(rollup)?;
7573            }
7574        }
7575
7576        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
7577        // Only output for MySQL-compatible dialects; strip for others during transpilation
7578        // COMMENT is also used by Hive/Spark so we selectively preserve it
7579        let is_mysql_compatible = matches!(
7580            self.config.dialect,
7581            Some(DialectType::MySQL)
7582                | Some(DialectType::SingleStore)
7583                | Some(DialectType::Doris)
7584                | Some(DialectType::StarRocks)
7585                | None
7586        );
7587        let is_hive_compatible = matches!(
7588            self.config.dialect,
7589            Some(DialectType::Hive)
7590                | Some(DialectType::Spark)
7591                | Some(DialectType::Databricks)
7592                | Some(DialectType::Athena)
7593        );
7594        let mysql_pretty_options =
7595            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
7596        for (key, value) in &ct.mysql_table_options {
7597            // Skip non-MySQL-specific options for non-MySQL targets
7598            let should_output = if is_mysql_compatible {
7599                true
7600            } else if is_hive_compatible && key == "COMMENT" {
7601                true // COMMENT is valid in Hive/Spark table definitions
7602            } else {
7603                false
7604            };
7605            if should_output {
7606                if mysql_pretty_options {
7607                    self.write_newline();
7608                    self.write_indent();
7609                } else {
7610                    self.write_space();
7611                }
7612                self.write_keyword(key);
7613                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
7614                if key == "COMMENT" && !self.config.schema_comment_with_eq {
7615                    self.write_space();
7616                } else {
7617                    self.write("=");
7618                }
7619                self.write(value);
7620            }
7621        }
7622
7623        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
7624        if ct.temporary
7625            && matches!(
7626                self.config.dialect,
7627                Some(DialectType::Spark) | Some(DialectType::Databricks)
7628            )
7629            && ct.as_select.is_none()
7630        {
7631            self.write_space();
7632            self.write_keyword("USING PARQUET");
7633        }
7634
7635        // PostgreSQL INHERITS clause
7636        if !ct.inherits.is_empty() {
7637            self.write_space();
7638            self.write_keyword("INHERITS");
7639            self.write(" (");
7640            for (i, parent) in ct.inherits.iter().enumerate() {
7641                if i > 0 {
7642                    self.write(", ");
7643                }
7644                self.generate_table(parent)?;
7645            }
7646            self.write(")");
7647        }
7648
7649        // CREATE TABLE AS SELECT
7650        if let Some(ref query) = ct.as_select {
7651            self.write_space();
7652            self.write_keyword("AS");
7653            self.write_space();
7654            if ct.as_select_parenthesized {
7655                self.write("(");
7656            }
7657            self.generate_expression(query)?;
7658            if ct.as_select_parenthesized {
7659                self.write(")");
7660            }
7661
7662            // Teradata: WITH DATA / WITH NO DATA
7663            if let Some(with_data) = ct.with_data {
7664                self.write_space();
7665                self.write_keyword("WITH");
7666                if !with_data {
7667                    self.write_space();
7668                    self.write_keyword("NO");
7669                }
7670                self.write_space();
7671                self.write_keyword("DATA");
7672            }
7673
7674            // Teradata: AND STATISTICS / AND NO STATISTICS
7675            if let Some(with_statistics) = ct.with_statistics {
7676                self.write_space();
7677                self.write_keyword("AND");
7678                if !with_statistics {
7679                    self.write_space();
7680                    self.write_keyword("NO");
7681                }
7682                self.write_space();
7683                self.write_keyword("STATISTICS");
7684            }
7685
7686            // Teradata: Index specifications
7687            for index in &ct.teradata_indexes {
7688                self.write_space();
7689                match index.kind {
7690                    TeradataIndexKind::NoPrimary => {
7691                        self.write_keyword("NO PRIMARY INDEX");
7692                    }
7693                    TeradataIndexKind::Primary => {
7694                        self.write_keyword("PRIMARY INDEX");
7695                    }
7696                    TeradataIndexKind::PrimaryAmp => {
7697                        self.write_keyword("PRIMARY AMP INDEX");
7698                    }
7699                    TeradataIndexKind::Unique => {
7700                        self.write_keyword("UNIQUE INDEX");
7701                    }
7702                    TeradataIndexKind::UniquePrimary => {
7703                        self.write_keyword("UNIQUE PRIMARY INDEX");
7704                    }
7705                    TeradataIndexKind::Secondary => {
7706                        self.write_keyword("INDEX");
7707                    }
7708                }
7709                // Output index name if present
7710                if let Some(ref name) = index.name {
7711                    self.write_space();
7712                    self.write(name);
7713                }
7714                // Output columns if present
7715                if !index.columns.is_empty() {
7716                    self.write(" (");
7717                    for (i, col) in index.columns.iter().enumerate() {
7718                        if i > 0 {
7719                            self.write(", ");
7720                        }
7721                        self.write(col);
7722                    }
7723                    self.write(")");
7724                }
7725            }
7726
7727            // Teradata: ON COMMIT behavior for volatile tables
7728            if let Some(ref on_commit) = ct.on_commit {
7729                self.write_space();
7730                self.write_keyword("ON COMMIT");
7731                self.write_space();
7732                match on_commit {
7733                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
7734                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
7735                }
7736            }
7737
7738            if !post_as_properties.is_empty() {
7739                for prop in post_as_properties {
7740                    self.write_space();
7741                    self.generate_expression(prop)?;
7742                }
7743            }
7744
7745            // Restore Athena Hive context before early return
7746            self.athena_hive_context = saved_athena_hive_context;
7747            return Ok(());
7748        }
7749
7750        // ON COMMIT behavior (for non-CTAS tables)
7751        if let Some(ref on_commit) = ct.on_commit {
7752            self.write_space();
7753            self.write_keyword("ON COMMIT");
7754            self.write_space();
7755            match on_commit {
7756                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
7757                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
7758            }
7759        }
7760
7761        // Restore Athena Hive context
7762        self.athena_hive_context = saved_athena_hive_context;
7763
7764        Ok(())
7765    }
7766
7767    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
7768    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
7769    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
7770        // Output column name
7771        self.generate_identifier(&col.name)?;
7772        // Output data type if known
7773        if !matches!(col.data_type, DataType::Unknown) {
7774            self.write_space();
7775            self.generate_data_type(&col.data_type)?;
7776        }
7777        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
7778        for constraint in &col.constraints {
7779            if let ColumnConstraint::Path(path_expr) = constraint {
7780                self.write_space();
7781                self.write_keyword("PATH");
7782                self.write_space();
7783                self.generate_expression(path_expr)?;
7784            }
7785        }
7786        Ok(())
7787    }
7788
7789    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
7790        // Check if this is a TSQL computed column (no data type)
7791        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
7792            && col
7793                .constraints
7794                .iter()
7795                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
7796        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
7797        let omit_computed_type = !self.config.computed_column_with_type
7798            && col
7799                .constraints
7800                .iter()
7801                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
7802
7803        // Check if this is a partition column spec (no data type, type is Unknown)
7804        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
7805        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
7806
7807        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
7808        // Also check the no_type flag for SQLite columns without types
7809        let has_no_type = col.no_type
7810            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
7811                && col.constraints.is_empty());
7812
7813        self.generate_identifier(&col.name)?;
7814
7815        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
7816        let serial_expansion = if matches!(
7817            self.config.dialect,
7818            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
7819        ) {
7820            if let DataType::Custom { ref name } = col.data_type {
7821                match name.to_uppercase().as_str() {
7822                    "SERIAL" => Some("INT"),
7823                    "BIGSERIAL" => Some("BIGINT"),
7824                    "SMALLSERIAL" => Some("SMALLINT"),
7825                    _ => None,
7826                }
7827            } else {
7828                None
7829            }
7830        } else {
7831            None
7832        };
7833
7834        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
7835        {
7836            self.write_space();
7837            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
7838            // since ClickHouse uses explicit Nullable() in its type system.
7839            let saved_nullable_depth = self.clickhouse_nullable_depth;
7840            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
7841                self.clickhouse_nullable_depth = -1;
7842            }
7843            if let Some(int_type) = serial_expansion {
7844                // SERIAL -> INT (+ constraints added below)
7845                self.write_keyword(int_type);
7846            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
7847                // For DuckDB: convert unsigned integer types to their unsigned equivalents
7848                let unsigned_type = match &col.data_type {
7849                    DataType::Int { .. } => Some("UINTEGER"),
7850                    DataType::BigInt { .. } => Some("UBIGINT"),
7851                    DataType::SmallInt { .. } => Some("USMALLINT"),
7852                    DataType::TinyInt { .. } => Some("UTINYINT"),
7853                    _ => None,
7854                };
7855                if let Some(utype) = unsigned_type {
7856                    self.write_keyword(utype);
7857                } else {
7858                    self.generate_data_type(&col.data_type)?;
7859                }
7860            } else {
7861                self.generate_data_type(&col.data_type)?;
7862            }
7863            self.clickhouse_nullable_depth = saved_nullable_depth;
7864        }
7865
7866        // MySQL type modifiers (must come right after data type)
7867        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
7868        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
7869            self.write_space();
7870            self.write_keyword("UNSIGNED");
7871        }
7872        if col.zerofill {
7873            self.write_space();
7874            self.write_keyword("ZEROFILL");
7875        }
7876
7877        // Teradata column attributes (must come right after data type, in specific order)
7878        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
7879
7880        if let Some(ref charset) = col.character_set {
7881            self.write_space();
7882            self.write_keyword("CHARACTER SET");
7883            self.write_space();
7884            self.write(charset);
7885        }
7886
7887        if col.uppercase {
7888            self.write_space();
7889            self.write_keyword("UPPERCASE");
7890        }
7891
7892        if let Some(casespecific) = col.casespecific {
7893            self.write_space();
7894            if casespecific {
7895                self.write_keyword("CASESPECIFIC");
7896            } else {
7897                self.write_keyword("NOT CASESPECIFIC");
7898            }
7899        }
7900
7901        if let Some(ref format) = col.format {
7902            self.write_space();
7903            self.write_keyword("FORMAT");
7904            self.write(" '");
7905            self.write(format);
7906            self.write("'");
7907        }
7908
7909        if let Some(ref title) = col.title {
7910            self.write_space();
7911            self.write_keyword("TITLE");
7912            self.write(" '");
7913            self.write(title);
7914            self.write("'");
7915        }
7916
7917        if let Some(length) = col.inline_length {
7918            self.write_space();
7919            self.write_keyword("INLINE LENGTH");
7920            self.write(" ");
7921            self.write(&length.to_string());
7922        }
7923
7924        if let Some(ref compress) = col.compress {
7925            self.write_space();
7926            self.write_keyword("COMPRESS");
7927            if !compress.is_empty() {
7928                // Single string literal: output without parentheses (Teradata syntax)
7929                if compress.len() == 1 {
7930                    if let Expression::Literal(Literal::String(_)) = &compress[0] {
7931                        self.write_space();
7932                        self.generate_expression(&compress[0])?;
7933                    } else {
7934                        self.write(" (");
7935                        self.generate_expression(&compress[0])?;
7936                        self.write(")");
7937                    }
7938                } else {
7939                    self.write(" (");
7940                    for (i, val) in compress.iter().enumerate() {
7941                        if i > 0 {
7942                            self.write(", ");
7943                        }
7944                        self.generate_expression(val)?;
7945                    }
7946                    self.write(")");
7947                }
7948            }
7949        }
7950
7951        // Column constraints - output in original order if constraint_order is populated
7952        // Otherwise fall back to legacy fixed order for backward compatibility
7953        if !col.constraint_order.is_empty() {
7954            // Use constraint_order for original ordering
7955            // Track indices for constraints stored in the constraints Vec
7956            let mut references_idx = 0;
7957            let mut check_idx = 0;
7958            let mut generated_idx = 0;
7959            let mut collate_idx = 0;
7960            let mut comment_idx = 0;
7961            // The preprocessing in dialects/mod.rs now handles the correct ordering of
7962            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
7963            let defer_not_null_after_identity = false;
7964            let mut pending_not_null_after_identity = false;
7965
7966            for constraint_type in &col.constraint_order {
7967                match constraint_type {
7968                    ConstraintType::PrimaryKey => {
7969                        // Materialize doesn't support PRIMARY KEY column constraints
7970                        if col.primary_key
7971                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
7972                        {
7973                            if let Some(ref cname) = col.primary_key_constraint_name {
7974                                self.write_space();
7975                                self.write_keyword("CONSTRAINT");
7976                                self.write_space();
7977                                self.write(cname);
7978                            }
7979                            self.write_space();
7980                            self.write_keyword("PRIMARY KEY");
7981                            if let Some(ref order) = col.primary_key_order {
7982                                self.write_space();
7983                                match order {
7984                                    SortOrder::Asc => self.write_keyword("ASC"),
7985                                    SortOrder::Desc => self.write_keyword("DESC"),
7986                                }
7987                            }
7988                        }
7989                    }
7990                    ConstraintType::Unique => {
7991                        if col.unique {
7992                            if let Some(ref cname) = col.unique_constraint_name {
7993                                self.write_space();
7994                                self.write_keyword("CONSTRAINT");
7995                                self.write_space();
7996                                self.write(cname);
7997                            }
7998                            self.write_space();
7999                            self.write_keyword("UNIQUE");
8000                            // PostgreSQL 15+: NULLS NOT DISTINCT
8001                            if col.unique_nulls_not_distinct {
8002                                self.write(" NULLS NOT DISTINCT");
8003                            }
8004                        }
8005                    }
8006                    ConstraintType::NotNull => {
8007                        if col.nullable == Some(false) {
8008                            if defer_not_null_after_identity {
8009                                pending_not_null_after_identity = true;
8010                                continue;
8011                            }
8012                            if let Some(ref cname) = col.not_null_constraint_name {
8013                                self.write_space();
8014                                self.write_keyword("CONSTRAINT");
8015                                self.write_space();
8016                                self.write(cname);
8017                            }
8018                            self.write_space();
8019                            self.write_keyword("NOT NULL");
8020                        }
8021                    }
8022                    ConstraintType::Null => {
8023                        if col.nullable == Some(true) {
8024                            self.write_space();
8025                            self.write_keyword("NULL");
8026                        }
8027                    }
8028                    ConstraintType::Default => {
8029                        if let Some(ref default) = col.default {
8030                            self.write_space();
8031                            self.write_keyword("DEFAULT");
8032                            self.write_space();
8033                            self.generate_expression(default)?;
8034                        }
8035                    }
8036                    ConstraintType::AutoIncrement => {
8037                        if col.auto_increment {
8038                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
8039                            if matches!(
8040                                self.config.dialect,
8041                                Some(crate::dialects::DialectType::DuckDB)
8042                            ) {
8043                                // Skip - DuckDB uses sequences or rowid instead
8044                            } else if matches!(
8045                                self.config.dialect,
8046                                Some(crate::dialects::DialectType::Materialize)
8047                            ) {
8048                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
8049                                if !matches!(col.nullable, Some(false)) {
8050                                    self.write_space();
8051                                    self.write_keyword("NOT NULL");
8052                                }
8053                            } else if matches!(
8054                                self.config.dialect,
8055                                Some(crate::dialects::DialectType::PostgreSQL)
8056                            ) {
8057                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
8058                                self.write_space();
8059                                self.generate_auto_increment_keyword(col)?;
8060                            } else {
8061                                self.write_space();
8062                                self.generate_auto_increment_keyword(col)?;
8063                                if pending_not_null_after_identity {
8064                                    self.write_space();
8065                                    self.write_keyword("NOT NULL");
8066                                    pending_not_null_after_identity = false;
8067                                }
8068                            }
8069                        } // close else for DuckDB skip
8070                    }
8071                    ConstraintType::References => {
8072                        // Find next References constraint
8073                        while references_idx < col.constraints.len() {
8074                            if let ColumnConstraint::References(fk_ref) =
8075                                &col.constraints[references_idx]
8076                            {
8077                                // CONSTRAINT name if present
8078                                if let Some(ref name) = fk_ref.constraint_name {
8079                                    self.write_space();
8080                                    self.write_keyword("CONSTRAINT");
8081                                    self.write_space();
8082                                    self.write(name);
8083                                }
8084                                self.write_space();
8085                                if fk_ref.has_foreign_key_keywords {
8086                                    self.write_keyword("FOREIGN KEY");
8087                                    self.write_space();
8088                                }
8089                                self.write_keyword("REFERENCES");
8090                                self.write_space();
8091                                self.generate_table(&fk_ref.table)?;
8092                                if !fk_ref.columns.is_empty() {
8093                                    self.write(" (");
8094                                    for (i, c) in fk_ref.columns.iter().enumerate() {
8095                                        if i > 0 {
8096                                            self.write(", ");
8097                                        }
8098                                        self.generate_identifier(c)?;
8099                                    }
8100                                    self.write(")");
8101                                }
8102                                self.generate_referential_actions(fk_ref)?;
8103                                references_idx += 1;
8104                                break;
8105                            }
8106                            references_idx += 1;
8107                        }
8108                    }
8109                    ConstraintType::Check => {
8110                        // Find next Check constraint
8111                        while check_idx < col.constraints.len() {
8112                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
8113                                // Output CONSTRAINT name if present (only for first CHECK)
8114                                if check_idx == 0 {
8115                                    if let Some(ref cname) = col.check_constraint_name {
8116                                        self.write_space();
8117                                        self.write_keyword("CONSTRAINT");
8118                                        self.write_space();
8119                                        self.write(cname);
8120                                    }
8121                                }
8122                                self.write_space();
8123                                self.write_keyword("CHECK");
8124                                self.write(" (");
8125                                self.generate_expression(expr)?;
8126                                self.write(")");
8127                                check_idx += 1;
8128                                break;
8129                            }
8130                            check_idx += 1;
8131                        }
8132                    }
8133                    ConstraintType::GeneratedAsIdentity => {
8134                        // Find next GeneratedAsIdentity constraint
8135                        while generated_idx < col.constraints.len() {
8136                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
8137                                &col.constraints[generated_idx]
8138                            {
8139                                self.write_space();
8140                                // Redshift uses IDENTITY(start, increment) syntax
8141                                if matches!(
8142                                    self.config.dialect,
8143                                    Some(crate::dialects::DialectType::Redshift)
8144                                ) {
8145                                    self.write_keyword("IDENTITY");
8146                                    self.write("(");
8147                                    if let Some(ref start) = gen.start {
8148                                        self.generate_expression(start)?;
8149                                    } else {
8150                                        self.write("0");
8151                                    }
8152                                    self.write(", ");
8153                                    if let Some(ref incr) = gen.increment {
8154                                        self.generate_expression(incr)?;
8155                                    } else {
8156                                        self.write("1");
8157                                    }
8158                                    self.write(")");
8159                                } else {
8160                                    self.write_keyword("GENERATED");
8161                                    if gen.always {
8162                                        self.write_space();
8163                                        self.write_keyword("ALWAYS");
8164                                    } else {
8165                                        self.write_space();
8166                                        self.write_keyword("BY DEFAULT");
8167                                        if gen.on_null {
8168                                            self.write_space();
8169                                            self.write_keyword("ON NULL");
8170                                        }
8171                                    }
8172                                    self.write_space();
8173                                    self.write_keyword("AS IDENTITY");
8174
8175                                    let has_options = gen.start.is_some()
8176                                        || gen.increment.is_some()
8177                                        || gen.minvalue.is_some()
8178                                        || gen.maxvalue.is_some()
8179                                        || gen.cycle.is_some();
8180                                    if has_options {
8181                                        self.write(" (");
8182                                        let mut first = true;
8183                                        if let Some(ref start) = gen.start {
8184                                            if !first {
8185                                                self.write(" ");
8186                                            }
8187                                            first = false;
8188                                            self.write_keyword("START WITH");
8189                                            self.write_space();
8190                                            self.generate_expression(start)?;
8191                                        }
8192                                        if let Some(ref incr) = gen.increment {
8193                                            if !first {
8194                                                self.write(" ");
8195                                            }
8196                                            first = false;
8197                                            self.write_keyword("INCREMENT BY");
8198                                            self.write_space();
8199                                            self.generate_expression(incr)?;
8200                                        }
8201                                        if let Some(ref minv) = gen.minvalue {
8202                                            if !first {
8203                                                self.write(" ");
8204                                            }
8205                                            first = false;
8206                                            self.write_keyword("MINVALUE");
8207                                            self.write_space();
8208                                            self.generate_expression(minv)?;
8209                                        }
8210                                        if let Some(ref maxv) = gen.maxvalue {
8211                                            if !first {
8212                                                self.write(" ");
8213                                            }
8214                                            first = false;
8215                                            self.write_keyword("MAXVALUE");
8216                                            self.write_space();
8217                                            self.generate_expression(maxv)?;
8218                                        }
8219                                        if let Some(cycle) = gen.cycle {
8220                                            if !first {
8221                                                self.write(" ");
8222                                            }
8223                                            if cycle {
8224                                                self.write_keyword("CYCLE");
8225                                            } else {
8226                                                self.write_keyword("NO CYCLE");
8227                                            }
8228                                        }
8229                                        self.write(")");
8230                                    }
8231                                }
8232                                generated_idx += 1;
8233                                break;
8234                            }
8235                            generated_idx += 1;
8236                        }
8237                    }
8238                    ConstraintType::Collate => {
8239                        // Find next Collate constraint
8240                        while collate_idx < col.constraints.len() {
8241                            if let ColumnConstraint::Collate(collation) =
8242                                &col.constraints[collate_idx]
8243                            {
8244                                self.write_space();
8245                                self.write_keyword("COLLATE");
8246                                self.write_space();
8247                                self.generate_identifier(collation)?;
8248                                collate_idx += 1;
8249                                break;
8250                            }
8251                            collate_idx += 1;
8252                        }
8253                    }
8254                    ConstraintType::Comment => {
8255                        // Find next Comment constraint
8256                        while comment_idx < col.constraints.len() {
8257                            if let ColumnConstraint::Comment(comment) =
8258                                &col.constraints[comment_idx]
8259                            {
8260                                self.write_space();
8261                                self.write_keyword("COMMENT");
8262                                self.write_space();
8263                                self.generate_string_literal(comment)?;
8264                                comment_idx += 1;
8265                                break;
8266                            }
8267                            comment_idx += 1;
8268                        }
8269                    }
8270                    ConstraintType::Tags => {
8271                        // Find next Tags constraint (Snowflake)
8272                        for constraint in &col.constraints {
8273                            if let ColumnConstraint::Tags(tags) = constraint {
8274                                self.write_space();
8275                                self.write_keyword("TAG");
8276                                self.write(" (");
8277                                for (i, expr) in tags.expressions.iter().enumerate() {
8278                                    if i > 0 {
8279                                        self.write(", ");
8280                                    }
8281                                    self.generate_expression(expr)?;
8282                                }
8283                                self.write(")");
8284                                break;
8285                            }
8286                        }
8287                    }
8288                    ConstraintType::ComputedColumn => {
8289                        // Find next ComputedColumn constraint
8290                        for constraint in &col.constraints {
8291                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
8292                                self.write_space();
8293                                self.generate_computed_column_inline(cc)?;
8294                                break;
8295                            }
8296                        }
8297                    }
8298                    ConstraintType::GeneratedAsRow => {
8299                        // Find next GeneratedAsRow constraint
8300                        for constraint in &col.constraints {
8301                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
8302                                self.write_space();
8303                                self.generate_generated_as_row_inline(gar)?;
8304                                break;
8305                            }
8306                        }
8307                    }
8308                    ConstraintType::OnUpdate => {
8309                        if let Some(ref expr) = col.on_update {
8310                            self.write_space();
8311                            self.write_keyword("ON UPDATE");
8312                            self.write_space();
8313                            self.generate_expression(expr)?;
8314                        }
8315                    }
8316                    ConstraintType::Encode => {
8317                        if let Some(ref encoding) = col.encoding {
8318                            self.write_space();
8319                            self.write_keyword("ENCODE");
8320                            self.write_space();
8321                            self.write(encoding);
8322                        }
8323                    }
8324                    ConstraintType::Path => {
8325                        // Find next Path constraint
8326                        for constraint in &col.constraints {
8327                            if let ColumnConstraint::Path(path_expr) = constraint {
8328                                self.write_space();
8329                                self.write_keyword("PATH");
8330                                self.write_space();
8331                                self.generate_expression(path_expr)?;
8332                                break;
8333                            }
8334                        }
8335                    }
8336                }
8337            }
8338            if pending_not_null_after_identity {
8339                self.write_space();
8340                self.write_keyword("NOT NULL");
8341            }
8342        } else {
8343            // Legacy fixed order for backward compatibility
8344            if col.primary_key {
8345                self.write_space();
8346                self.write_keyword("PRIMARY KEY");
8347                if let Some(ref order) = col.primary_key_order {
8348                    self.write_space();
8349                    match order {
8350                        SortOrder::Asc => self.write_keyword("ASC"),
8351                        SortOrder::Desc => self.write_keyword("DESC"),
8352                    }
8353                }
8354            }
8355
8356            if col.unique {
8357                self.write_space();
8358                self.write_keyword("UNIQUE");
8359                // PostgreSQL 15+: NULLS NOT DISTINCT
8360                if col.unique_nulls_not_distinct {
8361                    self.write(" NULLS NOT DISTINCT");
8362                }
8363            }
8364
8365            match col.nullable {
8366                Some(false) => {
8367                    self.write_space();
8368                    self.write_keyword("NOT NULL");
8369                }
8370                Some(true) => {
8371                    self.write_space();
8372                    self.write_keyword("NULL");
8373                }
8374                None => {}
8375            }
8376
8377            if let Some(ref default) = col.default {
8378                self.write_space();
8379                self.write_keyword("DEFAULT");
8380                self.write_space();
8381                self.generate_expression(default)?;
8382            }
8383
8384            if col.auto_increment {
8385                self.write_space();
8386                self.generate_auto_increment_keyword(col)?;
8387            }
8388
8389            // Column-level constraints from Vec
8390            for constraint in &col.constraints {
8391                match constraint {
8392                    ColumnConstraint::References(fk_ref) => {
8393                        self.write_space();
8394                        if fk_ref.has_foreign_key_keywords {
8395                            self.write_keyword("FOREIGN KEY");
8396                            self.write_space();
8397                        }
8398                        self.write_keyword("REFERENCES");
8399                        self.write_space();
8400                        self.generate_table(&fk_ref.table)?;
8401                        if !fk_ref.columns.is_empty() {
8402                            self.write(" (");
8403                            for (i, c) in fk_ref.columns.iter().enumerate() {
8404                                if i > 0 {
8405                                    self.write(", ");
8406                                }
8407                                self.generate_identifier(c)?;
8408                            }
8409                            self.write(")");
8410                        }
8411                        self.generate_referential_actions(fk_ref)?;
8412                    }
8413                    ColumnConstraint::Check(expr) => {
8414                        self.write_space();
8415                        self.write_keyword("CHECK");
8416                        self.write(" (");
8417                        self.generate_expression(expr)?;
8418                        self.write(")");
8419                    }
8420                    ColumnConstraint::GeneratedAsIdentity(gen) => {
8421                        self.write_space();
8422                        // Redshift uses IDENTITY(start, increment) syntax
8423                        if matches!(
8424                            self.config.dialect,
8425                            Some(crate::dialects::DialectType::Redshift)
8426                        ) {
8427                            self.write_keyword("IDENTITY");
8428                            self.write("(");
8429                            if let Some(ref start) = gen.start {
8430                                self.generate_expression(start)?;
8431                            } else {
8432                                self.write("0");
8433                            }
8434                            self.write(", ");
8435                            if let Some(ref incr) = gen.increment {
8436                                self.generate_expression(incr)?;
8437                            } else {
8438                                self.write("1");
8439                            }
8440                            self.write(")");
8441                        } else {
8442                            self.write_keyword("GENERATED");
8443                            if gen.always {
8444                                self.write_space();
8445                                self.write_keyword("ALWAYS");
8446                            } else {
8447                                self.write_space();
8448                                self.write_keyword("BY DEFAULT");
8449                                if gen.on_null {
8450                                    self.write_space();
8451                                    self.write_keyword("ON NULL");
8452                                }
8453                            }
8454                            self.write_space();
8455                            self.write_keyword("AS IDENTITY");
8456
8457                            let has_options = gen.start.is_some()
8458                                || gen.increment.is_some()
8459                                || gen.minvalue.is_some()
8460                                || gen.maxvalue.is_some()
8461                                || gen.cycle.is_some();
8462                            if has_options {
8463                                self.write(" (");
8464                                let mut first = true;
8465                                if let Some(ref start) = gen.start {
8466                                    if !first {
8467                                        self.write(" ");
8468                                    }
8469                                    first = false;
8470                                    self.write_keyword("START WITH");
8471                                    self.write_space();
8472                                    self.generate_expression(start)?;
8473                                }
8474                                if let Some(ref incr) = gen.increment {
8475                                    if !first {
8476                                        self.write(" ");
8477                                    }
8478                                    first = false;
8479                                    self.write_keyword("INCREMENT BY");
8480                                    self.write_space();
8481                                    self.generate_expression(incr)?;
8482                                }
8483                                if let Some(ref minv) = gen.minvalue {
8484                                    if !first {
8485                                        self.write(" ");
8486                                    }
8487                                    first = false;
8488                                    self.write_keyword("MINVALUE");
8489                                    self.write_space();
8490                                    self.generate_expression(minv)?;
8491                                }
8492                                if let Some(ref maxv) = gen.maxvalue {
8493                                    if !first {
8494                                        self.write(" ");
8495                                    }
8496                                    first = false;
8497                                    self.write_keyword("MAXVALUE");
8498                                    self.write_space();
8499                                    self.generate_expression(maxv)?;
8500                                }
8501                                if let Some(cycle) = gen.cycle {
8502                                    if !first {
8503                                        self.write(" ");
8504                                    }
8505                                    if cycle {
8506                                        self.write_keyword("CYCLE");
8507                                    } else {
8508                                        self.write_keyword("NO CYCLE");
8509                                    }
8510                                }
8511                                self.write(")");
8512                            }
8513                        }
8514                    }
8515                    ColumnConstraint::Collate(collation) => {
8516                        self.write_space();
8517                        self.write_keyword("COLLATE");
8518                        self.write_space();
8519                        self.generate_identifier(collation)?;
8520                    }
8521                    ColumnConstraint::Comment(comment) => {
8522                        self.write_space();
8523                        self.write_keyword("COMMENT");
8524                        self.write_space();
8525                        self.generate_string_literal(comment)?;
8526                    }
8527                    ColumnConstraint::Path(path_expr) => {
8528                        self.write_space();
8529                        self.write_keyword("PATH");
8530                        self.write_space();
8531                        self.generate_expression(path_expr)?;
8532                    }
8533                    _ => {} // Other constraints handled above
8534                }
8535            }
8536
8537            // Redshift: ENCODE encoding_type (legacy path)
8538            if let Some(ref encoding) = col.encoding {
8539                self.write_space();
8540                self.write_keyword("ENCODE");
8541                self.write_space();
8542                self.write(encoding);
8543            }
8544        }
8545
8546        // ClickHouse: CODEC(...)
8547        if let Some(ref codec) = col.codec {
8548            self.write_space();
8549            self.write_keyword("CODEC");
8550            self.write("(");
8551            self.write(codec);
8552            self.write(")");
8553        }
8554
8555        // ClickHouse: EPHEMERAL [expr]
8556        if let Some(ref ephemeral) = col.ephemeral {
8557            self.write_space();
8558            self.write_keyword("EPHEMERAL");
8559            if let Some(ref expr) = ephemeral {
8560                self.write_space();
8561                self.generate_expression(expr)?;
8562            }
8563        }
8564
8565        // ClickHouse: MATERIALIZED expr
8566        if let Some(ref mat_expr) = col.materialized_expr {
8567            self.write_space();
8568            self.write_keyword("MATERIALIZED");
8569            self.write_space();
8570            self.generate_expression(mat_expr)?;
8571        }
8572
8573        // ClickHouse: ALIAS expr
8574        if let Some(ref alias_expr) = col.alias_expr {
8575            self.write_space();
8576            self.write_keyword("ALIAS");
8577            self.write_space();
8578            self.generate_expression(alias_expr)?;
8579        }
8580
8581        // ClickHouse: TTL expr
8582        if let Some(ref ttl_expr) = col.ttl_expr {
8583            self.write_space();
8584            self.write_keyword("TTL");
8585            self.write_space();
8586            self.generate_expression(ttl_expr)?;
8587        }
8588
8589        // TSQL: NOT FOR REPLICATION
8590        if col.not_for_replication
8591            && matches!(
8592                self.config.dialect,
8593                Some(crate::dialects::DialectType::TSQL)
8594                    | Some(crate::dialects::DialectType::Fabric)
8595            )
8596        {
8597            self.write_space();
8598            self.write_keyword("NOT FOR REPLICATION");
8599        }
8600
8601        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
8602        if !col.options.is_empty() {
8603            self.write_space();
8604            self.generate_options_clause(&col.options)?;
8605        }
8606
8607        // SQLite: Inline PRIMARY KEY from table constraint
8608        // This comes at the end, after all existing column constraints
8609        if !col.primary_key
8610            && self
8611                .sqlite_inline_pk_columns
8612                .contains(&col.name.name.to_lowercase())
8613        {
8614            self.write_space();
8615            self.write_keyword("PRIMARY KEY");
8616        }
8617
8618        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
8619        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
8620        if serial_expansion.is_some() {
8621            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
8622                self.write_space();
8623                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
8624            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
8625                self.write_space();
8626                self.write_keyword("NOT NULL");
8627            }
8628        }
8629
8630        Ok(())
8631    }
8632
8633    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
8634        match constraint {
8635            TableConstraint::PrimaryKey {
8636                name,
8637                columns,
8638                include_columns,
8639                modifiers,
8640                has_constraint_keyword,
8641            } => {
8642                if let Some(ref n) = name {
8643                    if *has_constraint_keyword {
8644                        self.write_keyword("CONSTRAINT");
8645                        self.write_space();
8646                        self.generate_identifier(n)?;
8647                        self.write_space();
8648                    }
8649                }
8650                self.write_keyword("PRIMARY KEY");
8651                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
8652                if let Some(ref clustered) = modifiers.clustered {
8653                    self.write_space();
8654                    self.write_keyword(clustered);
8655                }
8656                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
8657                if let Some(ref n) = name {
8658                    if !*has_constraint_keyword {
8659                        self.write_space();
8660                        self.generate_identifier(n)?;
8661                    }
8662                }
8663                self.write(" (");
8664                for (i, col) in columns.iter().enumerate() {
8665                    if i > 0 {
8666                        self.write(", ");
8667                    }
8668                    self.generate_identifier(col)?;
8669                }
8670                self.write(")");
8671                if !include_columns.is_empty() {
8672                    self.write_space();
8673                    self.write_keyword("INCLUDE");
8674                    self.write(" (");
8675                    for (i, col) in include_columns.iter().enumerate() {
8676                        if i > 0 {
8677                            self.write(", ");
8678                        }
8679                        self.generate_identifier(col)?;
8680                    }
8681                    self.write(")");
8682                }
8683                self.generate_constraint_modifiers(modifiers);
8684            }
8685            TableConstraint::Unique {
8686                name,
8687                columns,
8688                columns_parenthesized,
8689                modifiers,
8690                has_constraint_keyword,
8691                nulls_not_distinct,
8692            } => {
8693                if let Some(ref n) = name {
8694                    if *has_constraint_keyword {
8695                        self.write_keyword("CONSTRAINT");
8696                        self.write_space();
8697                        self.generate_identifier(n)?;
8698                        self.write_space();
8699                    }
8700                }
8701                self.write_keyword("UNIQUE");
8702                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
8703                if let Some(ref clustered) = modifiers.clustered {
8704                    self.write_space();
8705                    self.write_keyword(clustered);
8706                }
8707                // PostgreSQL 15+: NULLS NOT DISTINCT
8708                if *nulls_not_distinct {
8709                    self.write(" NULLS NOT DISTINCT");
8710                }
8711                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
8712                if let Some(ref n) = name {
8713                    if !*has_constraint_keyword {
8714                        self.write_space();
8715                        self.generate_identifier(n)?;
8716                    }
8717                }
8718                if *columns_parenthesized {
8719                    self.write(" (");
8720                    for (i, col) in columns.iter().enumerate() {
8721                        if i > 0 {
8722                            self.write(", ");
8723                        }
8724                        self.generate_identifier(col)?;
8725                    }
8726                    self.write(")");
8727                } else {
8728                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
8729                    for col in columns.iter() {
8730                        self.write_space();
8731                        self.generate_identifier(col)?;
8732                    }
8733                }
8734                self.generate_constraint_modifiers(modifiers);
8735            }
8736            TableConstraint::ForeignKey {
8737                name,
8738                columns,
8739                references,
8740                on_delete,
8741                on_update,
8742                modifiers,
8743            } => {
8744                if let Some(ref n) = name {
8745                    self.write_keyword("CONSTRAINT");
8746                    self.write_space();
8747                    self.generate_identifier(n)?;
8748                    self.write_space();
8749                }
8750                self.write_keyword("FOREIGN KEY");
8751                self.write(" (");
8752                for (i, col) in columns.iter().enumerate() {
8753                    if i > 0 {
8754                        self.write(", ");
8755                    }
8756                    self.generate_identifier(col)?;
8757                }
8758                self.write(")");
8759                if let Some(ref refs) = references {
8760                    self.write(" ");
8761                    self.write_keyword("REFERENCES");
8762                    self.write_space();
8763                    self.generate_table(&refs.table)?;
8764                    if !refs.columns.is_empty() {
8765                        if self.config.pretty {
8766                            self.write(" (");
8767                            self.write_newline();
8768                            self.indent_level += 1;
8769                            for (i, col) in refs.columns.iter().enumerate() {
8770                                if i > 0 {
8771                                    self.write(",");
8772                                    self.write_newline();
8773                                }
8774                                self.write_indent();
8775                                self.generate_identifier(col)?;
8776                            }
8777                            self.indent_level -= 1;
8778                            self.write_newline();
8779                            self.write_indent();
8780                            self.write(")");
8781                        } else {
8782                            self.write(" (");
8783                            for (i, col) in refs.columns.iter().enumerate() {
8784                                if i > 0 {
8785                                    self.write(", ");
8786                                }
8787                                self.generate_identifier(col)?;
8788                            }
8789                            self.write(")");
8790                        }
8791                    }
8792                    self.generate_referential_actions(refs)?;
8793                } else {
8794                    // No REFERENCES - output ON DELETE/ON UPDATE directly
8795                    if let Some(ref action) = on_delete {
8796                        self.write_space();
8797                        self.write_keyword("ON DELETE");
8798                        self.write_space();
8799                        self.generate_referential_action(action);
8800                    }
8801                    if let Some(ref action) = on_update {
8802                        self.write_space();
8803                        self.write_keyword("ON UPDATE");
8804                        self.write_space();
8805                        self.generate_referential_action(action);
8806                    }
8807                }
8808                self.generate_constraint_modifiers(modifiers);
8809            }
8810            TableConstraint::Check {
8811                name,
8812                expression,
8813                modifiers,
8814            } => {
8815                if let Some(ref n) = name {
8816                    self.write_keyword("CONSTRAINT");
8817                    self.write_space();
8818                    self.generate_identifier(n)?;
8819                    self.write_space();
8820                }
8821                self.write_keyword("CHECK");
8822                self.write(" (");
8823                self.generate_expression(expression)?;
8824                self.write(")");
8825                self.generate_constraint_modifiers(modifiers);
8826            }
8827            TableConstraint::Index {
8828                name,
8829                columns,
8830                kind,
8831                modifiers,
8832                use_key_keyword,
8833                expression,
8834                index_type,
8835                granularity,
8836            } => {
8837                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
8838                if expression.is_some() {
8839                    self.write_keyword("INDEX");
8840                    if let Some(ref n) = name {
8841                        self.write_space();
8842                        self.generate_identifier(n)?;
8843                    }
8844                    if let Some(ref expr) = expression {
8845                        self.write_space();
8846                        self.generate_expression(expr)?;
8847                    }
8848                    if let Some(ref idx_type) = index_type {
8849                        self.write_space();
8850                        self.write_keyword("TYPE");
8851                        self.write_space();
8852                        self.generate_expression(idx_type)?;
8853                    }
8854                    if let Some(ref gran) = granularity {
8855                        self.write_space();
8856                        self.write_keyword("GRANULARITY");
8857                        self.write_space();
8858                        self.generate_expression(gran)?;
8859                    }
8860                } else {
8861                    // Standard INDEX syntax
8862                    // Determine the index keyword to use
8863                    // MySQL normalizes KEY to INDEX
8864                    use crate::dialects::DialectType;
8865                    let index_keyword = if *use_key_keyword
8866                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
8867                    {
8868                        "KEY"
8869                    } else {
8870                        "INDEX"
8871                    };
8872
8873                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
8874                    if let Some(ref k) = kind {
8875                        self.write_keyword(k);
8876                        // For UNIQUE, don't add INDEX/KEY keyword
8877                        if k != "UNIQUE" {
8878                            self.write_space();
8879                            self.write_keyword(index_keyword);
8880                        }
8881                    } else {
8882                        self.write_keyword(index_keyword);
8883                    }
8884
8885                    // Output USING before name if using_before_columns is true and there's no name
8886                    if modifiers.using_before_columns && name.is_none() {
8887                        if let Some(ref using) = modifiers.using {
8888                            self.write_space();
8889                            self.write_keyword("USING");
8890                            self.write_space();
8891                            self.write_keyword(using);
8892                        }
8893                    }
8894
8895                    // Output index name if present
8896                    if let Some(ref n) = name {
8897                        self.write_space();
8898                        self.generate_identifier(n)?;
8899                    }
8900
8901                    // Output USING after name but before columns if using_before_columns and there's a name
8902                    if modifiers.using_before_columns && name.is_some() {
8903                        if let Some(ref using) = modifiers.using {
8904                            self.write_space();
8905                            self.write_keyword("USING");
8906                            self.write_space();
8907                            self.write_keyword(using);
8908                        }
8909                    }
8910
8911                    // Output columns
8912                    self.write(" (");
8913                    for (i, col) in columns.iter().enumerate() {
8914                        if i > 0 {
8915                            self.write(", ");
8916                        }
8917                        self.generate_identifier(col)?;
8918                    }
8919                    self.write(")");
8920
8921                    // Output USING after columns if not using_before_columns
8922                    if !modifiers.using_before_columns {
8923                        if let Some(ref using) = modifiers.using {
8924                            self.write_space();
8925                            self.write_keyword("USING");
8926                            self.write_space();
8927                            self.write_keyword(using);
8928                        }
8929                    }
8930
8931                    // Output other constraint modifiers (but skip USING since we already handled it)
8932                    self.generate_constraint_modifiers_without_using(modifiers);
8933                }
8934            }
8935            TableConstraint::Projection { name, expression } => {
8936                // ClickHouse: PROJECTION name (SELECT ...)
8937                self.write_keyword("PROJECTION");
8938                self.write_space();
8939                self.generate_identifier(name)?;
8940                self.write(" (");
8941                self.generate_expression(expression)?;
8942                self.write(")");
8943            }
8944            TableConstraint::Like { source, options } => {
8945                self.write_keyword("LIKE");
8946                self.write_space();
8947                self.generate_table(source)?;
8948                for (action, prop) in options {
8949                    self.write_space();
8950                    match action {
8951                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
8952                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
8953                    }
8954                    self.write_space();
8955                    self.write_keyword(prop);
8956                }
8957            }
8958            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
8959                self.write_keyword("PERIOD FOR SYSTEM_TIME");
8960                self.write(" (");
8961                self.generate_identifier(start_col)?;
8962                self.write(", ");
8963                self.generate_identifier(end_col)?;
8964                self.write(")");
8965            }
8966            TableConstraint::Exclude {
8967                name,
8968                using,
8969                elements,
8970                include_columns,
8971                where_clause,
8972                with_params,
8973                using_index_tablespace,
8974                modifiers: _,
8975            } => {
8976                if let Some(ref n) = name {
8977                    self.write_keyword("CONSTRAINT");
8978                    self.write_space();
8979                    self.generate_identifier(n)?;
8980                    self.write_space();
8981                }
8982                self.write_keyword("EXCLUDE");
8983                if let Some(ref method) = using {
8984                    self.write_space();
8985                    self.write_keyword("USING");
8986                    self.write_space();
8987                    self.write(method);
8988                    self.write("(");
8989                } else {
8990                    self.write(" (");
8991                }
8992                for (i, elem) in elements.iter().enumerate() {
8993                    if i > 0 {
8994                        self.write(", ");
8995                    }
8996                    self.write(&elem.expression);
8997                    self.write_space();
8998                    self.write_keyword("WITH");
8999                    self.write_space();
9000                    self.write(&elem.operator);
9001                }
9002                self.write(")");
9003                if !include_columns.is_empty() {
9004                    self.write_space();
9005                    self.write_keyword("INCLUDE");
9006                    self.write(" (");
9007                    for (i, col) in include_columns.iter().enumerate() {
9008                        if i > 0 {
9009                            self.write(", ");
9010                        }
9011                        self.generate_identifier(col)?;
9012                    }
9013                    self.write(")");
9014                }
9015                if !with_params.is_empty() {
9016                    self.write_space();
9017                    self.write_keyword("WITH");
9018                    self.write(" (");
9019                    for (i, (key, val)) in with_params.iter().enumerate() {
9020                        if i > 0 {
9021                            self.write(", ");
9022                        }
9023                        self.write(key);
9024                        self.write("=");
9025                        self.write(val);
9026                    }
9027                    self.write(")");
9028                }
9029                if let Some(ref tablespace) = using_index_tablespace {
9030                    self.write_space();
9031                    self.write_keyword("USING INDEX TABLESPACE");
9032                    self.write_space();
9033                    self.write(tablespace);
9034                }
9035                if let Some(ref where_expr) = where_clause {
9036                    self.write_space();
9037                    self.write_keyword("WHERE");
9038                    self.write(" (");
9039                    self.generate_expression(where_expr)?;
9040                    self.write(")");
9041                }
9042            }
9043            TableConstraint::Tags(tags) => {
9044                self.write_keyword("TAG");
9045                self.write(" (");
9046                for (i, expr) in tags.expressions.iter().enumerate() {
9047                    if i > 0 {
9048                        self.write(", ");
9049                    }
9050                    self.generate_expression(expr)?;
9051                }
9052                self.write(")");
9053            }
9054            TableConstraint::InitiallyDeferred { deferred } => {
9055                self.write_keyword("INITIALLY");
9056                self.write_space();
9057                if *deferred {
9058                    self.write_keyword("DEFERRED");
9059                } else {
9060                    self.write_keyword("IMMEDIATE");
9061                }
9062            }
9063        }
9064        Ok(())
9065    }
9066
9067    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9068        // Output USING BTREE/HASH (MySQL) - comes first
9069        if let Some(using) = &modifiers.using {
9070            self.write_space();
9071            self.write_keyword("USING");
9072            self.write_space();
9073            self.write_keyword(using);
9074        }
9075        // Output ENFORCED/NOT ENFORCED
9076        if let Some(enforced) = modifiers.enforced {
9077            self.write_space();
9078            if enforced {
9079                self.write_keyword("ENFORCED");
9080            } else {
9081                self.write_keyword("NOT ENFORCED");
9082            }
9083        }
9084        // Output DEFERRABLE/NOT DEFERRABLE
9085        if let Some(deferrable) = modifiers.deferrable {
9086            self.write_space();
9087            if deferrable {
9088                self.write_keyword("DEFERRABLE");
9089            } else {
9090                self.write_keyword("NOT DEFERRABLE");
9091            }
9092        }
9093        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9094        if let Some(initially_deferred) = modifiers.initially_deferred {
9095            self.write_space();
9096            if initially_deferred {
9097                self.write_keyword("INITIALLY DEFERRED");
9098            } else {
9099                self.write_keyword("INITIALLY IMMEDIATE");
9100            }
9101        }
9102        // Output NORELY
9103        if modifiers.norely {
9104            self.write_space();
9105            self.write_keyword("NORELY");
9106        }
9107        // Output RELY
9108        if modifiers.rely {
9109            self.write_space();
9110            self.write_keyword("RELY");
9111        }
9112        // Output NOT VALID (PostgreSQL)
9113        if modifiers.not_valid {
9114            self.write_space();
9115            self.write_keyword("NOT VALID");
9116        }
9117        // Output ON CONFLICT (SQLite)
9118        if let Some(on_conflict) = &modifiers.on_conflict {
9119            self.write_space();
9120            self.write_keyword("ON CONFLICT");
9121            self.write_space();
9122            self.write_keyword(on_conflict);
9123        }
9124        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
9125        if !modifiers.with_options.is_empty() {
9126            self.write_space();
9127            self.write_keyword("WITH");
9128            self.write(" (");
9129            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
9130                if i > 0 {
9131                    self.write(", ");
9132                }
9133                self.write(key);
9134                self.write("=");
9135                self.write(value);
9136            }
9137            self.write(")");
9138        }
9139        // Output TSQL ON filegroup
9140        if let Some(ref fg) = modifiers.on_filegroup {
9141            self.write_space();
9142            self.write_keyword("ON");
9143            self.write_space();
9144            let _ = self.generate_identifier(fg);
9145        }
9146    }
9147
9148    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
9149    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
9150        // Output ENFORCED/NOT ENFORCED
9151        if let Some(enforced) = modifiers.enforced {
9152            self.write_space();
9153            if enforced {
9154                self.write_keyword("ENFORCED");
9155            } else {
9156                self.write_keyword("NOT ENFORCED");
9157            }
9158        }
9159        // Output DEFERRABLE/NOT DEFERRABLE
9160        if let Some(deferrable) = modifiers.deferrable {
9161            self.write_space();
9162            if deferrable {
9163                self.write_keyword("DEFERRABLE");
9164            } else {
9165                self.write_keyword("NOT DEFERRABLE");
9166            }
9167        }
9168        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9169        if let Some(initially_deferred) = modifiers.initially_deferred {
9170            self.write_space();
9171            if initially_deferred {
9172                self.write_keyword("INITIALLY DEFERRED");
9173            } else {
9174                self.write_keyword("INITIALLY IMMEDIATE");
9175            }
9176        }
9177        // Output NORELY
9178        if modifiers.norely {
9179            self.write_space();
9180            self.write_keyword("NORELY");
9181        }
9182        // Output RELY
9183        if modifiers.rely {
9184            self.write_space();
9185            self.write_keyword("RELY");
9186        }
9187        // Output NOT VALID (PostgreSQL)
9188        if modifiers.not_valid {
9189            self.write_space();
9190            self.write_keyword("NOT VALID");
9191        }
9192        // Output ON CONFLICT (SQLite)
9193        if let Some(on_conflict) = &modifiers.on_conflict {
9194            self.write_space();
9195            self.write_keyword("ON CONFLICT");
9196            self.write_space();
9197            self.write_keyword(on_conflict);
9198        }
9199        // Output MySQL index-specific modifiers
9200        self.generate_index_specific_modifiers(modifiers);
9201    }
9202
9203    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
9204    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9205        if let Some(ref comment) = modifiers.comment {
9206            self.write_space();
9207            self.write_keyword("COMMENT");
9208            self.write(" '");
9209            self.write(comment);
9210            self.write("'");
9211        }
9212        if let Some(visible) = modifiers.visible {
9213            self.write_space();
9214            if visible {
9215                self.write_keyword("VISIBLE");
9216            } else {
9217                self.write_keyword("INVISIBLE");
9218            }
9219        }
9220        if let Some(ref attr) = modifiers.engine_attribute {
9221            self.write_space();
9222            self.write_keyword("ENGINE_ATTRIBUTE");
9223            self.write(" = '");
9224            self.write(attr);
9225            self.write("'");
9226        }
9227        if let Some(ref parser) = modifiers.with_parser {
9228            self.write_space();
9229            self.write_keyword("WITH PARSER");
9230            self.write_space();
9231            self.write(parser);
9232        }
9233    }
9234
9235    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
9236        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
9237        if !fk_ref.match_after_actions {
9238            if let Some(ref match_type) = fk_ref.match_type {
9239                self.write_space();
9240                self.write_keyword("MATCH");
9241                self.write_space();
9242                match match_type {
9243                    MatchType::Full => self.write_keyword("FULL"),
9244                    MatchType::Partial => self.write_keyword("PARTIAL"),
9245                    MatchType::Simple => self.write_keyword("SIMPLE"),
9246                }
9247            }
9248        }
9249
9250        // Output ON UPDATE and ON DELETE in the original order
9251        if fk_ref.on_update_first {
9252            if let Some(ref action) = fk_ref.on_update {
9253                self.write_space();
9254                self.write_keyword("ON UPDATE");
9255                self.write_space();
9256                self.generate_referential_action(action);
9257            }
9258            if let Some(ref action) = fk_ref.on_delete {
9259                self.write_space();
9260                self.write_keyword("ON DELETE");
9261                self.write_space();
9262                self.generate_referential_action(action);
9263            }
9264        } else {
9265            if let Some(ref action) = fk_ref.on_delete {
9266                self.write_space();
9267                self.write_keyword("ON DELETE");
9268                self.write_space();
9269                self.generate_referential_action(action);
9270            }
9271            if let Some(ref action) = fk_ref.on_update {
9272                self.write_space();
9273                self.write_keyword("ON UPDATE");
9274                self.write_space();
9275                self.generate_referential_action(action);
9276            }
9277        }
9278
9279        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
9280        if fk_ref.match_after_actions {
9281            if let Some(ref match_type) = fk_ref.match_type {
9282                self.write_space();
9283                self.write_keyword("MATCH");
9284                self.write_space();
9285                match match_type {
9286                    MatchType::Full => self.write_keyword("FULL"),
9287                    MatchType::Partial => self.write_keyword("PARTIAL"),
9288                    MatchType::Simple => self.write_keyword("SIMPLE"),
9289                }
9290            }
9291        }
9292
9293        // DEFERRABLE / NOT DEFERRABLE
9294        if let Some(deferrable) = fk_ref.deferrable {
9295            self.write_space();
9296            if deferrable {
9297                self.write_keyword("DEFERRABLE");
9298            } else {
9299                self.write_keyword("NOT DEFERRABLE");
9300            }
9301        }
9302
9303        Ok(())
9304    }
9305
9306    fn generate_referential_action(&mut self, action: &ReferentialAction) {
9307        match action {
9308            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
9309            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
9310            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
9311            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
9312            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
9313        }
9314    }
9315
9316    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
9317        // Athena: DROP TABLE uses Hive engine (backticks)
9318        let saved_athena_hive_context = self.athena_hive_context;
9319        if matches!(
9320            self.config.dialect,
9321            Some(crate::dialects::DialectType::Athena)
9322        ) {
9323            self.athena_hive_context = true;
9324        }
9325
9326        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
9327        for comment in &dt.leading_comments {
9328            self.write_formatted_comment(comment);
9329            self.write_space();
9330        }
9331        self.write_keyword("DROP TABLE");
9332
9333        if dt.if_exists {
9334            self.write_space();
9335            self.write_keyword("IF EXISTS");
9336        }
9337
9338        self.write_space();
9339        for (i, table) in dt.names.iter().enumerate() {
9340            if i > 0 {
9341                self.write(", ");
9342            }
9343            self.generate_table(table)?;
9344        }
9345
9346        if dt.cascade_constraints {
9347            self.write_space();
9348            self.write_keyword("CASCADE CONSTRAINTS");
9349        } else if dt.cascade {
9350            self.write_space();
9351            self.write_keyword("CASCADE");
9352        }
9353
9354        if dt.purge {
9355            self.write_space();
9356            self.write_keyword("PURGE");
9357        }
9358
9359        // Restore Athena Hive context
9360        self.athena_hive_context = saved_athena_hive_context;
9361
9362        Ok(())
9363    }
9364
9365    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
9366        // Athena: ALTER TABLE uses Hive engine (backticks)
9367        let saved_athena_hive_context = self.athena_hive_context;
9368        if matches!(
9369            self.config.dialect,
9370            Some(crate::dialects::DialectType::Athena)
9371        ) {
9372            self.athena_hive_context = true;
9373        }
9374
9375        self.write_keyword("ALTER TABLE");
9376        if at.if_exists {
9377            self.write_space();
9378            self.write_keyword("IF EXISTS");
9379        }
9380        self.write_space();
9381        self.generate_table(&at.name)?;
9382
9383        // ClickHouse: ON CLUSTER clause
9384        if let Some(ref on_cluster) = at.on_cluster {
9385            self.write_space();
9386            self.generate_on_cluster(on_cluster)?;
9387        }
9388
9389        // Hive: PARTITION(key=value, ...) clause
9390        if let Some(ref partition) = at.partition {
9391            self.write_space();
9392            self.write_keyword("PARTITION");
9393            self.write("(");
9394            for (i, (key, value)) in partition.iter().enumerate() {
9395                if i > 0 {
9396                    self.write(", ");
9397                }
9398                self.generate_identifier(key)?;
9399                self.write(" = ");
9400                self.generate_expression(value)?;
9401            }
9402            self.write(")");
9403        }
9404
9405        // TSQL: WITH CHECK / WITH NOCHECK modifier
9406        if let Some(ref with_check) = at.with_check {
9407            self.write_space();
9408            self.write_keyword(with_check);
9409        }
9410
9411        if self.config.pretty {
9412            // In pretty mode, format actions with newlines and indentation
9413            self.write_newline();
9414            self.indent_level += 1;
9415            for (i, action) in at.actions.iter().enumerate() {
9416                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
9417                let is_continuation = i > 0
9418                    && matches!(
9419                        (&at.actions[i - 1], action),
9420                        (
9421                            AlterTableAction::AddColumn { .. },
9422                            AlterTableAction::AddColumn { .. }
9423                        ) | (
9424                            AlterTableAction::AddConstraint(_),
9425                            AlterTableAction::AddConstraint(_)
9426                        )
9427                    );
9428                if i > 0 {
9429                    self.write(",");
9430                    self.write_newline();
9431                }
9432                self.write_indent();
9433                self.generate_alter_action_with_continuation(action, is_continuation)?;
9434            }
9435            self.indent_level -= 1;
9436        } else {
9437            for (i, action) in at.actions.iter().enumerate() {
9438                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
9439                let is_continuation = i > 0
9440                    && matches!(
9441                        (&at.actions[i - 1], action),
9442                        (
9443                            AlterTableAction::AddColumn { .. },
9444                            AlterTableAction::AddColumn { .. }
9445                        ) | (
9446                            AlterTableAction::AddConstraint(_),
9447                            AlterTableAction::AddConstraint(_)
9448                        )
9449                    );
9450                if i > 0 {
9451                    self.write(",");
9452                }
9453                self.write_space();
9454                self.generate_alter_action_with_continuation(action, is_continuation)?;
9455            }
9456        }
9457
9458        // MySQL ALTER TABLE trailing options
9459        if let Some(ref algorithm) = at.algorithm {
9460            self.write(", ");
9461            self.write_keyword("ALGORITHM");
9462            self.write("=");
9463            self.write_keyword(algorithm);
9464        }
9465        if let Some(ref lock) = at.lock {
9466            self.write(", ");
9467            self.write_keyword("LOCK");
9468            self.write("=");
9469            self.write_keyword(lock);
9470        }
9471
9472        // Restore Athena Hive context
9473        self.athena_hive_context = saved_athena_hive_context;
9474
9475        Ok(())
9476    }
9477
9478    fn generate_alter_action_with_continuation(
9479        &mut self,
9480        action: &AlterTableAction,
9481        is_continuation: bool,
9482    ) -> Result<()> {
9483        match action {
9484            AlterTableAction::AddColumn {
9485                column,
9486                if_not_exists,
9487                position,
9488            } => {
9489                use crate::dialects::DialectType;
9490                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
9491                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
9492                // For other dialects, repeat ADD COLUMN for each
9493                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
9494                let is_tsql_like = matches!(
9495                    self.config.dialect,
9496                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
9497                );
9498                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
9499                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
9500
9501                if is_continuation && (is_snowflake || is_tsql_like) {
9502                    // Don't write ADD keyword for continuation in Snowflake/TSQL
9503                } else if is_snowflake {
9504                    self.write_keyword("ADD");
9505                    self.write_space();
9506                } else if is_athena {
9507                    // Athena uses ADD COLUMNS (col_def) syntax
9508                    self.write_keyword("ADD COLUMNS");
9509                    self.write(" (");
9510                } else if self.config.alter_table_include_column_keyword {
9511                    self.write_keyword("ADD COLUMN");
9512                    self.write_space();
9513                } else {
9514                    // Dialects like Oracle and TSQL don't use COLUMN keyword
9515                    self.write_keyword("ADD");
9516                    self.write_space();
9517                }
9518
9519                if *if_not_exists {
9520                    self.write_keyword("IF NOT EXISTS");
9521                    self.write_space();
9522                }
9523                self.generate_column_def(column)?;
9524
9525                // Close parenthesis for Athena
9526                if is_athena {
9527                    self.write(")");
9528                }
9529
9530                // Column position (FIRST or AFTER)
9531                if let Some(pos) = position {
9532                    self.write_space();
9533                    match pos {
9534                        ColumnPosition::First => self.write_keyword("FIRST"),
9535                        ColumnPosition::After(col_name) => {
9536                            self.write_keyword("AFTER");
9537                            self.write_space();
9538                            self.generate_identifier(col_name)?;
9539                        }
9540                    }
9541                }
9542            }
9543            AlterTableAction::DropColumn {
9544                name,
9545                if_exists,
9546                cascade,
9547            } => {
9548                self.write_keyword("DROP COLUMN");
9549                if *if_exists {
9550                    self.write_space();
9551                    self.write_keyword("IF EXISTS");
9552                }
9553                self.write_space();
9554                self.generate_identifier(name)?;
9555                if *cascade {
9556                    self.write_space();
9557                    self.write_keyword("CASCADE");
9558                }
9559            }
9560            AlterTableAction::DropColumns { names } => {
9561                self.write_keyword("DROP COLUMNS");
9562                self.write(" (");
9563                for (i, name) in names.iter().enumerate() {
9564                    if i > 0 {
9565                        self.write(", ");
9566                    }
9567                    self.generate_identifier(name)?;
9568                }
9569                self.write(")");
9570            }
9571            AlterTableAction::RenameColumn {
9572                old_name,
9573                new_name,
9574                if_exists,
9575            } => {
9576                self.write_keyword("RENAME COLUMN");
9577                if *if_exists {
9578                    self.write_space();
9579                    self.write_keyword("IF EXISTS");
9580                }
9581                self.write_space();
9582                self.generate_identifier(old_name)?;
9583                self.write_space();
9584                self.write_keyword("TO");
9585                self.write_space();
9586                self.generate_identifier(new_name)?;
9587            }
9588            AlterTableAction::AlterColumn {
9589                name,
9590                action,
9591                use_modify_keyword,
9592            } => {
9593                use crate::dialects::DialectType;
9594                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
9595                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
9596                let use_modify = *use_modify_keyword
9597                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
9598                        && matches!(action, AlterColumnAction::SetDataType { .. }));
9599                if use_modify {
9600                    self.write_keyword("MODIFY COLUMN");
9601                    self.write_space();
9602                    self.generate_identifier(name)?;
9603                    // For MODIFY COLUMN, output the type directly
9604                    if let AlterColumnAction::SetDataType {
9605                        data_type,
9606                        using: _,
9607                        collate,
9608                    } = action
9609                    {
9610                        self.write_space();
9611                        self.generate_data_type(data_type)?;
9612                        // Output COLLATE clause if present
9613                        if let Some(collate_name) = collate {
9614                            self.write_space();
9615                            self.write_keyword("COLLATE");
9616                            self.write_space();
9617                            // Output as single-quoted string
9618                            self.write(&format!("'{}'", collate_name));
9619                        }
9620                    } else {
9621                        self.write_space();
9622                        self.generate_alter_column_action(action)?;
9623                    }
9624                } else if matches!(self.config.dialect, Some(DialectType::Hive))
9625                    && matches!(action, AlterColumnAction::SetDataType { .. })
9626                {
9627                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
9628                    self.write_keyword("CHANGE COLUMN");
9629                    self.write_space();
9630                    self.generate_identifier(name)?;
9631                    self.write_space();
9632                    self.generate_identifier(name)?;
9633                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
9634                        self.write_space();
9635                        self.generate_data_type(data_type)?;
9636                    }
9637                } else {
9638                    self.write_keyword("ALTER COLUMN");
9639                    self.write_space();
9640                    self.generate_identifier(name)?;
9641                    self.write_space();
9642                    self.generate_alter_column_action(action)?;
9643                }
9644            }
9645            AlterTableAction::RenameTable(new_name) => {
9646                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
9647                let mysql_like = matches!(
9648                    self.config.dialect,
9649                    Some(DialectType::MySQL)
9650                        | Some(DialectType::Doris)
9651                        | Some(DialectType::StarRocks)
9652                        | Some(DialectType::SingleStore)
9653                );
9654                if mysql_like {
9655                    self.write_keyword("RENAME");
9656                } else {
9657                    self.write_keyword("RENAME TO");
9658                }
9659                self.write_space();
9660                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
9661                let rename_table_with_db = !matches!(
9662                    self.config.dialect,
9663                    Some(DialectType::Doris)
9664                        | Some(DialectType::DuckDB)
9665                        | Some(DialectType::BigQuery)
9666                        | Some(DialectType::PostgreSQL)
9667                );
9668                if !rename_table_with_db {
9669                    let mut stripped = new_name.clone();
9670                    stripped.schema = None;
9671                    stripped.catalog = None;
9672                    self.generate_table(&stripped)?;
9673                } else {
9674                    self.generate_table(new_name)?;
9675                }
9676            }
9677            AlterTableAction::AddConstraint(constraint) => {
9678                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
9679                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
9680                if !is_continuation {
9681                    self.write_keyword("ADD");
9682                    self.write_space();
9683                }
9684                self.generate_table_constraint(constraint)?;
9685            }
9686            AlterTableAction::DropConstraint { name, if_exists } => {
9687                self.write_keyword("DROP CONSTRAINT");
9688                if *if_exists {
9689                    self.write_space();
9690                    self.write_keyword("IF EXISTS");
9691                }
9692                self.write_space();
9693                self.generate_identifier(name)?;
9694            }
9695            AlterTableAction::DropForeignKey { name } => {
9696                self.write_keyword("DROP FOREIGN KEY");
9697                self.write_space();
9698                self.generate_identifier(name)?;
9699            }
9700            AlterTableAction::DropPartition {
9701                partitions,
9702                if_exists,
9703            } => {
9704                self.write_keyword("DROP");
9705                if *if_exists {
9706                    self.write_space();
9707                    self.write_keyword("IF EXISTS");
9708                }
9709                for (i, partition) in partitions.iter().enumerate() {
9710                    if i > 0 {
9711                        self.write(",");
9712                    }
9713                    self.write_space();
9714                    self.write_keyword("PARTITION");
9715                    // Check for special ClickHouse partition formats
9716                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
9717                        // ClickHouse: PARTITION <expression>
9718                        self.write_space();
9719                        self.generate_expression(&partition[0].1)?;
9720                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
9721                        // ClickHouse: PARTITION ALL
9722                        self.write_space();
9723                        self.write_keyword("ALL");
9724                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
9725                        // ClickHouse: PARTITION ID 'string'
9726                        self.write_space();
9727                        self.write_keyword("ID");
9728                        self.write_space();
9729                        self.generate_expression(&partition[0].1)?;
9730                    } else {
9731                        // Standard SQL: PARTITION(key=value, ...)
9732                        self.write("(");
9733                        for (j, (key, value)) in partition.iter().enumerate() {
9734                            if j > 0 {
9735                                self.write(", ");
9736                            }
9737                            self.generate_identifier(key)?;
9738                            self.write(" = ");
9739                            self.generate_expression(value)?;
9740                        }
9741                        self.write(")");
9742                    }
9743                }
9744            }
9745            AlterTableAction::Delete { where_clause } => {
9746                self.write_keyword("DELETE");
9747                self.write_space();
9748                self.write_keyword("WHERE");
9749                self.write_space();
9750                self.generate_expression(where_clause)?;
9751            }
9752            AlterTableAction::SwapWith(target) => {
9753                self.write_keyword("SWAP WITH");
9754                self.write_space();
9755                self.generate_table(target)?;
9756            }
9757            AlterTableAction::SetProperty { properties } => {
9758                use crate::dialects::DialectType;
9759                self.write_keyword("SET");
9760                // Trino/Presto use SET PROPERTIES syntax with spaces around =
9761                let is_trino_presto = matches!(
9762                    self.config.dialect,
9763                    Some(DialectType::Trino) | Some(DialectType::Presto)
9764                );
9765                if is_trino_presto {
9766                    self.write_space();
9767                    self.write_keyword("PROPERTIES");
9768                }
9769                let eq = if is_trino_presto { " = " } else { "=" };
9770                for (i, (key, value)) in properties.iter().enumerate() {
9771                    if i > 0 {
9772                        self.write(",");
9773                    }
9774                    self.write_space();
9775                    // Handle quoted property names for Trino
9776                    if key.contains(' ') {
9777                        self.generate_string_literal(key)?;
9778                    } else {
9779                        self.write(key);
9780                    }
9781                    self.write(eq);
9782                    self.generate_expression(value)?;
9783                }
9784            }
9785            AlterTableAction::UnsetProperty { properties } => {
9786                self.write_keyword("UNSET");
9787                for (i, name) in properties.iter().enumerate() {
9788                    if i > 0 {
9789                        self.write(",");
9790                    }
9791                    self.write_space();
9792                    self.write(name);
9793                }
9794            }
9795            AlterTableAction::ClusterBy { expressions } => {
9796                self.write_keyword("CLUSTER BY");
9797                self.write(" (");
9798                for (i, expr) in expressions.iter().enumerate() {
9799                    if i > 0 {
9800                        self.write(", ");
9801                    }
9802                    self.generate_expression(expr)?;
9803                }
9804                self.write(")");
9805            }
9806            AlterTableAction::SetTag { expressions } => {
9807                self.write_keyword("SET TAG");
9808                for (i, (key, value)) in expressions.iter().enumerate() {
9809                    if i > 0 {
9810                        self.write(",");
9811                    }
9812                    self.write_space();
9813                    self.write(key);
9814                    self.write(" = ");
9815                    self.generate_expression(value)?;
9816                }
9817            }
9818            AlterTableAction::UnsetTag { names } => {
9819                self.write_keyword("UNSET TAG");
9820                for (i, name) in names.iter().enumerate() {
9821                    if i > 0 {
9822                        self.write(",");
9823                    }
9824                    self.write_space();
9825                    self.write(name);
9826                }
9827            }
9828            AlterTableAction::SetOptions { expressions } => {
9829                self.write_keyword("SET");
9830                self.write(" (");
9831                for (i, expr) in expressions.iter().enumerate() {
9832                    if i > 0 {
9833                        self.write(", ");
9834                    }
9835                    self.generate_expression(expr)?;
9836                }
9837                self.write(")");
9838            }
9839            AlterTableAction::AlterIndex { name, visible } => {
9840                self.write_keyword("ALTER INDEX");
9841                self.write_space();
9842                self.generate_identifier(name)?;
9843                self.write_space();
9844                if *visible {
9845                    self.write_keyword("VISIBLE");
9846                } else {
9847                    self.write_keyword("INVISIBLE");
9848                }
9849            }
9850            AlterTableAction::SetAttribute { attribute } => {
9851                self.write_keyword("SET");
9852                self.write_space();
9853                self.write_keyword(attribute);
9854            }
9855            AlterTableAction::SetStageFileFormat { options } => {
9856                self.write_keyword("SET");
9857                self.write_space();
9858                self.write_keyword("STAGE_FILE_FORMAT");
9859                self.write(" = (");
9860                if let Some(opts) = options {
9861                    self.generate_space_separated_properties(opts)?;
9862                }
9863                self.write(")");
9864            }
9865            AlterTableAction::SetStageCopyOptions { options } => {
9866                self.write_keyword("SET");
9867                self.write_space();
9868                self.write_keyword("STAGE_COPY_OPTIONS");
9869                self.write(" = (");
9870                if let Some(opts) = options {
9871                    self.generate_space_separated_properties(opts)?;
9872                }
9873                self.write(")");
9874            }
9875            AlterTableAction::AddColumns { columns, cascade } => {
9876                // Oracle uses ADD (...) without COLUMNS keyword
9877                // Hive/Spark uses ADD COLUMNS (...)
9878                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
9879                if is_oracle {
9880                    self.write_keyword("ADD");
9881                } else {
9882                    self.write_keyword("ADD COLUMNS");
9883                }
9884                self.write(" (");
9885                for (i, col) in columns.iter().enumerate() {
9886                    if i > 0 {
9887                        self.write(", ");
9888                    }
9889                    self.generate_column_def(col)?;
9890                }
9891                self.write(")");
9892                if *cascade {
9893                    self.write_space();
9894                    self.write_keyword("CASCADE");
9895                }
9896            }
9897            AlterTableAction::ChangeColumn {
9898                old_name,
9899                new_name,
9900                data_type,
9901                comment,
9902                cascade,
9903            } => {
9904                use crate::dialects::DialectType;
9905                let is_spark = matches!(
9906                    self.config.dialect,
9907                    Some(DialectType::Spark) | Some(DialectType::Databricks)
9908                );
9909                let is_rename = old_name.name != new_name.name;
9910
9911                if is_spark {
9912                    if is_rename {
9913                        // Spark: RENAME COLUMN old TO new
9914                        self.write_keyword("RENAME COLUMN");
9915                        self.write_space();
9916                        self.generate_identifier(old_name)?;
9917                        self.write_space();
9918                        self.write_keyword("TO");
9919                        self.write_space();
9920                        self.generate_identifier(new_name)?;
9921                    } else if comment.is_some() {
9922                        // Spark: ALTER COLUMN old COMMENT 'comment'
9923                        self.write_keyword("ALTER COLUMN");
9924                        self.write_space();
9925                        self.generate_identifier(old_name)?;
9926                        self.write_space();
9927                        self.write_keyword("COMMENT");
9928                        self.write_space();
9929                        self.write("'");
9930                        self.write(comment.as_ref().unwrap());
9931                        self.write("'");
9932                    } else if data_type.is_some() {
9933                        // Spark: ALTER COLUMN old TYPE data_type
9934                        self.write_keyword("ALTER COLUMN");
9935                        self.write_space();
9936                        self.generate_identifier(old_name)?;
9937                        self.write_space();
9938                        self.write_keyword("TYPE");
9939                        self.write_space();
9940                        self.generate_data_type(data_type.as_ref().unwrap())?;
9941                    } else {
9942                        // Fallback to CHANGE COLUMN
9943                        self.write_keyword("CHANGE COLUMN");
9944                        self.write_space();
9945                        self.generate_identifier(old_name)?;
9946                        self.write_space();
9947                        self.generate_identifier(new_name)?;
9948                    }
9949                } else {
9950                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
9951                    if data_type.is_some() {
9952                        self.write_keyword("CHANGE COLUMN");
9953                    } else {
9954                        self.write_keyword("CHANGE");
9955                    }
9956                    self.write_space();
9957                    self.generate_identifier(old_name)?;
9958                    self.write_space();
9959                    self.generate_identifier(new_name)?;
9960                    if let Some(ref dt) = data_type {
9961                        self.write_space();
9962                        self.generate_data_type(dt)?;
9963                    }
9964                    if let Some(ref c) = comment {
9965                        self.write_space();
9966                        self.write_keyword("COMMENT");
9967                        self.write_space();
9968                        self.write("'");
9969                        self.write(c);
9970                        self.write("'");
9971                    }
9972                    if *cascade {
9973                        self.write_space();
9974                        self.write_keyword("CASCADE");
9975                    }
9976                }
9977            }
9978            AlterTableAction::AddPartition {
9979                partition,
9980                if_not_exists,
9981                location,
9982            } => {
9983                self.write_keyword("ADD");
9984                self.write_space();
9985                if *if_not_exists {
9986                    self.write_keyword("IF NOT EXISTS");
9987                    self.write_space();
9988                }
9989                self.generate_expression(partition)?;
9990                if let Some(ref loc) = location {
9991                    self.write_space();
9992                    self.write_keyword("LOCATION");
9993                    self.write_space();
9994                    self.generate_expression(loc)?;
9995                }
9996            }
9997            AlterTableAction::AlterSortKey {
9998                this,
9999                expressions,
10000                compound,
10001            } => {
10002                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
10003                self.write_keyword("ALTER");
10004                if *compound {
10005                    self.write_space();
10006                    self.write_keyword("COMPOUND");
10007                }
10008                self.write_space();
10009                self.write_keyword("SORTKEY");
10010                self.write_space();
10011                if let Some(style) = this {
10012                    self.write_keyword(style);
10013                } else if !expressions.is_empty() {
10014                    self.write("(");
10015                    for (i, expr) in expressions.iter().enumerate() {
10016                        if i > 0 {
10017                            self.write(", ");
10018                        }
10019                        self.generate_expression(expr)?;
10020                    }
10021                    self.write(")");
10022                }
10023            }
10024            AlterTableAction::AlterDistStyle { style, distkey } => {
10025                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
10026                self.write_keyword("ALTER");
10027                self.write_space();
10028                self.write_keyword("DISTSTYLE");
10029                self.write_space();
10030                self.write_keyword(style);
10031                if let Some(col) = distkey {
10032                    self.write_space();
10033                    self.write_keyword("DISTKEY");
10034                    self.write_space();
10035                    self.generate_identifier(col)?;
10036                }
10037            }
10038            AlterTableAction::SetTableProperties { properties } => {
10039                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
10040                self.write_keyword("SET TABLE PROPERTIES");
10041                self.write(" (");
10042                for (i, (key, value)) in properties.iter().enumerate() {
10043                    if i > 0 {
10044                        self.write(", ");
10045                    }
10046                    self.generate_expression(key)?;
10047                    self.write(" = ");
10048                    self.generate_expression(value)?;
10049                }
10050                self.write(")");
10051            }
10052            AlterTableAction::SetLocation { location } => {
10053                // Redshift: SET LOCATION 's3://bucket/folder/'
10054                self.write_keyword("SET LOCATION");
10055                self.write_space();
10056                self.write("'");
10057                self.write(location);
10058                self.write("'");
10059            }
10060            AlterTableAction::SetFileFormat { format } => {
10061                // Redshift: SET FILE FORMAT AVRO
10062                self.write_keyword("SET FILE FORMAT");
10063                self.write_space();
10064                self.write_keyword(format);
10065            }
10066            AlterTableAction::ReplacePartition { partition, source } => {
10067                // ClickHouse: REPLACE PARTITION expr FROM source
10068                self.write_keyword("REPLACE PARTITION");
10069                self.write_space();
10070                self.generate_expression(partition)?;
10071                if let Some(src) = source {
10072                    self.write_space();
10073                    self.write_keyword("FROM");
10074                    self.write_space();
10075                    self.generate_expression(src)?;
10076                }
10077            }
10078            AlterTableAction::Raw { sql } => {
10079                self.write(sql);
10080            }
10081        }
10082        Ok(())
10083    }
10084
10085    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
10086        match action {
10087            AlterColumnAction::SetDataType {
10088                data_type,
10089                using,
10090                collate,
10091            } => {
10092                use crate::dialects::DialectType;
10093                // Dialect-specific type change syntax:
10094                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
10095                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
10096                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
10097                let is_no_prefix = matches!(
10098                    self.config.dialect,
10099                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
10100                );
10101                let is_type_only = matches!(
10102                    self.config.dialect,
10103                    Some(DialectType::Redshift)
10104                        | Some(DialectType::Spark)
10105                        | Some(DialectType::Databricks)
10106                );
10107                if is_type_only {
10108                    self.write_keyword("TYPE");
10109                    self.write_space();
10110                } else if !is_no_prefix {
10111                    self.write_keyword("SET DATA TYPE");
10112                    self.write_space();
10113                }
10114                self.generate_data_type(data_type)?;
10115                if let Some(ref collation) = collate {
10116                    self.write_space();
10117                    self.write_keyword("COLLATE");
10118                    self.write_space();
10119                    self.write(collation);
10120                }
10121                if let Some(ref using_expr) = using {
10122                    self.write_space();
10123                    self.write_keyword("USING");
10124                    self.write_space();
10125                    self.generate_expression(using_expr)?;
10126                }
10127            }
10128            AlterColumnAction::SetDefault(expr) => {
10129                self.write_keyword("SET DEFAULT");
10130                self.write_space();
10131                self.generate_expression(expr)?;
10132            }
10133            AlterColumnAction::DropDefault => {
10134                self.write_keyword("DROP DEFAULT");
10135            }
10136            AlterColumnAction::SetNotNull => {
10137                self.write_keyword("SET NOT NULL");
10138            }
10139            AlterColumnAction::DropNotNull => {
10140                self.write_keyword("DROP NOT NULL");
10141            }
10142            AlterColumnAction::Comment(comment) => {
10143                self.write_keyword("COMMENT");
10144                self.write_space();
10145                self.generate_string_literal(comment)?;
10146            }
10147            AlterColumnAction::SetVisible => {
10148                self.write_keyword("SET VISIBLE");
10149            }
10150            AlterColumnAction::SetInvisible => {
10151                self.write_keyword("SET INVISIBLE");
10152            }
10153        }
10154        Ok(())
10155    }
10156
10157    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
10158        self.write_keyword("CREATE");
10159
10160        if ci.unique {
10161            self.write_space();
10162            self.write_keyword("UNIQUE");
10163        }
10164
10165        // TSQL CLUSTERED/NONCLUSTERED modifier
10166        if let Some(ref clustered) = ci.clustered {
10167            self.write_space();
10168            self.write_keyword(clustered);
10169        }
10170
10171        self.write_space();
10172        self.write_keyword("INDEX");
10173
10174        // PostgreSQL CONCURRENTLY modifier
10175        if ci.concurrently {
10176            self.write_space();
10177            self.write_keyword("CONCURRENTLY");
10178        }
10179
10180        if ci.if_not_exists {
10181            self.write_space();
10182            self.write_keyword("IF NOT EXISTS");
10183        }
10184
10185        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
10186        if !ci.name.name.is_empty() {
10187            self.write_space();
10188            self.generate_identifier(&ci.name)?;
10189        }
10190        self.write_space();
10191        self.write_keyword("ON");
10192        // Hive uses ON TABLE
10193        if matches!(self.config.dialect, Some(DialectType::Hive)) {
10194            self.write_space();
10195            self.write_keyword("TABLE");
10196        }
10197        self.write_space();
10198        self.generate_table(&ci.table)?;
10199
10200        // Column list (optional for COLUMNSTORE indexes)
10201        // Standard SQL convention: ON t(a) without space before paren
10202        if !ci.columns.is_empty() || ci.using.is_some() {
10203            let space_before_paren = false;
10204
10205            if let Some(ref using) = ci.using {
10206                self.write_space();
10207                self.write_keyword("USING");
10208                self.write_space();
10209                self.write(using);
10210                if space_before_paren {
10211                    self.write(" (");
10212                } else {
10213                    self.write("(");
10214                }
10215            } else {
10216                if space_before_paren {
10217                    self.write(" (");
10218                } else {
10219                    self.write("(");
10220                }
10221            }
10222            for (i, col) in ci.columns.iter().enumerate() {
10223                if i > 0 {
10224                    self.write(", ");
10225                }
10226                self.generate_identifier(&col.column)?;
10227                if let Some(ref opclass) = col.opclass {
10228                    self.write_space();
10229                    self.write(opclass);
10230                }
10231                if col.desc {
10232                    self.write_space();
10233                    self.write_keyword("DESC");
10234                } else if col.asc {
10235                    self.write_space();
10236                    self.write_keyword("ASC");
10237                }
10238                if let Some(nulls_first) = col.nulls_first {
10239                    self.write_space();
10240                    self.write_keyword("NULLS");
10241                    self.write_space();
10242                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
10243                }
10244            }
10245            self.write(")");
10246        }
10247
10248        // PostgreSQL INCLUDE (col1, col2) clause
10249        if !ci.include_columns.is_empty() {
10250            self.write_space();
10251            self.write_keyword("INCLUDE");
10252            self.write(" (");
10253            for (i, col) in ci.include_columns.iter().enumerate() {
10254                if i > 0 {
10255                    self.write(", ");
10256                }
10257                self.generate_identifier(col)?;
10258            }
10259            self.write(")");
10260        }
10261
10262        // TSQL: WITH (option=value, ...) clause
10263        if !ci.with_options.is_empty() {
10264            self.write_space();
10265            self.write_keyword("WITH");
10266            self.write(" (");
10267            for (i, (key, value)) in ci.with_options.iter().enumerate() {
10268                if i > 0 {
10269                    self.write(", ");
10270                }
10271                self.write(key);
10272                self.write("=");
10273                self.write(value);
10274            }
10275            self.write(")");
10276        }
10277
10278        // PostgreSQL WHERE clause for partial indexes
10279        if let Some(ref where_clause) = ci.where_clause {
10280            self.write_space();
10281            self.write_keyword("WHERE");
10282            self.write_space();
10283            self.generate_expression(where_clause)?;
10284        }
10285
10286        // TSQL: ON filegroup or partition scheme clause
10287        if let Some(ref on_fg) = ci.on_filegroup {
10288            self.write_space();
10289            self.write_keyword("ON");
10290            self.write_space();
10291            self.write(on_fg);
10292        }
10293
10294        Ok(())
10295    }
10296
10297    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
10298        self.write_keyword("DROP INDEX");
10299
10300        if di.concurrently {
10301            self.write_space();
10302            self.write_keyword("CONCURRENTLY");
10303        }
10304
10305        if di.if_exists {
10306            self.write_space();
10307            self.write_keyword("IF EXISTS");
10308        }
10309
10310        self.write_space();
10311        self.generate_identifier(&di.name)?;
10312
10313        if let Some(ref table) = di.table {
10314            self.write_space();
10315            self.write_keyword("ON");
10316            self.write_space();
10317            self.generate_table(table)?;
10318        }
10319
10320        Ok(())
10321    }
10322
10323    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
10324        self.write_keyword("CREATE");
10325
10326        // MySQL: ALGORITHM=...
10327        if let Some(ref algorithm) = cv.algorithm {
10328            self.write_space();
10329            self.write_keyword("ALGORITHM");
10330            self.write("=");
10331            self.write_keyword(algorithm);
10332        }
10333
10334        // MySQL: DEFINER=...
10335        if let Some(ref definer) = cv.definer {
10336            self.write_space();
10337            self.write_keyword("DEFINER");
10338            self.write("=");
10339            self.write(definer);
10340        }
10341
10342        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword)
10343        if cv.security_sql_style {
10344            if let Some(ref security) = cv.security {
10345                self.write_space();
10346                self.write_keyword("SQL SECURITY");
10347                self.write_space();
10348                match security {
10349                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
10350                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
10351                    FunctionSecurity::None => self.write_keyword("NONE"),
10352                }
10353            }
10354        }
10355
10356        if cv.or_replace {
10357            self.write_space();
10358            self.write_keyword("OR REPLACE");
10359        }
10360
10361        if cv.temporary {
10362            self.write_space();
10363            self.write_keyword("TEMPORARY");
10364        }
10365
10366        if cv.materialized {
10367            self.write_space();
10368            self.write_keyword("MATERIALIZED");
10369        }
10370
10371        // Snowflake: SECURE VIEW
10372        if cv.secure {
10373            self.write_space();
10374            self.write_keyword("SECURE");
10375        }
10376
10377        self.write_space();
10378        self.write_keyword("VIEW");
10379
10380        if cv.if_not_exists {
10381            self.write_space();
10382            self.write_keyword("IF NOT EXISTS");
10383        }
10384
10385        self.write_space();
10386        self.generate_table(&cv.name)?;
10387
10388        // ClickHouse: ON CLUSTER clause
10389        if let Some(ref on_cluster) = cv.on_cluster {
10390            self.write_space();
10391            self.generate_on_cluster(on_cluster)?;
10392        }
10393
10394        // ClickHouse: TO destination_table
10395        if let Some(ref to_table) = cv.to_table {
10396            self.write_space();
10397            self.write_keyword("TO");
10398            self.write_space();
10399            self.generate_table(to_table)?;
10400        }
10401
10402        // For regular VIEW: columns come before COPY GRANTS
10403        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
10404        if !cv.materialized {
10405            // Regular VIEW: columns first
10406            if !cv.columns.is_empty() {
10407                self.write(" (");
10408                for (i, col) in cv.columns.iter().enumerate() {
10409                    if i > 0 {
10410                        self.write(", ");
10411                    }
10412                    self.generate_identifier(&col.name)?;
10413                    // BigQuery: OPTIONS (key=value, ...) on view column
10414                    if !col.options.is_empty() {
10415                        self.write_space();
10416                        self.generate_options_clause(&col.options)?;
10417                    }
10418                    if let Some(ref comment) = col.comment {
10419                        self.write_space();
10420                        self.write_keyword("COMMENT");
10421                        self.write_space();
10422                        self.generate_string_literal(comment)?;
10423                    }
10424                }
10425                self.write(")");
10426            }
10427
10428            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
10429            if !cv.security_sql_style {
10430                if let Some(ref security) = cv.security {
10431                    self.write_space();
10432                    self.write_keyword("SECURITY");
10433                    self.write_space();
10434                    match security {
10435                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
10436                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
10437                        FunctionSecurity::None => self.write_keyword("NONE"),
10438                    }
10439                }
10440            }
10441
10442            // Snowflake: COPY GRANTS
10443            if cv.copy_grants {
10444                self.write_space();
10445                self.write_keyword("COPY GRANTS");
10446            }
10447        } else {
10448            // MATERIALIZED VIEW: COPY GRANTS first
10449            if cv.copy_grants {
10450                self.write_space();
10451                self.write_keyword("COPY GRANTS");
10452            }
10453
10454            // Doris: If we have a schema (typed columns), generate that instead
10455            if let Some(ref schema) = cv.schema {
10456                self.write(" (");
10457                for (i, expr) in schema.expressions.iter().enumerate() {
10458                    if i > 0 {
10459                        self.write(", ");
10460                    }
10461                    self.generate_expression(expr)?;
10462                }
10463                self.write(")");
10464            } else if !cv.columns.is_empty() {
10465                // Then columns (simple column names without types)
10466                self.write(" (");
10467                for (i, col) in cv.columns.iter().enumerate() {
10468                    if i > 0 {
10469                        self.write(", ");
10470                    }
10471                    self.generate_identifier(&col.name)?;
10472                    // BigQuery: OPTIONS (key=value, ...) on view column
10473                    if !col.options.is_empty() {
10474                        self.write_space();
10475                        self.generate_options_clause(&col.options)?;
10476                    }
10477                    if let Some(ref comment) = col.comment {
10478                        self.write_space();
10479                        self.write_keyword("COMMENT");
10480                        self.write_space();
10481                        self.generate_string_literal(comment)?;
10482                    }
10483                }
10484                self.write(")");
10485            }
10486
10487            // Doris: KEY (columns) for materialized views
10488            if let Some(ref unique_key) = cv.unique_key {
10489                self.write_space();
10490                self.write_keyword("KEY");
10491                self.write(" (");
10492                for (i, expr) in unique_key.expressions.iter().enumerate() {
10493                    if i > 0 {
10494                        self.write(", ");
10495                    }
10496                    self.generate_expression(expr)?;
10497                }
10498                self.write(")");
10499            }
10500        }
10501
10502        // Snowflake: COMMENT = 'text'
10503        if let Some(ref comment) = cv.comment {
10504            self.write_space();
10505            self.write_keyword("COMMENT");
10506            self.write("=");
10507            self.generate_string_literal(comment)?;
10508        }
10509
10510        // Snowflake: TAG (name='value', ...)
10511        if !cv.tags.is_empty() {
10512            self.write_space();
10513            self.write_keyword("TAG");
10514            self.write(" (");
10515            for (i, (name, value)) in cv.tags.iter().enumerate() {
10516                if i > 0 {
10517                    self.write(", ");
10518                }
10519                self.write(name);
10520                self.write("='");
10521                self.write(value);
10522                self.write("'");
10523            }
10524            self.write(")");
10525        }
10526
10527        // BigQuery: OPTIONS (key=value, ...)
10528        if !cv.options.is_empty() {
10529            self.write_space();
10530            self.generate_options_clause(&cv.options)?;
10531        }
10532
10533        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
10534        if let Some(ref build) = cv.build {
10535            self.write_space();
10536            self.write_keyword("BUILD");
10537            self.write_space();
10538            self.write_keyword(build);
10539        }
10540
10541        // Doris: REFRESH clause for materialized views
10542        if let Some(ref refresh) = cv.refresh {
10543            self.write_space();
10544            self.generate_refresh_trigger_property(refresh)?;
10545        }
10546
10547        // Redshift: AUTO REFRESH YES|NO for materialized views
10548        if let Some(auto_refresh) = cv.auto_refresh {
10549            self.write_space();
10550            self.write_keyword("AUTO REFRESH");
10551            self.write_space();
10552            if auto_refresh {
10553                self.write_keyword("YES");
10554            } else {
10555                self.write_keyword("NO");
10556            }
10557        }
10558
10559        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
10560        for prop in &cv.table_properties {
10561            self.write_space();
10562            self.generate_expression(prop)?;
10563        }
10564
10565        // Only output AS clause if there's a real query (not just NULL placeholder)
10566        if !matches!(&cv.query, Expression::Null(_)) {
10567            self.write_space();
10568            self.write_keyword("AS");
10569            self.write_space();
10570
10571            // Teradata: LOCKING clause (between AS and query)
10572            if let Some(ref mode) = cv.locking_mode {
10573                self.write_keyword("LOCKING");
10574                self.write_space();
10575                self.write_keyword(mode);
10576                if let Some(ref access) = cv.locking_access {
10577                    self.write_space();
10578                    self.write_keyword("FOR");
10579                    self.write_space();
10580                    self.write_keyword(access);
10581                }
10582                self.write_space();
10583            }
10584
10585            if cv.query_parenthesized {
10586                self.write("(");
10587            }
10588            self.generate_expression(&cv.query)?;
10589            if cv.query_parenthesized {
10590                self.write(")");
10591            }
10592        }
10593
10594        // Redshift: WITH NO SCHEMA BINDING (after query)
10595        if cv.no_schema_binding {
10596            self.write_space();
10597            self.write_keyword("WITH NO SCHEMA BINDING");
10598        }
10599
10600        Ok(())
10601    }
10602
10603    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
10604        self.write_keyword("DROP");
10605
10606        if dv.materialized {
10607            self.write_space();
10608            self.write_keyword("MATERIALIZED");
10609        }
10610
10611        self.write_space();
10612        self.write_keyword("VIEW");
10613
10614        if dv.if_exists {
10615            self.write_space();
10616            self.write_keyword("IF EXISTS");
10617        }
10618
10619        self.write_space();
10620        self.generate_table(&dv.name)?;
10621
10622        Ok(())
10623    }
10624
10625    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
10626        match tr.target {
10627            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
10628            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
10629        }
10630        if tr.if_exists {
10631            self.write_space();
10632            self.write_keyword("IF EXISTS");
10633        }
10634        self.write_space();
10635        self.generate_table(&tr.table)?;
10636
10637        // ClickHouse: ON CLUSTER clause
10638        if let Some(ref on_cluster) = tr.on_cluster {
10639            self.write_space();
10640            self.generate_on_cluster(on_cluster)?;
10641        }
10642
10643        // Check if first table has a * (multi-table with star)
10644        if !tr.extra_tables.is_empty() {
10645            // Check if the first entry matches the main table (star case)
10646            let skip_first = if let Some(first) = tr.extra_tables.first() {
10647                first.table.name == tr.table.name && first.star
10648            } else {
10649                false
10650            };
10651
10652            // PostgreSQL normalizes away the * suffix (it's the default behavior)
10653            let strip_star = matches!(
10654                self.config.dialect,
10655                Some(crate::dialects::DialectType::PostgreSQL)
10656                    | Some(crate::dialects::DialectType::Redshift)
10657            );
10658            if skip_first && !strip_star {
10659                self.write("*");
10660            }
10661
10662            // Generate additional tables
10663            for (i, entry) in tr.extra_tables.iter().enumerate() {
10664                if i == 0 && skip_first {
10665                    continue; // Already handled the star for first table
10666                }
10667                self.write(", ");
10668                self.generate_table(&entry.table)?;
10669                if entry.star && !strip_star {
10670                    self.write("*");
10671                }
10672            }
10673        }
10674
10675        // RESTART/CONTINUE IDENTITY
10676        if let Some(identity) = &tr.identity {
10677            self.write_space();
10678            match identity {
10679                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
10680                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
10681            }
10682        }
10683
10684        if tr.cascade {
10685            self.write_space();
10686            self.write_keyword("CASCADE");
10687        }
10688
10689        if tr.restrict {
10690            self.write_space();
10691            self.write_keyword("RESTRICT");
10692        }
10693
10694        // Output Hive PARTITION clause
10695        if let Some(ref partition) = tr.partition {
10696            self.write_space();
10697            self.generate_expression(partition)?;
10698        }
10699
10700        Ok(())
10701    }
10702
10703    fn generate_use(&mut self, u: &Use) -> Result<()> {
10704        // Teradata uses "DATABASE <name>" instead of "USE <name>"
10705        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
10706            self.write_keyword("DATABASE");
10707            self.write_space();
10708            self.generate_identifier(&u.this)?;
10709            return Ok(());
10710        }
10711
10712        self.write_keyword("USE");
10713
10714        if let Some(kind) = &u.kind {
10715            self.write_space();
10716            match kind {
10717                UseKind::Database => self.write_keyword("DATABASE"),
10718                UseKind::Schema => self.write_keyword("SCHEMA"),
10719                UseKind::Role => self.write_keyword("ROLE"),
10720                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
10721                UseKind::Catalog => self.write_keyword("CATALOG"),
10722                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
10723            }
10724        }
10725
10726        self.write_space();
10727        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
10728        // without quoting, since these are keywords not identifiers
10729        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
10730            self.write(&u.this.name);
10731        } else {
10732            self.generate_identifier(&u.this)?;
10733        }
10734        Ok(())
10735    }
10736
10737    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
10738        self.write_keyword("CACHE");
10739        if c.lazy {
10740            self.write_space();
10741            self.write_keyword("LAZY");
10742        }
10743        self.write_space();
10744        self.write_keyword("TABLE");
10745        self.write_space();
10746        self.generate_identifier(&c.table)?;
10747
10748        // OPTIONS clause
10749        if !c.options.is_empty() {
10750            self.write_space();
10751            self.write_keyword("OPTIONS");
10752            self.write("(");
10753            for (i, (key, value)) in c.options.iter().enumerate() {
10754                if i > 0 {
10755                    self.write(", ");
10756                }
10757                self.generate_expression(key)?;
10758                self.write(" = ");
10759                self.generate_expression(value)?;
10760            }
10761            self.write(")");
10762        }
10763
10764        // AS query
10765        if let Some(query) = &c.query {
10766            self.write_space();
10767            self.write_keyword("AS");
10768            self.write_space();
10769            self.generate_expression(query)?;
10770        }
10771
10772        Ok(())
10773    }
10774
10775    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
10776        self.write_keyword("UNCACHE TABLE");
10777        if u.if_exists {
10778            self.write_space();
10779            self.write_keyword("IF EXISTS");
10780        }
10781        self.write_space();
10782        self.generate_identifier(&u.table)?;
10783        Ok(())
10784    }
10785
10786    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
10787        self.write_keyword("LOAD DATA");
10788        if l.local {
10789            self.write_space();
10790            self.write_keyword("LOCAL");
10791        }
10792        self.write_space();
10793        self.write_keyword("INPATH");
10794        self.write_space();
10795        self.write("'");
10796        self.write(&l.inpath);
10797        self.write("'");
10798
10799        if l.overwrite {
10800            self.write_space();
10801            self.write_keyword("OVERWRITE");
10802        }
10803
10804        self.write_space();
10805        self.write_keyword("INTO TABLE");
10806        self.write_space();
10807        self.generate_expression(&l.table)?;
10808
10809        // PARTITION clause
10810        if !l.partition.is_empty() {
10811            self.write_space();
10812            self.write_keyword("PARTITION");
10813            self.write("(");
10814            for (i, (col, val)) in l.partition.iter().enumerate() {
10815                if i > 0 {
10816                    self.write(", ");
10817                }
10818                self.generate_identifier(col)?;
10819                self.write(" = ");
10820                self.generate_expression(val)?;
10821            }
10822            self.write(")");
10823        }
10824
10825        // INPUTFORMAT clause
10826        if let Some(fmt) = &l.input_format {
10827            self.write_space();
10828            self.write_keyword("INPUTFORMAT");
10829            self.write_space();
10830            self.write("'");
10831            self.write(fmt);
10832            self.write("'");
10833        }
10834
10835        // SERDE clause
10836        if let Some(serde) = &l.serde {
10837            self.write_space();
10838            self.write_keyword("SERDE");
10839            self.write_space();
10840            self.write("'");
10841            self.write(serde);
10842            self.write("'");
10843        }
10844
10845        Ok(())
10846    }
10847
10848    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
10849        self.write_keyword("PRAGMA");
10850        self.write_space();
10851
10852        // Schema prefix if present
10853        if let Some(schema) = &p.schema {
10854            self.generate_identifier(schema)?;
10855            self.write(".");
10856        }
10857
10858        // Pragma name
10859        self.generate_identifier(&p.name)?;
10860
10861        // Value assignment or function call
10862        if let Some(value) = &p.value {
10863            self.write(" = ");
10864            self.generate_expression(value)?;
10865        } else if !p.args.is_empty() {
10866            self.write("(");
10867            for (i, arg) in p.args.iter().enumerate() {
10868                if i > 0 {
10869                    self.write(", ");
10870                }
10871                self.generate_expression(arg)?;
10872            }
10873            self.write(")");
10874        }
10875
10876        Ok(())
10877    }
10878
10879    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
10880        self.write_keyword("GRANT");
10881        self.write_space();
10882
10883        // Privileges (with optional column lists)
10884        for (i, privilege) in g.privileges.iter().enumerate() {
10885            if i > 0 {
10886                self.write(", ");
10887            }
10888            self.write_keyword(&privilege.name);
10889            // Output column list if present: SELECT(col1, col2)
10890            if !privilege.columns.is_empty() {
10891                self.write("(");
10892                for (j, col) in privilege.columns.iter().enumerate() {
10893                    if j > 0 {
10894                        self.write(", ");
10895                    }
10896                    self.write(col);
10897                }
10898                self.write(")");
10899            }
10900        }
10901
10902        self.write_space();
10903        self.write_keyword("ON");
10904        self.write_space();
10905
10906        // Object kind (TABLE, SCHEMA, etc.)
10907        if let Some(kind) = &g.kind {
10908            self.write_keyword(kind);
10909            self.write_space();
10910        }
10911
10912        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
10913        {
10914            use crate::dialects::DialectType;
10915            let should_upper = matches!(
10916                self.config.dialect,
10917                Some(DialectType::PostgreSQL)
10918                    | Some(DialectType::CockroachDB)
10919                    | Some(DialectType::Materialize)
10920                    | Some(DialectType::RisingWave)
10921            ) && (g.kind.as_deref() == Some("FUNCTION")
10922                || g.kind.as_deref() == Some("PROCEDURE"));
10923            if should_upper {
10924                use crate::expressions::Identifier;
10925                let upper_id = Identifier {
10926                    name: g.securable.name.to_uppercase(),
10927                    quoted: g.securable.quoted,
10928                    ..g.securable.clone()
10929                };
10930                self.generate_identifier(&upper_id)?;
10931            } else {
10932                self.generate_identifier(&g.securable)?;
10933            }
10934        }
10935
10936        // Function parameter types (if present)
10937        if !g.function_params.is_empty() {
10938            self.write("(");
10939            for (i, param) in g.function_params.iter().enumerate() {
10940                if i > 0 {
10941                    self.write(", ");
10942                }
10943                self.write(param);
10944            }
10945            self.write(")");
10946        }
10947
10948        self.write_space();
10949        self.write_keyword("TO");
10950        self.write_space();
10951
10952        // Principals
10953        for (i, principal) in g.principals.iter().enumerate() {
10954            if i > 0 {
10955                self.write(", ");
10956            }
10957            if principal.is_role {
10958                self.write_keyword("ROLE");
10959                self.write_space();
10960            } else if principal.is_group {
10961                self.write_keyword("GROUP");
10962                self.write_space();
10963            }
10964            self.generate_identifier(&principal.name)?;
10965        }
10966
10967        // WITH GRANT OPTION
10968        if g.grant_option {
10969            self.write_space();
10970            self.write_keyword("WITH GRANT OPTION");
10971        }
10972
10973        // TSQL: AS principal
10974        if let Some(ref principal) = g.as_principal {
10975            self.write_space();
10976            self.write_keyword("AS");
10977            self.write_space();
10978            self.generate_identifier(principal)?;
10979        }
10980
10981        Ok(())
10982    }
10983
10984    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
10985        self.write_keyword("REVOKE");
10986        self.write_space();
10987
10988        // GRANT OPTION FOR
10989        if r.grant_option {
10990            self.write_keyword("GRANT OPTION FOR");
10991            self.write_space();
10992        }
10993
10994        // Privileges (with optional column lists)
10995        for (i, privilege) in r.privileges.iter().enumerate() {
10996            if i > 0 {
10997                self.write(", ");
10998            }
10999            self.write_keyword(&privilege.name);
11000            // Output column list if present: SELECT(col1, col2)
11001            if !privilege.columns.is_empty() {
11002                self.write("(");
11003                for (j, col) in privilege.columns.iter().enumerate() {
11004                    if j > 0 {
11005                        self.write(", ");
11006                    }
11007                    self.write(col);
11008                }
11009                self.write(")");
11010            }
11011        }
11012
11013        self.write_space();
11014        self.write_keyword("ON");
11015        self.write_space();
11016
11017        // Object kind
11018        if let Some(kind) = &r.kind {
11019            self.write_keyword(kind);
11020            self.write_space();
11021        }
11022
11023        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11024        {
11025            use crate::dialects::DialectType;
11026            let should_upper = matches!(
11027                self.config.dialect,
11028                Some(DialectType::PostgreSQL)
11029                    | Some(DialectType::CockroachDB)
11030                    | Some(DialectType::Materialize)
11031                    | Some(DialectType::RisingWave)
11032            ) && (r.kind.as_deref() == Some("FUNCTION")
11033                || r.kind.as_deref() == Some("PROCEDURE"));
11034            if should_upper {
11035                use crate::expressions::Identifier;
11036                let upper_id = Identifier {
11037                    name: r.securable.name.to_uppercase(),
11038                    quoted: r.securable.quoted,
11039                    ..r.securable.clone()
11040                };
11041                self.generate_identifier(&upper_id)?;
11042            } else {
11043                self.generate_identifier(&r.securable)?;
11044            }
11045        }
11046
11047        // Function parameter types (if present)
11048        if !r.function_params.is_empty() {
11049            self.write("(");
11050            for (i, param) in r.function_params.iter().enumerate() {
11051                if i > 0 {
11052                    self.write(", ");
11053                }
11054                self.write(param);
11055            }
11056            self.write(")");
11057        }
11058
11059        self.write_space();
11060        self.write_keyword("FROM");
11061        self.write_space();
11062
11063        // Principals
11064        for (i, principal) in r.principals.iter().enumerate() {
11065            if i > 0 {
11066                self.write(", ");
11067            }
11068            if principal.is_role {
11069                self.write_keyword("ROLE");
11070                self.write_space();
11071            } else if principal.is_group {
11072                self.write_keyword("GROUP");
11073                self.write_space();
11074            }
11075            self.generate_identifier(&principal.name)?;
11076        }
11077
11078        // CASCADE or RESTRICT
11079        if r.cascade {
11080            self.write_space();
11081            self.write_keyword("CASCADE");
11082        } else if r.restrict {
11083            self.write_space();
11084            self.write_keyword("RESTRICT");
11085        }
11086
11087        Ok(())
11088    }
11089
11090    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
11091        self.write_keyword("COMMENT");
11092
11093        // IF EXISTS
11094        if c.exists {
11095            self.write_space();
11096            self.write_keyword("IF EXISTS");
11097        }
11098
11099        self.write_space();
11100        self.write_keyword("ON");
11101
11102        // MATERIALIZED
11103        if c.materialized {
11104            self.write_space();
11105            self.write_keyword("MATERIALIZED");
11106        }
11107
11108        self.write_space();
11109        self.write_keyword(&c.kind);
11110        self.write_space();
11111
11112        // Object name
11113        self.generate_expression(&c.this)?;
11114
11115        self.write_space();
11116        self.write_keyword("IS");
11117        self.write_space();
11118
11119        // Comment expression
11120        self.generate_expression(&c.expression)?;
11121
11122        Ok(())
11123    }
11124
11125    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
11126        self.write_keyword("SET");
11127
11128        for (i, item) in s.items.iter().enumerate() {
11129            if i > 0 {
11130                self.write(",");
11131            }
11132            self.write_space();
11133
11134            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY)
11135            if let Some(ref kind) = item.kind {
11136                self.write_keyword(kind);
11137                self.write_space();
11138            }
11139
11140            // Check for special SET forms by name
11141            let name_str = match &item.name {
11142                Expression::Identifier(id) => Some(id.name.as_str()),
11143                _ => None,
11144            };
11145
11146            let is_transaction = name_str == Some("TRANSACTION");
11147            let is_character_set = name_str == Some("CHARACTER SET");
11148            let is_names = name_str == Some("NAMES");
11149            let is_collate = name_str == Some("COLLATE");
11150            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
11151            let name_has_variable_prefix = name_str.map_or(false, |n| n.starts_with("VARIABLE "));
11152            let is_variable = has_variable_kind || name_has_variable_prefix;
11153            let is_value_only =
11154                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
11155
11156            if is_transaction {
11157                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
11158                self.write_keyword("TRANSACTION");
11159                if let Expression::Identifier(id) = &item.value {
11160                    if !id.name.is_empty() {
11161                        self.write_space();
11162                        self.write(&id.name);
11163                    }
11164                }
11165            } else if is_character_set {
11166                // Output: SET CHARACTER SET <charset>
11167                self.write_keyword("CHARACTER SET");
11168                self.write_space();
11169                self.generate_set_value(&item.value)?;
11170            } else if is_names {
11171                // Output: SET NAMES <charset>
11172                self.write_keyword("NAMES");
11173                self.write_space();
11174                self.generate_set_value(&item.value)?;
11175            } else if is_collate {
11176                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
11177                self.write_keyword("COLLATE");
11178                self.write_space();
11179                self.generate_set_value(&item.value)?;
11180            } else if is_variable {
11181                // Output: SET [VARIABLE] <name> = <value>
11182                // If kind=VARIABLE, the keyword was already written above.
11183                // If name has VARIABLE prefix, write VARIABLE keyword for DuckDB target only.
11184                if name_has_variable_prefix && !has_variable_kind {
11185                    if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
11186                        self.write_keyword("VARIABLE");
11187                        self.write_space();
11188                    }
11189                }
11190                // Extract actual variable name (strip VARIABLE prefix if present)
11191                if let Some(ns) = name_str {
11192                    let var_name = if name_has_variable_prefix {
11193                        &ns["VARIABLE ".len()..]
11194                    } else {
11195                        ns
11196                    };
11197                    self.write(var_name);
11198                } else {
11199                    self.generate_expression(&item.name)?;
11200                }
11201                self.write(" = ");
11202                self.generate_set_value(&item.value)?;
11203            } else if is_value_only {
11204                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
11205                self.generate_expression(&item.name)?;
11206            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
11207                // SET key value without = (TSQL style)
11208                self.generate_expression(&item.name)?;
11209                self.write_space();
11210                self.generate_set_value(&item.value)?;
11211            } else {
11212                // Standard: variable = value
11213                // SET item names should not be quoted (they are config parameter names, not column refs)
11214                match &item.name {
11215                    Expression::Identifier(id) => {
11216                        self.write(&id.name);
11217                    }
11218                    _ => {
11219                        self.generate_expression(&item.name)?;
11220                    }
11221                }
11222                self.write(" = ");
11223                self.generate_set_value(&item.value)?;
11224            }
11225        }
11226
11227        Ok(())
11228    }
11229
11230    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
11231    /// directly to avoid reserved keyword quoting.
11232    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
11233        if let Expression::Identifier(id) = value {
11234            match id.name.as_str() {
11235                "DEFAULT" | "ON" | "OFF" => {
11236                    self.write_keyword(&id.name);
11237                    return Ok(());
11238                }
11239                _ => {}
11240            }
11241        }
11242        self.generate_expression(value)
11243    }
11244
11245    // ==================== Phase 4: Additional DDL Generation ====================
11246
11247    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
11248        self.write_keyword("ALTER");
11249        // MySQL modifiers before VIEW
11250        if let Some(ref algorithm) = av.algorithm {
11251            self.write_space();
11252            self.write_keyword("ALGORITHM");
11253            self.write(" = ");
11254            self.write_keyword(algorithm);
11255        }
11256        if let Some(ref definer) = av.definer {
11257            self.write_space();
11258            self.write_keyword("DEFINER");
11259            self.write(" = ");
11260            self.write(definer);
11261        }
11262        if let Some(ref sql_security) = av.sql_security {
11263            self.write_space();
11264            self.write_keyword("SQL SECURITY");
11265            self.write(" = ");
11266            self.write_keyword(sql_security);
11267        }
11268        self.write_space();
11269        self.write_keyword("VIEW");
11270        self.write_space();
11271        self.generate_table(&av.name)?;
11272
11273        // Hive: Column aliases with optional COMMENT
11274        if !av.columns.is_empty() {
11275            self.write(" (");
11276            for (i, col) in av.columns.iter().enumerate() {
11277                if i > 0 {
11278                    self.write(", ");
11279                }
11280                self.generate_identifier(&col.name)?;
11281                if let Some(ref comment) = col.comment {
11282                    self.write_space();
11283                    self.write_keyword("COMMENT");
11284                    self.write(" ");
11285                    self.generate_string_literal(comment)?;
11286                }
11287            }
11288            self.write(")");
11289        }
11290
11291        // TSQL: WITH option before actions
11292        if let Some(ref opt) = av.with_option {
11293            self.write_space();
11294            self.write_keyword("WITH");
11295            self.write_space();
11296            self.write_keyword(opt);
11297        }
11298
11299        for action in &av.actions {
11300            self.write_space();
11301            match action {
11302                AlterViewAction::Rename(new_name) => {
11303                    self.write_keyword("RENAME TO");
11304                    self.write_space();
11305                    self.generate_table(new_name)?;
11306                }
11307                AlterViewAction::OwnerTo(owner) => {
11308                    self.write_keyword("OWNER TO");
11309                    self.write_space();
11310                    self.generate_identifier(owner)?;
11311                }
11312                AlterViewAction::SetSchema(schema) => {
11313                    self.write_keyword("SET SCHEMA");
11314                    self.write_space();
11315                    self.generate_identifier(schema)?;
11316                }
11317                AlterViewAction::SetAuthorization(auth) => {
11318                    self.write_keyword("SET AUTHORIZATION");
11319                    self.write_space();
11320                    self.write(auth);
11321                }
11322                AlterViewAction::AlterColumn { name, action } => {
11323                    self.write_keyword("ALTER COLUMN");
11324                    self.write_space();
11325                    self.generate_identifier(name)?;
11326                    self.write_space();
11327                    self.generate_alter_column_action(action)?;
11328                }
11329                AlterViewAction::AsSelect(query) => {
11330                    self.write_keyword("AS");
11331                    self.write_space();
11332                    self.generate_expression(query)?;
11333                }
11334                AlterViewAction::SetTblproperties(props) => {
11335                    self.write_keyword("SET TBLPROPERTIES");
11336                    self.write(" (");
11337                    for (i, (key, value)) in props.iter().enumerate() {
11338                        if i > 0 {
11339                            self.write(", ");
11340                        }
11341                        self.generate_string_literal(key)?;
11342                        self.write("=");
11343                        self.generate_string_literal(value)?;
11344                    }
11345                    self.write(")");
11346                }
11347                AlterViewAction::UnsetTblproperties(keys) => {
11348                    self.write_keyword("UNSET TBLPROPERTIES");
11349                    self.write(" (");
11350                    for (i, key) in keys.iter().enumerate() {
11351                        if i > 0 {
11352                            self.write(", ");
11353                        }
11354                        self.generate_string_literal(key)?;
11355                    }
11356                    self.write(")");
11357                }
11358            }
11359        }
11360
11361        Ok(())
11362    }
11363
11364    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
11365        self.write_keyword("ALTER INDEX");
11366        self.write_space();
11367        self.generate_identifier(&ai.name)?;
11368
11369        if let Some(table) = &ai.table {
11370            self.write_space();
11371            self.write_keyword("ON");
11372            self.write_space();
11373            self.generate_table(table)?;
11374        }
11375
11376        for action in &ai.actions {
11377            self.write_space();
11378            match action {
11379                AlterIndexAction::Rename(new_name) => {
11380                    self.write_keyword("RENAME TO");
11381                    self.write_space();
11382                    self.generate_identifier(new_name)?;
11383                }
11384                AlterIndexAction::SetTablespace(tablespace) => {
11385                    self.write_keyword("SET TABLESPACE");
11386                    self.write_space();
11387                    self.generate_identifier(tablespace)?;
11388                }
11389                AlterIndexAction::Visible(visible) => {
11390                    if *visible {
11391                        self.write_keyword("VISIBLE");
11392                    } else {
11393                        self.write_keyword("INVISIBLE");
11394                    }
11395                }
11396            }
11397        }
11398
11399        Ok(())
11400    }
11401
11402    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
11403        // Output leading comments
11404        for comment in &cs.leading_comments {
11405            self.write_formatted_comment(comment);
11406            self.write_space();
11407        }
11408
11409        // Athena: CREATE SCHEMA uses Hive engine (backticks)
11410        let saved_athena_hive_context = self.athena_hive_context;
11411        if matches!(
11412            self.config.dialect,
11413            Some(crate::dialects::DialectType::Athena)
11414        ) {
11415            self.athena_hive_context = true;
11416        }
11417
11418        self.write_keyword("CREATE SCHEMA");
11419
11420        if cs.if_not_exists {
11421            self.write_space();
11422            self.write_keyword("IF NOT EXISTS");
11423        }
11424
11425        self.write_space();
11426        self.generate_identifier(&cs.name)?;
11427
11428        if let Some(ref clone_src) = cs.clone_from {
11429            self.write_keyword(" CLONE ");
11430            self.generate_identifier(clone_src)?;
11431        }
11432
11433        if let Some(ref at_clause) = cs.at_clause {
11434            self.write_space();
11435            self.generate_expression(at_clause)?;
11436        }
11437
11438        if let Some(auth) = &cs.authorization {
11439            self.write_space();
11440            self.write_keyword("AUTHORIZATION");
11441            self.write_space();
11442            self.generate_identifier(auth)?;
11443        }
11444
11445        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
11446        // Separate WITH properties from other properties
11447        let with_properties: Vec<_> = cs
11448            .properties
11449            .iter()
11450            .filter(|p| matches!(p, Expression::Property(_)))
11451            .collect();
11452        let other_properties: Vec<_> = cs
11453            .properties
11454            .iter()
11455            .filter(|p| !matches!(p, Expression::Property(_)))
11456            .collect();
11457
11458        // Generate WITH (props) if we have Property expressions
11459        if !with_properties.is_empty() {
11460            self.write_space();
11461            self.write_keyword("WITH");
11462            self.write(" (");
11463            for (i, prop) in with_properties.iter().enumerate() {
11464                if i > 0 {
11465                    self.write(", ");
11466                }
11467                self.generate_expression(prop)?;
11468            }
11469            self.write(")");
11470        }
11471
11472        // Generate other properties (like DEFAULT COLLATE)
11473        for prop in other_properties {
11474            self.write_space();
11475            self.generate_expression(prop)?;
11476        }
11477
11478        // Restore Athena Hive context
11479        self.athena_hive_context = saved_athena_hive_context;
11480
11481        Ok(())
11482    }
11483
11484    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
11485        self.write_keyword("DROP SCHEMA");
11486
11487        if ds.if_exists {
11488            self.write_space();
11489            self.write_keyword("IF EXISTS");
11490        }
11491
11492        self.write_space();
11493        self.generate_identifier(&ds.name)?;
11494
11495        if ds.cascade {
11496            self.write_space();
11497            self.write_keyword("CASCADE");
11498        }
11499
11500        Ok(())
11501    }
11502
11503    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
11504        self.write_keyword("DROP NAMESPACE");
11505
11506        if dn.if_exists {
11507            self.write_space();
11508            self.write_keyword("IF EXISTS");
11509        }
11510
11511        self.write_space();
11512        self.generate_identifier(&dn.name)?;
11513
11514        if dn.cascade {
11515            self.write_space();
11516            self.write_keyword("CASCADE");
11517        }
11518
11519        Ok(())
11520    }
11521
11522    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
11523        self.write_keyword("CREATE DATABASE");
11524
11525        if cd.if_not_exists {
11526            self.write_space();
11527            self.write_keyword("IF NOT EXISTS");
11528        }
11529
11530        self.write_space();
11531        self.generate_identifier(&cd.name)?;
11532
11533        if let Some(ref clone_src) = cd.clone_from {
11534            self.write_keyword(" CLONE ");
11535            self.generate_identifier(clone_src)?;
11536        }
11537
11538        // AT/BEFORE clause for time travel (Snowflake)
11539        if let Some(ref at_clause) = cd.at_clause {
11540            self.write_space();
11541            self.generate_expression(at_clause)?;
11542        }
11543
11544        for option in &cd.options {
11545            self.write_space();
11546            match option {
11547                DatabaseOption::CharacterSet(charset) => {
11548                    self.write_keyword("CHARACTER SET");
11549                    self.write(" = ");
11550                    self.write(&format!("'{}'", charset));
11551                }
11552                DatabaseOption::Collate(collate) => {
11553                    self.write_keyword("COLLATE");
11554                    self.write(" = ");
11555                    self.write(&format!("'{}'", collate));
11556                }
11557                DatabaseOption::Owner(owner) => {
11558                    self.write_keyword("OWNER");
11559                    self.write(" = ");
11560                    self.generate_identifier(owner)?;
11561                }
11562                DatabaseOption::Template(template) => {
11563                    self.write_keyword("TEMPLATE");
11564                    self.write(" = ");
11565                    self.generate_identifier(template)?;
11566                }
11567                DatabaseOption::Encoding(encoding) => {
11568                    self.write_keyword("ENCODING");
11569                    self.write(" = ");
11570                    self.write(&format!("'{}'", encoding));
11571                }
11572                DatabaseOption::Location(location) => {
11573                    self.write_keyword("LOCATION");
11574                    self.write(" = ");
11575                    self.write(&format!("'{}'", location));
11576                }
11577            }
11578        }
11579
11580        Ok(())
11581    }
11582
11583    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
11584        self.write_keyword("DROP DATABASE");
11585
11586        if dd.if_exists {
11587            self.write_space();
11588            self.write_keyword("IF EXISTS");
11589        }
11590
11591        self.write_space();
11592        self.generate_identifier(&dd.name)?;
11593
11594        Ok(())
11595    }
11596
11597    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
11598        self.write_keyword("CREATE");
11599
11600        if cf.or_replace {
11601            self.write_space();
11602            self.write_keyword("OR REPLACE");
11603        }
11604
11605        if cf.temporary {
11606            self.write_space();
11607            self.write_keyword("TEMPORARY");
11608        }
11609
11610        self.write_space();
11611        if cf.is_table_function {
11612            self.write_keyword("TABLE FUNCTION");
11613        } else {
11614            self.write_keyword("FUNCTION");
11615        }
11616
11617        if cf.if_not_exists {
11618            self.write_space();
11619            self.write_keyword("IF NOT EXISTS");
11620        }
11621
11622        self.write_space();
11623        self.generate_table(&cf.name)?;
11624        if cf.has_parens {
11625            let func_multiline = self.config.pretty
11626                && matches!(
11627                    self.config.dialect,
11628                    Some(crate::dialects::DialectType::TSQL)
11629                        | Some(crate::dialects::DialectType::Fabric)
11630                )
11631                && !cf.parameters.is_empty();
11632            if func_multiline {
11633                self.write("(\n");
11634                self.indent_level += 2;
11635                self.write_indent();
11636                self.generate_function_parameters(&cf.parameters)?;
11637                self.write("\n");
11638                self.indent_level -= 2;
11639                self.write(")");
11640            } else {
11641                self.write("(");
11642                self.generate_function_parameters(&cf.parameters)?;
11643                self.write(")");
11644            }
11645        }
11646
11647        // Output RETURNS clause (always comes first after parameters)
11648        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
11649        let use_multiline = self.config.pretty
11650            && matches!(
11651                self.config.dialect,
11652                Some(crate::dialects::DialectType::BigQuery)
11653                    | Some(crate::dialects::DialectType::TSQL)
11654                    | Some(crate::dialects::DialectType::Fabric)
11655            );
11656
11657        if cf.language_first {
11658            // LANGUAGE first, then SQL data access, then RETURNS
11659            if let Some(lang) = &cf.language {
11660                if use_multiline {
11661                    self.write_newline();
11662                } else {
11663                    self.write_space();
11664                }
11665                self.write_keyword("LANGUAGE");
11666                self.write_space();
11667                self.write(lang);
11668            }
11669
11670            // SQL data access comes after LANGUAGE in this case
11671            if let Some(sql_data) = &cf.sql_data_access {
11672                self.write_space();
11673                match sql_data {
11674                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
11675                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
11676                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
11677                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
11678                }
11679            }
11680
11681            if let Some(ref rtb) = cf.returns_table_body {
11682                if use_multiline {
11683                    self.write_newline();
11684                } else {
11685                    self.write_space();
11686                }
11687                self.write_keyword("RETURNS");
11688                self.write_space();
11689                self.write(rtb);
11690            } else if let Some(return_type) = &cf.return_type {
11691                if use_multiline {
11692                    self.write_newline();
11693                } else {
11694                    self.write_space();
11695                }
11696                self.write_keyword("RETURNS");
11697                self.write_space();
11698                self.generate_data_type(return_type)?;
11699            }
11700        } else {
11701            // RETURNS first (default)
11702            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
11703            let is_duckdb = matches!(
11704                self.config.dialect,
11705                Some(crate::dialects::DialectType::DuckDB)
11706            );
11707            if let Some(ref rtb) = cf.returns_table_body {
11708                if !(is_duckdb && rtb.is_empty()) {
11709                    if use_multiline {
11710                        self.write_newline();
11711                    } else {
11712                        self.write_space();
11713                    }
11714                    self.write_keyword("RETURNS");
11715                    self.write_space();
11716                    self.write(rtb);
11717                }
11718            } else if let Some(return_type) = &cf.return_type {
11719                if use_multiline {
11720                    self.write_newline();
11721                } else {
11722                    self.write_space();
11723                }
11724                self.write_keyword("RETURNS");
11725                self.write_space();
11726                self.generate_data_type(return_type)?;
11727            }
11728        }
11729
11730        // If we have property_order, use it to output properties in original order
11731        if !cf.property_order.is_empty() {
11732            // For BigQuery, OPTIONS must come before AS - reorder if needed
11733            let is_bigquery = matches!(
11734                self.config.dialect,
11735                Some(crate::dialects::DialectType::BigQuery)
11736            );
11737            let property_order = if is_bigquery {
11738                // Move Options before As if both are present
11739                let mut reordered = Vec::new();
11740                let mut has_as = false;
11741                let mut has_options = false;
11742                for prop in &cf.property_order {
11743                    match prop {
11744                        FunctionPropertyKind::As => has_as = true,
11745                        FunctionPropertyKind::Options => has_options = true,
11746                        _ => {}
11747                    }
11748                }
11749                if has_as && has_options {
11750                    // Output all props except As and Options, then Options, then As
11751                    for prop in &cf.property_order {
11752                        if *prop != FunctionPropertyKind::As
11753                            && *prop != FunctionPropertyKind::Options
11754                        {
11755                            reordered.push(*prop);
11756                        }
11757                    }
11758                    reordered.push(FunctionPropertyKind::Options);
11759                    reordered.push(FunctionPropertyKind::As);
11760                    reordered
11761                } else {
11762                    cf.property_order.clone()
11763                }
11764            } else {
11765                cf.property_order.clone()
11766            };
11767
11768            for prop in &property_order {
11769                match prop {
11770                    FunctionPropertyKind::Set => {
11771                        self.generate_function_set_options(cf)?;
11772                    }
11773                    FunctionPropertyKind::As => {
11774                        self.generate_function_body(cf)?;
11775                    }
11776                    FunctionPropertyKind::Language => {
11777                        if !cf.language_first {
11778                            // Only output here if not already output above
11779                            if let Some(lang) = &cf.language {
11780                                // Only BigQuery uses multiline formatting
11781                                let use_multiline = self.config.pretty
11782                                    && matches!(
11783                                        self.config.dialect,
11784                                        Some(crate::dialects::DialectType::BigQuery)
11785                                    );
11786                                if use_multiline {
11787                                    self.write_newline();
11788                                } else {
11789                                    self.write_space();
11790                                }
11791                                self.write_keyword("LANGUAGE");
11792                                self.write_space();
11793                                self.write(lang);
11794                            }
11795                        }
11796                    }
11797                    FunctionPropertyKind::Determinism => {
11798                        self.generate_function_determinism(cf)?;
11799                    }
11800                    FunctionPropertyKind::NullInput => {
11801                        self.generate_function_null_input(cf)?;
11802                    }
11803                    FunctionPropertyKind::Security => {
11804                        self.generate_function_security(cf)?;
11805                    }
11806                    FunctionPropertyKind::SqlDataAccess => {
11807                        if !cf.language_first {
11808                            // Only output here if not already output above
11809                            self.generate_function_sql_data_access(cf)?;
11810                        }
11811                    }
11812                    FunctionPropertyKind::Options => {
11813                        if !cf.options.is_empty() {
11814                            self.write_space();
11815                            self.generate_options_clause(&cf.options)?;
11816                        }
11817                    }
11818                    FunctionPropertyKind::Environment => {
11819                        if !cf.environment.is_empty() {
11820                            self.write_space();
11821                            self.generate_environment_clause(&cf.environment)?;
11822                        }
11823                    }
11824                }
11825            }
11826
11827            // Output OPTIONS if not tracked in property_order (legacy)
11828            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
11829            {
11830                self.write_space();
11831                self.generate_options_clause(&cf.options)?;
11832            }
11833
11834            // Output ENVIRONMENT if not tracked in property_order (legacy)
11835            if !cf.environment.is_empty()
11836                && !cf
11837                    .property_order
11838                    .contains(&FunctionPropertyKind::Environment)
11839            {
11840                self.write_space();
11841                self.generate_environment_clause(&cf.environment)?;
11842            }
11843        } else {
11844            // Legacy behavior when property_order is empty
11845            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
11846            if matches!(
11847                self.config.dialect,
11848                Some(crate::dialects::DialectType::BigQuery)
11849            ) {
11850                self.generate_function_determinism(cf)?;
11851            }
11852
11853            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
11854            let use_multiline = self.config.pretty
11855                && matches!(
11856                    self.config.dialect,
11857                    Some(crate::dialects::DialectType::BigQuery)
11858                );
11859
11860            if !cf.language_first {
11861                if let Some(lang) = &cf.language {
11862                    if use_multiline {
11863                        self.write_newline();
11864                    } else {
11865                        self.write_space();
11866                    }
11867                    self.write_keyword("LANGUAGE");
11868                    self.write_space();
11869                    self.write(lang);
11870                }
11871
11872                // SQL data access characteristic comes after LANGUAGE
11873                self.generate_function_sql_data_access(cf)?;
11874            }
11875
11876            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
11877            if !matches!(
11878                self.config.dialect,
11879                Some(crate::dialects::DialectType::BigQuery)
11880            ) {
11881                self.generate_function_determinism(cf)?;
11882            }
11883
11884            self.generate_function_null_input(cf)?;
11885            self.generate_function_security(cf)?;
11886            self.generate_function_set_options(cf)?;
11887
11888            // BigQuery: OPTIONS (key=value, ...) - comes before AS
11889            if !cf.options.is_empty() {
11890                self.write_space();
11891                self.generate_options_clause(&cf.options)?;
11892            }
11893
11894            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
11895            if !cf.environment.is_empty() {
11896                self.write_space();
11897                self.generate_environment_clause(&cf.environment)?;
11898            }
11899
11900            self.generate_function_body(cf)?;
11901        }
11902
11903        Ok(())
11904    }
11905
11906    /// Generate SET options for CREATE FUNCTION
11907    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
11908        for opt in &cf.set_options {
11909            self.write_space();
11910            self.write_keyword("SET");
11911            self.write_space();
11912            self.write(&opt.name);
11913            match &opt.value {
11914                FunctionSetValue::Value { value, use_to } => {
11915                    if *use_to {
11916                        self.write(" TO ");
11917                    } else {
11918                        self.write(" = ");
11919                    }
11920                    self.write(value);
11921                }
11922                FunctionSetValue::FromCurrent => {
11923                    self.write_space();
11924                    self.write_keyword("FROM CURRENT");
11925                }
11926            }
11927        }
11928        Ok(())
11929    }
11930
11931    /// Generate function body (AS clause)
11932    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
11933        if let Some(body) = &cf.body {
11934            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
11935            self.write_space();
11936            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
11937            let use_multiline = self.config.pretty
11938                && matches!(
11939                    self.config.dialect,
11940                    Some(crate::dialects::DialectType::BigQuery)
11941                );
11942            match body {
11943                FunctionBody::Block(block) => {
11944                    self.write_keyword("AS");
11945                    if matches!(
11946                        self.config.dialect,
11947                        Some(crate::dialects::DialectType::TSQL)
11948                    ) {
11949                        self.write(" BEGIN ");
11950                        self.write(block);
11951                        self.write(" END");
11952                    } else if matches!(
11953                        self.config.dialect,
11954                        Some(crate::dialects::DialectType::PostgreSQL)
11955                    ) {
11956                        self.write(" $$");
11957                        self.write(block);
11958                        self.write("$$");
11959                    } else {
11960                        // Escape content for single-quoted output
11961                        let escaped = self.escape_block_for_single_quote(block);
11962                        // In BigQuery pretty mode, body content goes on new line
11963                        if use_multiline {
11964                            self.write_newline();
11965                        } else {
11966                            self.write(" ");
11967                        }
11968                        self.write("'");
11969                        self.write(&escaped);
11970                        self.write("'");
11971                    }
11972                }
11973                FunctionBody::StringLiteral(s) => {
11974                    self.write_keyword("AS");
11975                    // In BigQuery pretty mode, body content goes on new line
11976                    if use_multiline {
11977                        self.write_newline();
11978                    } else {
11979                        self.write(" ");
11980                    }
11981                    self.write("'");
11982                    self.write(s);
11983                    self.write("'");
11984                }
11985                FunctionBody::Expression(expr) => {
11986                    self.write_keyword("AS");
11987                    self.write_space();
11988                    self.generate_expression(expr)?;
11989                }
11990                FunctionBody::External(name) => {
11991                    self.write_keyword("EXTERNAL NAME");
11992                    self.write(" '");
11993                    self.write(name);
11994                    self.write("'");
11995                }
11996                FunctionBody::Return(expr) => {
11997                    if matches!(
11998                        self.config.dialect,
11999                        Some(crate::dialects::DialectType::DuckDB)
12000                    ) {
12001                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
12002                        self.write_keyword("AS");
12003                        self.write_space();
12004                        // Empty returns_table_body signals TABLE return
12005                        if cf.returns_table_body.is_some() {
12006                            self.write_keyword("TABLE");
12007                            self.write_space();
12008                        }
12009                        self.generate_expression(expr)?;
12010                    } else {
12011                        if self.config.create_function_return_as {
12012                            self.write_keyword("AS");
12013                            // TSQL pretty: newline between AS and RETURN
12014                            if self.config.pretty
12015                                && matches!(
12016                                    self.config.dialect,
12017                                    Some(crate::dialects::DialectType::TSQL)
12018                                        | Some(crate::dialects::DialectType::Fabric)
12019                                )
12020                            {
12021                                self.write_newline();
12022                            } else {
12023                                self.write_space();
12024                            }
12025                        }
12026                        self.write_keyword("RETURN");
12027                        self.write_space();
12028                        self.generate_expression(expr)?;
12029                    }
12030                }
12031                FunctionBody::Statements(stmts) => {
12032                    self.write_keyword("AS");
12033                    self.write(" BEGIN ");
12034                    for (i, stmt) in stmts.iter().enumerate() {
12035                        if i > 0 {
12036                            self.write(" ");
12037                        }
12038                        self.generate_expression(stmt)?;
12039                    }
12040                    self.write(" END");
12041                }
12042                FunctionBody::DollarQuoted { content, tag } => {
12043                    self.write_keyword("AS");
12044                    self.write(" ");
12045                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
12046                    let supports_dollar_quoting = matches!(
12047                        self.config.dialect,
12048                        Some(crate::dialects::DialectType::PostgreSQL)
12049                            | Some(crate::dialects::DialectType::Databricks)
12050                            | Some(crate::dialects::DialectType::Redshift)
12051                            | Some(crate::dialects::DialectType::DuckDB)
12052                    );
12053                    if supports_dollar_quoting {
12054                        // Output in dollar-quoted format
12055                        self.write("$");
12056                        if let Some(t) = tag {
12057                            self.write(t);
12058                        }
12059                        self.write("$");
12060                        self.write(content);
12061                        self.write("$");
12062                        if let Some(t) = tag {
12063                            self.write(t);
12064                        }
12065                        self.write("$");
12066                    } else {
12067                        // Convert to single-quoted string for other dialects
12068                        let escaped = self.escape_block_for_single_quote(content);
12069                        self.write("'");
12070                        self.write(&escaped);
12071                        self.write("'");
12072                    }
12073                }
12074            }
12075        }
12076        Ok(())
12077    }
12078
12079    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
12080    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
12081        if let Some(det) = cf.deterministic {
12082            self.write_space();
12083            if matches!(
12084                self.config.dialect,
12085                Some(crate::dialects::DialectType::BigQuery)
12086            ) {
12087                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
12088                if det {
12089                    self.write_keyword("DETERMINISTIC");
12090                } else {
12091                    self.write_keyword("NOT DETERMINISTIC");
12092                }
12093            } else {
12094                // PostgreSQL and others use IMMUTABLE/VOLATILE
12095                if det {
12096                    self.write_keyword("IMMUTABLE");
12097                } else {
12098                    self.write_keyword("VOLATILE");
12099                }
12100            }
12101        }
12102        Ok(())
12103    }
12104
12105    /// Generate null input handling clause
12106    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
12107        if let Some(returns_null) = cf.returns_null_on_null_input {
12108            self.write_space();
12109            if returns_null {
12110                if cf.strict {
12111                    self.write_keyword("STRICT");
12112                } else {
12113                    self.write_keyword("RETURNS NULL ON NULL INPUT");
12114                }
12115            } else {
12116                self.write_keyword("CALLED ON NULL INPUT");
12117            }
12118        }
12119        Ok(())
12120    }
12121
12122    /// Generate security clause
12123    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
12124        if let Some(security) = &cf.security {
12125            self.write_space();
12126            self.write_keyword("SECURITY");
12127            self.write_space();
12128            match security {
12129                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12130                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12131                FunctionSecurity::None => self.write_keyword("NONE"),
12132            }
12133        }
12134        Ok(())
12135    }
12136
12137    /// Generate SQL data access clause
12138    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
12139        if let Some(sql_data) = &cf.sql_data_access {
12140            self.write_space();
12141            match sql_data {
12142                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12143                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12144                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12145                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12146            }
12147        }
12148        Ok(())
12149    }
12150
12151    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
12152        for (i, param) in params.iter().enumerate() {
12153            if i > 0 {
12154                self.write(", ");
12155            }
12156
12157            if let Some(mode) = &param.mode {
12158                if let Some(text) = &param.mode_text {
12159                    self.write(text);
12160                } else {
12161                    match mode {
12162                        ParameterMode::In => self.write_keyword("IN"),
12163                        ParameterMode::Out => self.write_keyword("OUT"),
12164                        ParameterMode::InOut => self.write_keyword("INOUT"),
12165                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
12166                    }
12167                }
12168                self.write_space();
12169            }
12170
12171            if let Some(name) = &param.name {
12172                self.generate_identifier(name)?;
12173                // Skip space and type for empty Custom types (e.g., DuckDB macros)
12174                let skip_type =
12175                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
12176                if !skip_type {
12177                    self.write_space();
12178                    self.generate_data_type(&param.data_type)?;
12179                }
12180            } else {
12181                self.generate_data_type(&param.data_type)?;
12182            }
12183
12184            if let Some(default) = &param.default {
12185                if self.config.parameter_default_equals {
12186                    self.write(" = ");
12187                } else {
12188                    self.write(" DEFAULT ");
12189                }
12190                self.generate_expression(default)?;
12191            }
12192        }
12193
12194        Ok(())
12195    }
12196
12197    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
12198        self.write_keyword("DROP FUNCTION");
12199
12200        if df.if_exists {
12201            self.write_space();
12202            self.write_keyword("IF EXISTS");
12203        }
12204
12205        self.write_space();
12206        self.generate_table(&df.name)?;
12207
12208        if let Some(params) = &df.parameters {
12209            self.write(" (");
12210            for (i, dt) in params.iter().enumerate() {
12211                if i > 0 {
12212                    self.write(", ");
12213                }
12214                self.generate_data_type(dt)?;
12215            }
12216            self.write(")");
12217        }
12218
12219        if df.cascade {
12220            self.write_space();
12221            self.write_keyword("CASCADE");
12222        }
12223
12224        Ok(())
12225    }
12226
12227    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
12228        self.write_keyword("CREATE");
12229
12230        if cp.or_replace {
12231            self.write_space();
12232            self.write_keyword("OR REPLACE");
12233        }
12234
12235        self.write_space();
12236        if cp.use_proc_keyword {
12237            self.write_keyword("PROC");
12238        } else {
12239            self.write_keyword("PROCEDURE");
12240        }
12241
12242        if cp.if_not_exists {
12243            self.write_space();
12244            self.write_keyword("IF NOT EXISTS");
12245        }
12246
12247        self.write_space();
12248        self.generate_table(&cp.name)?;
12249        if cp.has_parens {
12250            self.write("(");
12251            self.generate_function_parameters(&cp.parameters)?;
12252            self.write(")");
12253        } else if !cp.parameters.is_empty() {
12254            // TSQL: unparenthesized parameters
12255            self.write_space();
12256            self.generate_function_parameters(&cp.parameters)?;
12257        }
12258
12259        // RETURNS clause (Snowflake)
12260        if let Some(return_type) = &cp.return_type {
12261            self.write_space();
12262            self.write_keyword("RETURNS");
12263            self.write_space();
12264            self.generate_data_type(return_type)?;
12265        }
12266
12267        // EXECUTE AS clause (Snowflake)
12268        if let Some(execute_as) = &cp.execute_as {
12269            self.write_space();
12270            self.write_keyword("EXECUTE AS");
12271            self.write_space();
12272            self.write_keyword(execute_as);
12273        }
12274
12275        if let Some(lang) = &cp.language {
12276            self.write_space();
12277            self.write_keyword("LANGUAGE");
12278            self.write_space();
12279            self.write(lang);
12280        }
12281
12282        if let Some(security) = &cp.security {
12283            self.write_space();
12284            self.write_keyword("SECURITY");
12285            self.write_space();
12286            match security {
12287                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12288                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12289                FunctionSecurity::None => self.write_keyword("NONE"),
12290            }
12291        }
12292
12293        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
12294        if !cp.with_options.is_empty() {
12295            self.write_space();
12296            self.write_keyword("WITH");
12297            self.write_space();
12298            for (i, opt) in cp.with_options.iter().enumerate() {
12299                if i > 0 {
12300                    self.write(", ");
12301                }
12302                self.write(opt);
12303            }
12304        }
12305
12306        if let Some(body) = &cp.body {
12307            self.write_space();
12308            match body {
12309                FunctionBody::Block(block) => {
12310                    self.write_keyword("AS");
12311                    if matches!(
12312                        self.config.dialect,
12313                        Some(crate::dialects::DialectType::TSQL)
12314                    ) {
12315                        self.write(" BEGIN ");
12316                        self.write(block);
12317                        self.write(" END");
12318                    } else if matches!(
12319                        self.config.dialect,
12320                        Some(crate::dialects::DialectType::PostgreSQL)
12321                    ) {
12322                        self.write(" $$");
12323                        self.write(block);
12324                        self.write("$$");
12325                    } else {
12326                        // Escape content for single-quoted output
12327                        let escaped = self.escape_block_for_single_quote(block);
12328                        self.write(" '");
12329                        self.write(&escaped);
12330                        self.write("'");
12331                    }
12332                }
12333                FunctionBody::StringLiteral(s) => {
12334                    self.write_keyword("AS");
12335                    self.write(" '");
12336                    self.write(s);
12337                    self.write("'");
12338                }
12339                FunctionBody::Expression(expr) => {
12340                    self.write_keyword("AS");
12341                    self.write_space();
12342                    self.generate_expression(expr)?;
12343                }
12344                FunctionBody::External(name) => {
12345                    self.write_keyword("EXTERNAL NAME");
12346                    self.write(" '");
12347                    self.write(name);
12348                    self.write("'");
12349                }
12350                FunctionBody::Return(expr) => {
12351                    self.write_keyword("RETURN");
12352                    self.write_space();
12353                    self.generate_expression(expr)?;
12354                }
12355                FunctionBody::Statements(stmts) => {
12356                    self.write_keyword("AS");
12357                    self.write(" BEGIN ");
12358                    for (i, stmt) in stmts.iter().enumerate() {
12359                        if i > 0 {
12360                            self.write(" ");
12361                        }
12362                        self.generate_expression(stmt)?;
12363                    }
12364                    self.write(" END");
12365                }
12366                FunctionBody::DollarQuoted { content, tag } => {
12367                    self.write_keyword("AS");
12368                    self.write(" ");
12369                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
12370                    let supports_dollar_quoting = matches!(
12371                        self.config.dialect,
12372                        Some(crate::dialects::DialectType::PostgreSQL)
12373                            | Some(crate::dialects::DialectType::Databricks)
12374                            | Some(crate::dialects::DialectType::Redshift)
12375                            | Some(crate::dialects::DialectType::DuckDB)
12376                    );
12377                    if supports_dollar_quoting {
12378                        // Output in dollar-quoted format
12379                        self.write("$");
12380                        if let Some(t) = tag {
12381                            self.write(t);
12382                        }
12383                        self.write("$");
12384                        self.write(content);
12385                        self.write("$");
12386                        if let Some(t) = tag {
12387                            self.write(t);
12388                        }
12389                        self.write("$");
12390                    } else {
12391                        // Convert to single-quoted string for other dialects
12392                        let escaped = self.escape_block_for_single_quote(content);
12393                        self.write("'");
12394                        self.write(&escaped);
12395                        self.write("'");
12396                    }
12397                }
12398            }
12399        }
12400
12401        Ok(())
12402    }
12403
12404    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
12405        self.write_keyword("DROP PROCEDURE");
12406
12407        if dp.if_exists {
12408            self.write_space();
12409            self.write_keyword("IF EXISTS");
12410        }
12411
12412        self.write_space();
12413        self.generate_table(&dp.name)?;
12414
12415        if let Some(params) = &dp.parameters {
12416            self.write(" (");
12417            for (i, dt) in params.iter().enumerate() {
12418                if i > 0 {
12419                    self.write(", ");
12420                }
12421                self.generate_data_type(dt)?;
12422            }
12423            self.write(")");
12424        }
12425
12426        if dp.cascade {
12427            self.write_space();
12428            self.write_keyword("CASCADE");
12429        }
12430
12431        Ok(())
12432    }
12433
12434    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
12435        self.write_keyword("CREATE");
12436
12437        if cs.or_replace {
12438            self.write_space();
12439            self.write_keyword("OR REPLACE");
12440        }
12441
12442        if cs.temporary {
12443            self.write_space();
12444            self.write_keyword("TEMPORARY");
12445        }
12446
12447        self.write_space();
12448        self.write_keyword("SEQUENCE");
12449
12450        if cs.if_not_exists {
12451            self.write_space();
12452            self.write_keyword("IF NOT EXISTS");
12453        }
12454
12455        self.write_space();
12456        self.generate_table(&cs.name)?;
12457
12458        // Output AS <type> if present
12459        if let Some(as_type) = &cs.as_type {
12460            self.write_space();
12461            self.write_keyword("AS");
12462            self.write_space();
12463            self.generate_data_type(as_type)?;
12464        }
12465
12466        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
12467        if let Some(comment) = &cs.comment {
12468            self.write_space();
12469            self.write_keyword("COMMENT");
12470            self.write("=");
12471            self.generate_string_literal(comment)?;
12472        }
12473
12474        // If property_order is available, use it to preserve original order
12475        if !cs.property_order.is_empty() {
12476            for prop in &cs.property_order {
12477                match prop {
12478                    SeqPropKind::Start => {
12479                        if let Some(start) = cs.start {
12480                            self.write_space();
12481                            self.write_keyword("START WITH");
12482                            self.write(&format!(" {}", start));
12483                        }
12484                    }
12485                    SeqPropKind::Increment => {
12486                        if let Some(inc) = cs.increment {
12487                            self.write_space();
12488                            self.write_keyword("INCREMENT BY");
12489                            self.write(&format!(" {}", inc));
12490                        }
12491                    }
12492                    SeqPropKind::Minvalue => {
12493                        if let Some(min) = &cs.minvalue {
12494                            self.write_space();
12495                            match min {
12496                                SequenceBound::Value(v) => {
12497                                    self.write_keyword("MINVALUE");
12498                                    self.write(&format!(" {}", v));
12499                                }
12500                                SequenceBound::None => {
12501                                    self.write_keyword("NO MINVALUE");
12502                                }
12503                            }
12504                        }
12505                    }
12506                    SeqPropKind::Maxvalue => {
12507                        if let Some(max) = &cs.maxvalue {
12508                            self.write_space();
12509                            match max {
12510                                SequenceBound::Value(v) => {
12511                                    self.write_keyword("MAXVALUE");
12512                                    self.write(&format!(" {}", v));
12513                                }
12514                                SequenceBound::None => {
12515                                    self.write_keyword("NO MAXVALUE");
12516                                }
12517                            }
12518                        }
12519                    }
12520                    SeqPropKind::Cache => {
12521                        if let Some(cache) = cs.cache {
12522                            self.write_space();
12523                            self.write_keyword("CACHE");
12524                            self.write(&format!(" {}", cache));
12525                        }
12526                    }
12527                    SeqPropKind::NoCache => {
12528                        self.write_space();
12529                        self.write_keyword("NO CACHE");
12530                    }
12531                    SeqPropKind::NoCacheWord => {
12532                        self.write_space();
12533                        self.write_keyword("NOCACHE");
12534                    }
12535                    SeqPropKind::Cycle => {
12536                        self.write_space();
12537                        self.write_keyword("CYCLE");
12538                    }
12539                    SeqPropKind::NoCycle => {
12540                        self.write_space();
12541                        self.write_keyword("NO CYCLE");
12542                    }
12543                    SeqPropKind::NoCycleWord => {
12544                        self.write_space();
12545                        self.write_keyword("NOCYCLE");
12546                    }
12547                    SeqPropKind::OwnedBy => {
12548                        // Skip OWNED BY NONE (it's a no-op)
12549                        if !cs.owned_by_none {
12550                            if let Some(owned) = &cs.owned_by {
12551                                self.write_space();
12552                                self.write_keyword("OWNED BY");
12553                                self.write_space();
12554                                self.generate_table(owned)?;
12555                            }
12556                        }
12557                    }
12558                    SeqPropKind::Order => {
12559                        self.write_space();
12560                        self.write_keyword("ORDER");
12561                    }
12562                    SeqPropKind::NoOrder => {
12563                        self.write_space();
12564                        self.write_keyword("NOORDER");
12565                    }
12566                    SeqPropKind::Comment => {
12567                        // COMMENT is output above, before property_order iteration
12568                    }
12569                    SeqPropKind::Sharing => {
12570                        if let Some(val) = &cs.sharing {
12571                            self.write_space();
12572                            self.write(&format!("SHARING={}", val));
12573                        }
12574                    }
12575                    SeqPropKind::Keep => {
12576                        self.write_space();
12577                        self.write_keyword("KEEP");
12578                    }
12579                    SeqPropKind::NoKeep => {
12580                        self.write_space();
12581                        self.write_keyword("NOKEEP");
12582                    }
12583                    SeqPropKind::Scale => {
12584                        self.write_space();
12585                        self.write_keyword("SCALE");
12586                        if let Some(modifier) = &cs.scale_modifier {
12587                            if !modifier.is_empty() {
12588                                self.write_space();
12589                                self.write_keyword(modifier);
12590                            }
12591                        }
12592                    }
12593                    SeqPropKind::NoScale => {
12594                        self.write_space();
12595                        self.write_keyword("NOSCALE");
12596                    }
12597                    SeqPropKind::Shard => {
12598                        self.write_space();
12599                        self.write_keyword("SHARD");
12600                        if let Some(modifier) = &cs.shard_modifier {
12601                            if !modifier.is_empty() {
12602                                self.write_space();
12603                                self.write_keyword(modifier);
12604                            }
12605                        }
12606                    }
12607                    SeqPropKind::NoShard => {
12608                        self.write_space();
12609                        self.write_keyword("NOSHARD");
12610                    }
12611                    SeqPropKind::Session => {
12612                        self.write_space();
12613                        self.write_keyword("SESSION");
12614                    }
12615                    SeqPropKind::Global => {
12616                        self.write_space();
12617                        self.write_keyword("GLOBAL");
12618                    }
12619                    SeqPropKind::NoMinvalueWord => {
12620                        self.write_space();
12621                        self.write_keyword("NOMINVALUE");
12622                    }
12623                    SeqPropKind::NoMaxvalueWord => {
12624                        self.write_space();
12625                        self.write_keyword("NOMAXVALUE");
12626                    }
12627                }
12628            }
12629        } else {
12630            // Fallback: default order for backwards compatibility
12631            if let Some(inc) = cs.increment {
12632                self.write_space();
12633                self.write_keyword("INCREMENT BY");
12634                self.write(&format!(" {}", inc));
12635            }
12636
12637            if let Some(min) = &cs.minvalue {
12638                self.write_space();
12639                match min {
12640                    SequenceBound::Value(v) => {
12641                        self.write_keyword("MINVALUE");
12642                        self.write(&format!(" {}", v));
12643                    }
12644                    SequenceBound::None => {
12645                        self.write_keyword("NO MINVALUE");
12646                    }
12647                }
12648            }
12649
12650            if let Some(max) = &cs.maxvalue {
12651                self.write_space();
12652                match max {
12653                    SequenceBound::Value(v) => {
12654                        self.write_keyword("MAXVALUE");
12655                        self.write(&format!(" {}", v));
12656                    }
12657                    SequenceBound::None => {
12658                        self.write_keyword("NO MAXVALUE");
12659                    }
12660                }
12661            }
12662
12663            if let Some(start) = cs.start {
12664                self.write_space();
12665                self.write_keyword("START WITH");
12666                self.write(&format!(" {}", start));
12667            }
12668
12669            if let Some(cache) = cs.cache {
12670                self.write_space();
12671                self.write_keyword("CACHE");
12672                self.write(&format!(" {}", cache));
12673            }
12674
12675            if cs.cycle {
12676                self.write_space();
12677                self.write_keyword("CYCLE");
12678            }
12679
12680            if let Some(owned) = &cs.owned_by {
12681                self.write_space();
12682                self.write_keyword("OWNED BY");
12683                self.write_space();
12684                self.generate_table(owned)?;
12685            }
12686        }
12687
12688        Ok(())
12689    }
12690
12691    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
12692        self.write_keyword("DROP SEQUENCE");
12693
12694        if ds.if_exists {
12695            self.write_space();
12696            self.write_keyword("IF EXISTS");
12697        }
12698
12699        self.write_space();
12700        self.generate_table(&ds.name)?;
12701
12702        if ds.cascade {
12703            self.write_space();
12704            self.write_keyword("CASCADE");
12705        }
12706
12707        Ok(())
12708    }
12709
12710    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
12711        self.write_keyword("ALTER SEQUENCE");
12712
12713        if als.if_exists {
12714            self.write_space();
12715            self.write_keyword("IF EXISTS");
12716        }
12717
12718        self.write_space();
12719        self.generate_table(&als.name)?;
12720
12721        if let Some(inc) = als.increment {
12722            self.write_space();
12723            self.write_keyword("INCREMENT BY");
12724            self.write(&format!(" {}", inc));
12725        }
12726
12727        if let Some(min) = &als.minvalue {
12728            self.write_space();
12729            match min {
12730                SequenceBound::Value(v) => {
12731                    self.write_keyword("MINVALUE");
12732                    self.write(&format!(" {}", v));
12733                }
12734                SequenceBound::None => {
12735                    self.write_keyword("NO MINVALUE");
12736                }
12737            }
12738        }
12739
12740        if let Some(max) = &als.maxvalue {
12741            self.write_space();
12742            match max {
12743                SequenceBound::Value(v) => {
12744                    self.write_keyword("MAXVALUE");
12745                    self.write(&format!(" {}", v));
12746                }
12747                SequenceBound::None => {
12748                    self.write_keyword("NO MAXVALUE");
12749                }
12750            }
12751        }
12752
12753        if let Some(start) = als.start {
12754            self.write_space();
12755            self.write_keyword("START WITH");
12756            self.write(&format!(" {}", start));
12757        }
12758
12759        if let Some(restart) = &als.restart {
12760            self.write_space();
12761            self.write_keyword("RESTART");
12762            if let Some(val) = restart {
12763                self.write_keyword(" WITH");
12764                self.write(&format!(" {}", val));
12765            }
12766        }
12767
12768        if let Some(cache) = als.cache {
12769            self.write_space();
12770            self.write_keyword("CACHE");
12771            self.write(&format!(" {}", cache));
12772        }
12773
12774        if let Some(cycle) = als.cycle {
12775            self.write_space();
12776            if cycle {
12777                self.write_keyword("CYCLE");
12778            } else {
12779                self.write_keyword("NO CYCLE");
12780            }
12781        }
12782
12783        if let Some(owned) = &als.owned_by {
12784            self.write_space();
12785            self.write_keyword("OWNED BY");
12786            self.write_space();
12787            if let Some(table) = owned {
12788                self.generate_table(table)?;
12789            } else {
12790                self.write_keyword("NONE");
12791            }
12792        }
12793
12794        Ok(())
12795    }
12796
12797    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
12798        self.write_keyword("CREATE");
12799
12800        if ct.or_replace {
12801            self.write_space();
12802            self.write_keyword("OR REPLACE");
12803        }
12804
12805        if ct.constraint {
12806            self.write_space();
12807            self.write_keyword("CONSTRAINT");
12808        }
12809
12810        self.write_space();
12811        self.write_keyword("TRIGGER");
12812        self.write_space();
12813        self.generate_identifier(&ct.name)?;
12814
12815        self.write_space();
12816        match ct.timing {
12817            TriggerTiming::Before => self.write_keyword("BEFORE"),
12818            TriggerTiming::After => self.write_keyword("AFTER"),
12819            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
12820        }
12821
12822        // Events
12823        for (i, event) in ct.events.iter().enumerate() {
12824            if i > 0 {
12825                self.write_keyword(" OR");
12826            }
12827            self.write_space();
12828            match event {
12829                TriggerEvent::Insert => self.write_keyword("INSERT"),
12830                TriggerEvent::Update(cols) => {
12831                    self.write_keyword("UPDATE");
12832                    if let Some(cols) = cols {
12833                        self.write_space();
12834                        self.write_keyword("OF");
12835                        for (j, col) in cols.iter().enumerate() {
12836                            if j > 0 {
12837                                self.write(",");
12838                            }
12839                            self.write_space();
12840                            self.generate_identifier(col)?;
12841                        }
12842                    }
12843                }
12844                TriggerEvent::Delete => self.write_keyword("DELETE"),
12845                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
12846            }
12847        }
12848
12849        self.write_space();
12850        self.write_keyword("ON");
12851        self.write_space();
12852        self.generate_table(&ct.table)?;
12853
12854        // Referencing clause
12855        if let Some(ref_clause) = &ct.referencing {
12856            self.write_space();
12857            self.write_keyword("REFERENCING");
12858            if let Some(old_table) = &ref_clause.old_table {
12859                self.write_space();
12860                self.write_keyword("OLD TABLE AS");
12861                self.write_space();
12862                self.generate_identifier(old_table)?;
12863            }
12864            if let Some(new_table) = &ref_clause.new_table {
12865                self.write_space();
12866                self.write_keyword("NEW TABLE AS");
12867                self.write_space();
12868                self.generate_identifier(new_table)?;
12869            }
12870            if let Some(old_row) = &ref_clause.old_row {
12871                self.write_space();
12872                self.write_keyword("OLD ROW AS");
12873                self.write_space();
12874                self.generate_identifier(old_row)?;
12875            }
12876            if let Some(new_row) = &ref_clause.new_row {
12877                self.write_space();
12878                self.write_keyword("NEW ROW AS");
12879                self.write_space();
12880                self.generate_identifier(new_row)?;
12881            }
12882        }
12883
12884        // Deferrable options for constraint triggers (must come before FOR EACH)
12885        if let Some(deferrable) = ct.deferrable {
12886            self.write_space();
12887            if deferrable {
12888                self.write_keyword("DEFERRABLE");
12889            } else {
12890                self.write_keyword("NOT DEFERRABLE");
12891            }
12892        }
12893
12894        if let Some(initially) = ct.initially_deferred {
12895            self.write_space();
12896            self.write_keyword("INITIALLY");
12897            self.write_space();
12898            if initially {
12899                self.write_keyword("DEFERRED");
12900            } else {
12901                self.write_keyword("IMMEDIATE");
12902            }
12903        }
12904
12905        self.write_space();
12906        self.write_keyword("FOR EACH");
12907        self.write_space();
12908        match ct.for_each {
12909            TriggerForEach::Row => self.write_keyword("ROW"),
12910            TriggerForEach::Statement => self.write_keyword("STATEMENT"),
12911        }
12912
12913        // When clause
12914        if let Some(when) = &ct.when {
12915            self.write_space();
12916            self.write_keyword("WHEN");
12917            self.write(" (");
12918            self.generate_expression(when)?;
12919            self.write(")");
12920        }
12921
12922        // Body
12923        self.write_space();
12924        match &ct.body {
12925            TriggerBody::Execute { function, args } => {
12926                self.write_keyword("EXECUTE FUNCTION");
12927                self.write_space();
12928                self.generate_table(function)?;
12929                self.write("(");
12930                for (i, arg) in args.iter().enumerate() {
12931                    if i > 0 {
12932                        self.write(", ");
12933                    }
12934                    self.generate_expression(arg)?;
12935                }
12936                self.write(")");
12937            }
12938            TriggerBody::Block(block) => {
12939                self.write_keyword("BEGIN");
12940                self.write_space();
12941                self.write(block);
12942                self.write_space();
12943                self.write_keyword("END");
12944            }
12945        }
12946
12947        Ok(())
12948    }
12949
12950    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
12951        self.write_keyword("DROP TRIGGER");
12952
12953        if dt.if_exists {
12954            self.write_space();
12955            self.write_keyword("IF EXISTS");
12956        }
12957
12958        self.write_space();
12959        self.generate_identifier(&dt.name)?;
12960
12961        if let Some(table) = &dt.table {
12962            self.write_space();
12963            self.write_keyword("ON");
12964            self.write_space();
12965            self.generate_table(table)?;
12966        }
12967
12968        if dt.cascade {
12969            self.write_space();
12970            self.write_keyword("CASCADE");
12971        }
12972
12973        Ok(())
12974    }
12975
12976    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
12977        self.write_keyword("CREATE TYPE");
12978
12979        if ct.if_not_exists {
12980            self.write_space();
12981            self.write_keyword("IF NOT EXISTS");
12982        }
12983
12984        self.write_space();
12985        self.generate_table(&ct.name)?;
12986
12987        self.write_space();
12988        self.write_keyword("AS");
12989        self.write_space();
12990
12991        match &ct.definition {
12992            TypeDefinition::Enum(values) => {
12993                self.write_keyword("ENUM");
12994                self.write(" (");
12995                for (i, val) in values.iter().enumerate() {
12996                    if i > 0 {
12997                        self.write(", ");
12998                    }
12999                    self.write(&format!("'{}'", val));
13000                }
13001                self.write(")");
13002            }
13003            TypeDefinition::Composite(attrs) => {
13004                self.write("(");
13005                for (i, attr) in attrs.iter().enumerate() {
13006                    if i > 0 {
13007                        self.write(", ");
13008                    }
13009                    self.generate_identifier(&attr.name)?;
13010                    self.write_space();
13011                    self.generate_data_type(&attr.data_type)?;
13012                    if let Some(collate) = &attr.collate {
13013                        self.write_space();
13014                        self.write_keyword("COLLATE");
13015                        self.write_space();
13016                        self.generate_identifier(collate)?;
13017                    }
13018                }
13019                self.write(")");
13020            }
13021            TypeDefinition::Range {
13022                subtype,
13023                subtype_diff,
13024                canonical,
13025            } => {
13026                self.write_keyword("RANGE");
13027                self.write(" (");
13028                self.write_keyword("SUBTYPE");
13029                self.write(" = ");
13030                self.generate_data_type(subtype)?;
13031                if let Some(diff) = subtype_diff {
13032                    self.write(", ");
13033                    self.write_keyword("SUBTYPE_DIFF");
13034                    self.write(" = ");
13035                    self.write(diff);
13036                }
13037                if let Some(canon) = canonical {
13038                    self.write(", ");
13039                    self.write_keyword("CANONICAL");
13040                    self.write(" = ");
13041                    self.write(canon);
13042                }
13043                self.write(")");
13044            }
13045            TypeDefinition::Base {
13046                input,
13047                output,
13048                internallength,
13049            } => {
13050                self.write("(");
13051                self.write_keyword("INPUT");
13052                self.write(" = ");
13053                self.write(input);
13054                self.write(", ");
13055                self.write_keyword("OUTPUT");
13056                self.write(" = ");
13057                self.write(output);
13058                if let Some(len) = internallength {
13059                    self.write(", ");
13060                    self.write_keyword("INTERNALLENGTH");
13061                    self.write(" = ");
13062                    self.write(&len.to_string());
13063                }
13064                self.write(")");
13065            }
13066            TypeDefinition::Domain {
13067                base_type,
13068                default,
13069                constraints,
13070            } => {
13071                self.generate_data_type(base_type)?;
13072                if let Some(def) = default {
13073                    self.write_space();
13074                    self.write_keyword("DEFAULT");
13075                    self.write_space();
13076                    self.generate_expression(def)?;
13077                }
13078                for constr in constraints {
13079                    self.write_space();
13080                    if let Some(name) = &constr.name {
13081                        self.write_keyword("CONSTRAINT");
13082                        self.write_space();
13083                        self.generate_identifier(name)?;
13084                        self.write_space();
13085                    }
13086                    self.write_keyword("CHECK");
13087                    self.write(" (");
13088                    self.generate_expression(&constr.check)?;
13089                    self.write(")");
13090                }
13091            }
13092        }
13093
13094        Ok(())
13095    }
13096
13097    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
13098        self.write_keyword("DROP TYPE");
13099
13100        if dt.if_exists {
13101            self.write_space();
13102            self.write_keyword("IF EXISTS");
13103        }
13104
13105        self.write_space();
13106        self.generate_table(&dt.name)?;
13107
13108        if dt.cascade {
13109            self.write_space();
13110            self.write_keyword("CASCADE");
13111        }
13112
13113        Ok(())
13114    }
13115
13116    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
13117        // Athena: DESCRIBE uses Hive engine (backticks)
13118        let saved_athena_hive_context = self.athena_hive_context;
13119        if matches!(
13120            self.config.dialect,
13121            Some(crate::dialects::DialectType::Athena)
13122        ) {
13123            self.athena_hive_context = true;
13124        }
13125
13126        // Output leading comments before DESCRIBE
13127        for comment in &d.leading_comments {
13128            self.write_formatted_comment(comment);
13129            self.write(" ");
13130        }
13131
13132        self.write_keyword("DESCRIBE");
13133
13134        if d.extended {
13135            self.write_space();
13136            self.write_keyword("EXTENDED");
13137        } else if d.formatted {
13138            self.write_space();
13139            self.write_keyword("FORMATTED");
13140        }
13141
13142        // Output style like ANALYZE, HISTORY
13143        if let Some(ref style) = d.style {
13144            self.write_space();
13145            self.write_keyword(style);
13146        }
13147
13148        // Handle object kind (TABLE, VIEW) based on dialect
13149        let should_output_kind = match self.config.dialect {
13150            // Spark doesn't use TABLE/VIEW after DESCRIBE
13151            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
13152                false
13153            }
13154            // Snowflake always includes TABLE
13155            Some(DialectType::Snowflake) => true,
13156            _ => d.kind.is_some(),
13157        };
13158        if should_output_kind {
13159            if let Some(ref kind) = d.kind {
13160                self.write_space();
13161                self.write_keyword(kind);
13162            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
13163                self.write_space();
13164                self.write_keyword("TABLE");
13165            }
13166        }
13167
13168        self.write_space();
13169        self.generate_expression(&d.target)?;
13170
13171        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
13172        if let Some(ref partition) = d.partition {
13173            self.write_space();
13174            self.generate_expression(partition)?;
13175        }
13176
13177        // Databricks: AS JSON
13178        if d.as_json {
13179            self.write_space();
13180            self.write_keyword("AS JSON");
13181        }
13182
13183        // Output properties like type=stage
13184        for (name, value) in &d.properties {
13185            self.write_space();
13186            self.write(name);
13187            self.write("=");
13188            self.write(value);
13189        }
13190
13191        // Restore Athena Hive context
13192        self.athena_hive_context = saved_athena_hive_context;
13193
13194        Ok(())
13195    }
13196
13197    /// Generate SHOW statement (Snowflake, MySQL, etc.)
13198    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
13199    fn generate_show(&mut self, s: &Show) -> Result<()> {
13200        self.write_keyword("SHOW");
13201        self.write_space();
13202
13203        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
13204        // where TERSE is syntactically valid but has no effect on output
13205        let show_terse = s.terse
13206            && !matches!(
13207                s.this.as_str(),
13208                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
13209            );
13210        if show_terse {
13211            self.write_keyword("TERSE");
13212            self.write_space();
13213        }
13214
13215        // Object type (USERS, TABLES, DATABASES, etc.)
13216        self.write_keyword(&s.this);
13217
13218        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
13219        if let Some(ref target_expr) = s.target {
13220            self.write_space();
13221            self.generate_expression(target_expr)?;
13222        }
13223
13224        // HISTORY keyword
13225        if s.history {
13226            self.write_space();
13227            self.write_keyword("HISTORY");
13228        }
13229
13230        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
13231        if let Some(ref for_target) = s.for_target {
13232            self.write_space();
13233            self.write_keyword("FOR");
13234            self.write_space();
13235            self.generate_expression(for_target)?;
13236        }
13237
13238        // Determine ordering based on dialect:
13239        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
13240        // - MySQL: IN, FROM, LIKE (when FROM is present)
13241        use crate::dialects::DialectType;
13242        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
13243
13244        if !is_snowflake && s.from.is_some() {
13245            // MySQL ordering: IN, FROM, LIKE
13246
13247            // IN scope_kind [scope]
13248            if let Some(ref scope_kind) = s.scope_kind {
13249                self.write_space();
13250                self.write_keyword("IN");
13251                self.write_space();
13252                self.write_keyword(scope_kind);
13253                if let Some(ref scope) = s.scope {
13254                    self.write_space();
13255                    self.generate_expression(scope)?;
13256                }
13257            } else if let Some(ref scope) = s.scope {
13258                self.write_space();
13259                self.write_keyword("IN");
13260                self.write_space();
13261                self.generate_expression(scope)?;
13262            }
13263
13264            // FROM clause
13265            if let Some(ref from) = s.from {
13266                self.write_space();
13267                self.write_keyword("FROM");
13268                self.write_space();
13269                self.generate_expression(from)?;
13270            }
13271
13272            // Second FROM clause (db name)
13273            if let Some(ref db) = s.db {
13274                self.write_space();
13275                self.write_keyword("FROM");
13276                self.write_space();
13277                self.generate_expression(db)?;
13278            }
13279
13280            // LIKE pattern
13281            if let Some(ref like) = s.like {
13282                self.write_space();
13283                self.write_keyword("LIKE");
13284                self.write_space();
13285                self.generate_expression(like)?;
13286            }
13287        } else {
13288            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
13289
13290            // LIKE pattern
13291            if let Some(ref like) = s.like {
13292                self.write_space();
13293                self.write_keyword("LIKE");
13294                self.write_space();
13295                self.generate_expression(like)?;
13296            }
13297
13298            // IN scope_kind [scope]
13299            if let Some(ref scope_kind) = s.scope_kind {
13300                self.write_space();
13301                self.write_keyword("IN");
13302                self.write_space();
13303                self.write_keyword(scope_kind);
13304                if let Some(ref scope) = s.scope {
13305                    self.write_space();
13306                    self.generate_expression(scope)?;
13307                }
13308            } else if let Some(ref scope) = s.scope {
13309                self.write_space();
13310                self.write_keyword("IN");
13311                self.write_space();
13312                self.generate_expression(scope)?;
13313            }
13314        }
13315
13316        // STARTS WITH pattern
13317        if let Some(ref starts_with) = s.starts_with {
13318            self.write_space();
13319            self.write_keyword("STARTS WITH");
13320            self.write_space();
13321            self.generate_expression(starts_with)?;
13322        }
13323
13324        // LIMIT clause
13325        if let Some(ref limit) = s.limit {
13326            self.write_space();
13327            self.generate_limit(limit)?;
13328        }
13329
13330        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
13331        if is_snowflake {
13332            if let Some(ref from) = s.from {
13333                self.write_space();
13334                self.write_keyword("FROM");
13335                self.write_space();
13336                self.generate_expression(from)?;
13337            }
13338        }
13339
13340        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
13341        if let Some(ref where_clause) = s.where_clause {
13342            self.write_space();
13343            self.write_keyword("WHERE");
13344            self.write_space();
13345            self.generate_expression(where_clause)?;
13346        }
13347
13348        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
13349        if let Some(is_mutex) = s.mutex {
13350            self.write_space();
13351            if is_mutex {
13352                self.write_keyword("MUTEX");
13353            } else {
13354                self.write_keyword("STATUS");
13355            }
13356        }
13357
13358        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
13359        if !s.privileges.is_empty() {
13360            self.write_space();
13361            self.write_keyword("WITH PRIVILEGES");
13362            self.write_space();
13363            for (i, priv_name) in s.privileges.iter().enumerate() {
13364                if i > 0 {
13365                    self.write(", ");
13366                }
13367                self.write_keyword(priv_name);
13368            }
13369        }
13370
13371        Ok(())
13372    }
13373
13374    // ==================== End DDL Generation ====================
13375
13376    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
13377        use crate::dialects::DialectType;
13378        match lit {
13379            Literal::String(s) => {
13380                self.generate_string_literal(s)?;
13381            }
13382            Literal::Number(n) => {
13383                if matches!(self.config.dialect, Some(DialectType::MySQL))
13384                    && n.len() > 2
13385                    && (n.starts_with("0x") || n.starts_with("0X"))
13386                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
13387                {
13388                    return self.generate_identifier(&Identifier {
13389                        name: n.clone(),
13390                        quoted: true,
13391                        trailing_comments: Vec::new(),
13392                        span: None,
13393                    });
13394                }
13395                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
13396                // for dialects that don't support them (MySQL interprets as identifier).
13397                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
13398                let n = if n.contains('_')
13399                    && !matches!(
13400                        self.config.dialect,
13401                        Some(DialectType::ClickHouse)
13402                            | Some(DialectType::DuckDB)
13403                            | Some(DialectType::PostgreSQL)
13404                            | Some(DialectType::Hive)
13405                            | Some(DialectType::Spark)
13406                            | Some(DialectType::Databricks)
13407                    ) {
13408                    std::borrow::Cow::Owned(n.replace('_', ""))
13409                } else {
13410                    std::borrow::Cow::Borrowed(n.as_str())
13411                };
13412                // Normalize numbers starting with decimal point to have leading zero
13413                // e.g., .25 -> 0.25 (matches sqlglot behavior)
13414                if n.starts_with('.') {
13415                    self.write("0");
13416                    self.write(&n);
13417                } else if n.starts_with("-.") {
13418                    // Handle negative numbers like -.25 -> -0.25
13419                    self.write("-0");
13420                    self.write(&n[1..]);
13421                } else {
13422                    self.write(&n);
13423                }
13424            }
13425            Literal::HexString(h) => {
13426                // Most dialects use lowercase x'...' for hex literals; Spark/Databricks/Teradata use uppercase X'...'
13427                match self.config.dialect {
13428                    Some(DialectType::Spark)
13429                    | Some(DialectType::Databricks)
13430                    | Some(DialectType::Teradata) => self.write("X'"),
13431                    _ => self.write("x'"),
13432                }
13433                self.write(h);
13434                self.write("'");
13435            }
13436            Literal::HexNumber(h) => {
13437                // Hex number (0xA) - integer in hex notation (from BigQuery)
13438                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
13439                // For other dialects, convert to decimal integer
13440                match self.config.dialect {
13441                    Some(DialectType::BigQuery)
13442                    | Some(DialectType::TSQL)
13443                    | Some(DialectType::Fabric) => {
13444                        self.write("0x");
13445                        self.write(h);
13446                    }
13447                    _ => {
13448                        // Convert hex to decimal
13449                        if let Ok(val) = u64::from_str_radix(h, 16) {
13450                            self.write(&val.to_string());
13451                        } else {
13452                            // Fallback: keep as 0x notation
13453                            self.write("0x");
13454                            self.write(h);
13455                        }
13456                    }
13457                }
13458            }
13459            Literal::BitString(b) => {
13460                // Bit string B'0101...'
13461                self.write("B'");
13462                self.write(b);
13463                self.write("'");
13464            }
13465            Literal::ByteString(b) => {
13466                // Byte string b'...' (BigQuery style)
13467                self.write("b'");
13468                // Escape special characters for output
13469                self.write_escaped_byte_string(b);
13470                self.write("'");
13471            }
13472            Literal::NationalString(s) => {
13473                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
13474                // Other dialects strip the N prefix and output as regular string
13475                let keep_n_prefix = matches!(
13476                    self.config.dialect,
13477                    Some(DialectType::TSQL)
13478                        | Some(DialectType::Oracle)
13479                        | Some(DialectType::MySQL)
13480                        | None
13481                );
13482                if keep_n_prefix {
13483                    self.write("N'");
13484                } else {
13485                    self.write("'");
13486                }
13487                self.write(s);
13488                self.write("'");
13489            }
13490            Literal::Date(d) => {
13491                self.generate_date_literal(d)?;
13492            }
13493            Literal::Time(t) => {
13494                self.generate_time_literal(t)?;
13495            }
13496            Literal::Timestamp(ts) => {
13497                self.generate_timestamp_literal(ts)?;
13498            }
13499            Literal::Datetime(dt) => {
13500                self.generate_datetime_literal(dt)?;
13501            }
13502            Literal::TripleQuotedString(s, _quote_char) => {
13503                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
13504                if matches!(
13505                    self.config.dialect,
13506                    Some(crate::dialects::DialectType::BigQuery)
13507                        | Some(crate::dialects::DialectType::DuckDB)
13508                        | Some(crate::dialects::DialectType::Snowflake)
13509                        | Some(crate::dialects::DialectType::Spark)
13510                        | Some(crate::dialects::DialectType::Hive)
13511                        | Some(crate::dialects::DialectType::Presto)
13512                        | Some(crate::dialects::DialectType::Trino)
13513                        | Some(crate::dialects::DialectType::PostgreSQL)
13514                        | Some(crate::dialects::DialectType::MySQL)
13515                        | Some(crate::dialects::DialectType::Redshift)
13516                        | Some(crate::dialects::DialectType::TSQL)
13517                        | Some(crate::dialects::DialectType::Oracle)
13518                        | Some(crate::dialects::DialectType::ClickHouse)
13519                        | Some(crate::dialects::DialectType::Databricks)
13520                        | Some(crate::dialects::DialectType::SQLite)
13521                ) {
13522                    self.generate_string_literal(s)?;
13523                } else {
13524                    // Preserve triple-quoted string syntax for generic/unknown dialects
13525                    let quotes = format!("{0}{0}{0}", _quote_char);
13526                    self.write(&quotes);
13527                    self.write(s);
13528                    self.write(&quotes);
13529                }
13530            }
13531            Literal::EscapeString(s) => {
13532                // PostgreSQL escape string: e'...' or E'...'
13533                // Token text format is "e:content" or "E:content"
13534                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
13535                use crate::dialects::DialectType;
13536                let content = if let Some(c) = s.strip_prefix("e:") {
13537                    c
13538                } else if let Some(c) = s.strip_prefix("E:") {
13539                    c
13540                } else {
13541                    s.as_str()
13542                };
13543
13544                // MySQL: output the content without quotes or prefix
13545                if matches!(
13546                    self.config.dialect,
13547                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
13548                ) {
13549                    self.write(content);
13550                } else {
13551                    // Some dialects use lowercase e' prefix
13552                    let prefix = if matches!(
13553                        self.config.dialect,
13554                        Some(DialectType::SingleStore)
13555                            | Some(DialectType::DuckDB)
13556                            | Some(DialectType::PostgreSQL)
13557                            | Some(DialectType::CockroachDB)
13558                            | Some(DialectType::Materialize)
13559                            | Some(DialectType::RisingWave)
13560                    ) {
13561                        "e'"
13562                    } else {
13563                        "E'"
13564                    };
13565
13566                    // Normalize \' to '' for output
13567                    let normalized = content.replace("\\'", "''");
13568                    self.write(prefix);
13569                    self.write(&normalized);
13570                    self.write("'");
13571                }
13572            }
13573            Literal::DollarString(s) => {
13574                // Convert dollar-quoted strings to single-quoted strings
13575                // (like Python sqlglot's rawstring_sql)
13576                use crate::dialects::DialectType;
13577                // Extract content from tag\x00content format
13578                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
13579                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
13580                let escape_backslash = matches!(self.config.dialect, Some(DialectType::Snowflake));
13581                // Step 2: Determine quote escaping style
13582                // Snowflake: ' -> \' (backslash escape)
13583                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
13584                let use_backslash_quote =
13585                    matches!(self.config.dialect, Some(DialectType::Snowflake));
13586
13587                let mut escaped = String::with_capacity(content.len() + 4);
13588                for ch in content.chars() {
13589                    if escape_backslash && ch == '\\' {
13590                        // Escape backslash first (before quote escaping)
13591                        escaped.push('\\');
13592                        escaped.push('\\');
13593                    } else if ch == '\'' {
13594                        if use_backslash_quote {
13595                            escaped.push('\\');
13596                            escaped.push('\'');
13597                        } else {
13598                            escaped.push('\'');
13599                            escaped.push('\'');
13600                        }
13601                    } else {
13602                        escaped.push(ch);
13603                    }
13604                }
13605                self.write("'");
13606                self.write(&escaped);
13607                self.write("'");
13608            }
13609            Literal::RawString(s) => {
13610                // Raw strings (r"..." or r'...') contain literal backslashes.
13611                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
13612                // 1. If \\ is in STRING_ESCAPES, double all backslashes
13613                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
13614                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
13615                use crate::dialects::DialectType;
13616
13617                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
13618                let escape_backslash = matches!(
13619                    self.config.dialect,
13620                    Some(DialectType::BigQuery)
13621                        | Some(DialectType::MySQL)
13622                        | Some(DialectType::SingleStore)
13623                        | Some(DialectType::TiDB)
13624                        | Some(DialectType::Hive)
13625                        | Some(DialectType::Spark)
13626                        | Some(DialectType::Databricks)
13627                        | Some(DialectType::Drill)
13628                        | Some(DialectType::Snowflake)
13629                        | Some(DialectType::Redshift)
13630                        | Some(DialectType::ClickHouse)
13631                );
13632
13633                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
13634                // These escape quotes as \' instead of ''
13635                let backslash_escapes_quote = matches!(
13636                    self.config.dialect,
13637                    Some(DialectType::BigQuery)
13638                        | Some(DialectType::Hive)
13639                        | Some(DialectType::Spark)
13640                        | Some(DialectType::Databricks)
13641                        | Some(DialectType::Drill)
13642                        | Some(DialectType::Snowflake)
13643                        | Some(DialectType::Redshift)
13644                );
13645
13646                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
13647                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
13648                let supports_escape_sequences = escape_backslash;
13649
13650                let mut escaped = String::with_capacity(s.len() + 4);
13651                for ch in s.chars() {
13652                    if escape_backslash && ch == '\\' {
13653                        // Double the backslash for the target dialect
13654                        escaped.push('\\');
13655                        escaped.push('\\');
13656                    } else if ch == '\'' {
13657                        if backslash_escapes_quote {
13658                            // Use backslash to escape the quote: \'
13659                            escaped.push('\\');
13660                            escaped.push('\'');
13661                        } else {
13662                            // Use SQL standard quote doubling: ''
13663                            escaped.push('\'');
13664                            escaped.push('\'');
13665                        }
13666                    } else if supports_escape_sequences {
13667                        // Apply ESCAPED_SEQUENCES mapping for special chars
13668                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
13669                        match ch {
13670                            '\n' => {
13671                                escaped.push('\\');
13672                                escaped.push('n');
13673                            }
13674                            '\r' => {
13675                                escaped.push('\\');
13676                                escaped.push('r');
13677                            }
13678                            '\t' => {
13679                                escaped.push('\\');
13680                                escaped.push('t');
13681                            }
13682                            '\x07' => {
13683                                escaped.push('\\');
13684                                escaped.push('a');
13685                            }
13686                            '\x08' => {
13687                                escaped.push('\\');
13688                                escaped.push('b');
13689                            }
13690                            '\x0C' => {
13691                                escaped.push('\\');
13692                                escaped.push('f');
13693                            }
13694                            '\x0B' => {
13695                                escaped.push('\\');
13696                                escaped.push('v');
13697                            }
13698                            _ => escaped.push(ch),
13699                        }
13700                    } else {
13701                        escaped.push(ch);
13702                    }
13703                }
13704                self.write("'");
13705                self.write(&escaped);
13706                self.write("'");
13707            }
13708        }
13709        Ok(())
13710    }
13711
13712    /// Generate a DATE literal with dialect-specific formatting
13713    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
13714        use crate::dialects::DialectType;
13715
13716        match self.config.dialect {
13717            // SQL Server uses CONVERT or CAST
13718            Some(DialectType::TSQL) => {
13719                self.write("CAST('");
13720                self.write(d);
13721                self.write("' AS DATE)");
13722            }
13723            // BigQuery uses CAST syntax for type literals
13724            // DATE 'value' -> CAST('value' AS DATE)
13725            Some(DialectType::BigQuery) => {
13726                self.write("CAST('");
13727                self.write(d);
13728                self.write("' AS DATE)");
13729            }
13730            // Exasol uses CAST syntax for DATE literals
13731            // DATE 'value' -> CAST('value' AS DATE)
13732            Some(DialectType::Exasol) => {
13733                self.write("CAST('");
13734                self.write(d);
13735                self.write("' AS DATE)");
13736            }
13737            // Snowflake uses CAST syntax for DATE literals
13738            // DATE 'value' -> CAST('value' AS DATE)
13739            Some(DialectType::Snowflake) => {
13740                self.write("CAST('");
13741                self.write(d);
13742                self.write("' AS DATE)");
13743            }
13744            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
13745            Some(DialectType::PostgreSQL)
13746            | Some(DialectType::MySQL)
13747            | Some(DialectType::SingleStore)
13748            | Some(DialectType::TiDB)
13749            | Some(DialectType::Redshift) => {
13750                self.write("CAST('");
13751                self.write(d);
13752                self.write("' AS DATE)");
13753            }
13754            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
13755            Some(DialectType::DuckDB)
13756            | Some(DialectType::Presto)
13757            | Some(DialectType::Trino)
13758            | Some(DialectType::Athena)
13759            | Some(DialectType::Spark)
13760            | Some(DialectType::Databricks)
13761            | Some(DialectType::Hive) => {
13762                self.write("CAST('");
13763                self.write(d);
13764                self.write("' AS DATE)");
13765            }
13766            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
13767            Some(DialectType::Oracle) => {
13768                self.write("TO_DATE('");
13769                self.write(d);
13770                self.write("', 'YYYY-MM-DD')");
13771            }
13772            // Standard SQL: DATE '...'
13773            _ => {
13774                self.write_keyword("DATE");
13775                self.write(" '");
13776                self.write(d);
13777                self.write("'");
13778            }
13779        }
13780        Ok(())
13781    }
13782
13783    /// Generate a TIME literal with dialect-specific formatting
13784    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
13785        use crate::dialects::DialectType;
13786
13787        match self.config.dialect {
13788            // SQL Server uses CONVERT or CAST
13789            Some(DialectType::TSQL) => {
13790                self.write("CAST('");
13791                self.write(t);
13792                self.write("' AS TIME)");
13793            }
13794            // Standard SQL: TIME '...'
13795            _ => {
13796                self.write_keyword("TIME");
13797                self.write(" '");
13798                self.write(t);
13799                self.write("'");
13800            }
13801        }
13802        Ok(())
13803    }
13804
13805    /// Generate a date expression for Dremio, converting DATE literals to CAST
13806    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
13807        use crate::expressions::Literal;
13808
13809        match expr {
13810            Expression::Literal(Literal::Date(d)) => {
13811                // DATE 'value' -> CAST('value' AS DATE)
13812                self.write("CAST('");
13813                self.write(d);
13814                self.write("' AS DATE)");
13815            }
13816            _ => {
13817                // For all other expressions, generate normally
13818                self.generate_expression(expr)?;
13819            }
13820        }
13821        Ok(())
13822    }
13823
13824    /// Generate a TIMESTAMP literal with dialect-specific formatting
13825    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
13826        use crate::dialects::DialectType;
13827
13828        match self.config.dialect {
13829            // SQL Server uses CONVERT or CAST
13830            Some(DialectType::TSQL) => {
13831                self.write("CAST('");
13832                self.write(ts);
13833                self.write("' AS DATETIME2)");
13834            }
13835            // BigQuery uses CAST syntax for type literals
13836            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13837            Some(DialectType::BigQuery) => {
13838                self.write("CAST('");
13839                self.write(ts);
13840                self.write("' AS TIMESTAMP)");
13841            }
13842            // Snowflake uses CAST syntax for TIMESTAMP literals
13843            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13844            Some(DialectType::Snowflake) => {
13845                self.write("CAST('");
13846                self.write(ts);
13847                self.write("' AS TIMESTAMP)");
13848            }
13849            // Dremio uses CAST syntax for TIMESTAMP literals
13850            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13851            Some(DialectType::Dremio) => {
13852                self.write("CAST('");
13853                self.write(ts);
13854                self.write("' AS TIMESTAMP)");
13855            }
13856            // Exasol uses CAST syntax for TIMESTAMP literals
13857            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13858            Some(DialectType::Exasol) => {
13859                self.write("CAST('");
13860                self.write(ts);
13861                self.write("' AS TIMESTAMP)");
13862            }
13863            // Oracle prefers TO_TIMESTAMP function call
13864            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
13865            Some(DialectType::Oracle) => {
13866                self.write("TO_TIMESTAMP('");
13867                self.write(ts);
13868                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
13869            }
13870            // Presto/Trino: always use CAST for TIMESTAMP literals
13871            Some(DialectType::Presto) | Some(DialectType::Trino) => {
13872                if Self::timestamp_has_timezone(ts) {
13873                    self.write("CAST('");
13874                    self.write(ts);
13875                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
13876                } else {
13877                    self.write("CAST('");
13878                    self.write(ts);
13879                    self.write("' AS TIMESTAMP)");
13880                }
13881            }
13882            // ClickHouse: CAST('...' AS Nullable(DateTime))
13883            Some(DialectType::ClickHouse) => {
13884                self.write("CAST('");
13885                self.write(ts);
13886                self.write("' AS Nullable(DateTime))");
13887            }
13888            // Spark: CAST('...' AS TIMESTAMP)
13889            Some(DialectType::Spark) => {
13890                self.write("CAST('");
13891                self.write(ts);
13892                self.write("' AS TIMESTAMP)");
13893            }
13894            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
13895            // but TIMESTAMP '...' for special values like 'epoch'
13896            Some(DialectType::Redshift) => {
13897                if ts == "epoch" {
13898                    self.write_keyword("TIMESTAMP");
13899                    self.write(" '");
13900                    self.write(ts);
13901                    self.write("'");
13902                } else {
13903                    self.write("CAST('");
13904                    self.write(ts);
13905                    self.write("' AS TIMESTAMP)");
13906                }
13907            }
13908            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
13909            Some(DialectType::PostgreSQL)
13910            | Some(DialectType::Hive)
13911            | Some(DialectType::SQLite)
13912            | Some(DialectType::DuckDB)
13913            | Some(DialectType::Athena)
13914            | Some(DialectType::Drill)
13915            | Some(DialectType::Teradata) => {
13916                self.write("CAST('");
13917                self.write(ts);
13918                self.write("' AS TIMESTAMP)");
13919            }
13920            // MySQL/StarRocks: CAST('...' AS DATETIME)
13921            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
13922                self.write("CAST('");
13923                self.write(ts);
13924                self.write("' AS DATETIME)");
13925            }
13926            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
13927            Some(DialectType::Databricks) => {
13928                self.write("CAST('");
13929                self.write(ts);
13930                self.write("' AS TIMESTAMP_NTZ)");
13931            }
13932            // Standard SQL: TIMESTAMP '...'
13933            _ => {
13934                self.write_keyword("TIMESTAMP");
13935                self.write(" '");
13936                self.write(ts);
13937                self.write("'");
13938            }
13939        }
13940        Ok(())
13941    }
13942
13943    /// Check if a timestamp string contains a timezone identifier
13944    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
13945    fn timestamp_has_timezone(ts: &str) -> bool {
13946        // Check for common IANA timezone patterns: Continent/City format
13947        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
13948        // Also handles: UTC, GMT, Etc/GMT+0, etc.
13949        let ts_lower = ts.to_lowercase();
13950
13951        // Check for Continent/City pattern (most common)
13952        let continent_prefixes = [
13953            "africa/",
13954            "america/",
13955            "antarctica/",
13956            "arctic/",
13957            "asia/",
13958            "atlantic/",
13959            "australia/",
13960            "europe/",
13961            "indian/",
13962            "pacific/",
13963            "etc/",
13964            "brazil/",
13965            "canada/",
13966            "chile/",
13967            "mexico/",
13968            "us/",
13969        ];
13970
13971        for prefix in &continent_prefixes {
13972            if ts_lower.contains(prefix) {
13973                return true;
13974            }
13975        }
13976
13977        // Check for standalone timezone abbreviations at the end
13978        // These typically appear after the time portion
13979        let tz_abbrevs = [
13980            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
13981            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
13982            " sgt", " aest", " aedt", " acst", " acdt", " awst",
13983        ];
13984
13985        for abbrev in &tz_abbrevs {
13986            if ts_lower.ends_with(abbrev) {
13987                return true;
13988            }
13989        }
13990
13991        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
13992        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
13993        // Look for pattern: space followed by + or - and digits (optionally with :)
13994        let trimmed = ts.trim();
13995        if let Some(last_space) = trimmed.rfind(' ') {
13996            let suffix = &trimmed[last_space + 1..];
13997            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
13998                // Check if rest is numeric (possibly with : for hh:mm format)
13999                let rest = &suffix[1..];
14000                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
14001                    return true;
14002                }
14003            }
14004        }
14005
14006        false
14007    }
14008
14009    /// Generate a DATETIME literal with dialect-specific formatting
14010    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
14011        use crate::dialects::DialectType;
14012
14013        match self.config.dialect {
14014            // BigQuery uses CAST syntax for type literals
14015            // DATETIME 'value' -> CAST('value' AS DATETIME)
14016            Some(DialectType::BigQuery) => {
14017                self.write("CAST('");
14018                self.write(dt);
14019                self.write("' AS DATETIME)");
14020            }
14021            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
14022            Some(DialectType::DuckDB) => {
14023                self.write("CAST('");
14024                self.write(dt);
14025                self.write("' AS TIMESTAMP)");
14026            }
14027            // DATETIME is primarily a BigQuery type
14028            // Output as DATETIME '...' for dialects that support it
14029            _ => {
14030                self.write_keyword("DATETIME");
14031                self.write(" '");
14032                self.write(dt);
14033                self.write("'");
14034            }
14035        }
14036        Ok(())
14037    }
14038
14039    /// Generate a string literal with dialect-specific escaping
14040    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
14041        use crate::dialects::DialectType;
14042
14043        match self.config.dialect {
14044            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
14045            // and backslash escaping for special characters like newlines
14046            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
14047            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
14048                // Hive/Spark use backslash escaping for quotes (\') and special chars
14049                self.write("'");
14050                for c in s.chars() {
14051                    match c {
14052                        '\'' => self.write("\\'"),
14053                        '\\' => self.write("\\\\"),
14054                        '\n' => self.write("\\n"),
14055                        '\r' => self.write("\\r"),
14056                        '\t' => self.write("\\t"),
14057                        '\0' => self.write("\\0"),
14058                        _ => self.output.push(c),
14059                    }
14060                }
14061                self.write("'");
14062            }
14063            Some(DialectType::Drill) => {
14064                // Drill uses SQL-standard quote doubling ('') for quotes,
14065                // but backslash escaping for special characters
14066                self.write("'");
14067                for c in s.chars() {
14068                    match c {
14069                        '\'' => self.write("''"),
14070                        '\\' => self.write("\\\\"),
14071                        '\n' => self.write("\\n"),
14072                        '\r' => self.write("\\r"),
14073                        '\t' => self.write("\\t"),
14074                        '\0' => self.write("\\0"),
14075                        _ => self.output.push(c),
14076                    }
14077                }
14078                self.write("'");
14079            }
14080            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
14081                self.write("'");
14082                for c in s.chars() {
14083                    match c {
14084                        // MySQL uses SQL standard quote doubling
14085                        '\'' => self.write("''"),
14086                        '\\' => self.write("\\\\"),
14087                        '\n' => self.write("\\n"),
14088                        '\r' => self.write("\\r"),
14089                        '\t' => self.write("\\t"),
14090                        // sqlglot writes a literal NUL for this case
14091                        '\0' => self.output.push('\0'),
14092                        _ => self.output.push(c),
14093                    }
14094                }
14095                self.write("'");
14096            }
14097            // BigQuery: Uses backslash escaping
14098            Some(DialectType::BigQuery) => {
14099                self.write("'");
14100                for c in s.chars() {
14101                    match c {
14102                        '\'' => self.write("\\'"),
14103                        '\\' => self.write("\\\\"),
14104                        '\n' => self.write("\\n"),
14105                        '\r' => self.write("\\r"),
14106                        '\t' => self.write("\\t"),
14107                        '\0' => self.write("\\0"),
14108                        '\x07' => self.write("\\a"),
14109                        '\x08' => self.write("\\b"),
14110                        '\x0C' => self.write("\\f"),
14111                        '\x0B' => self.write("\\v"),
14112                        _ => self.output.push(c),
14113                    }
14114                }
14115                self.write("'");
14116            }
14117            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
14118            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
14119            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
14120            Some(DialectType::Athena) => {
14121                if self.athena_hive_context {
14122                    // Hive-style: backslash escaping
14123                    self.write("'");
14124                    for c in s.chars() {
14125                        match c {
14126                            '\'' => self.write("\\'"),
14127                            '\\' => self.write("\\\\"),
14128                            '\n' => self.write("\\n"),
14129                            '\r' => self.write("\\r"),
14130                            '\t' => self.write("\\t"),
14131                            '\0' => self.write("\\0"),
14132                            _ => self.output.push(c),
14133                        }
14134                    }
14135                    self.write("'");
14136                } else {
14137                    // Trino-style: SQL-standard escaping, preserve backslashes
14138                    self.write("'");
14139                    for c in s.chars() {
14140                        match c {
14141                            '\'' => self.write("''"),
14142                            // Preserve backslashes literally (no re-escaping)
14143                            _ => self.output.push(c),
14144                        }
14145                    }
14146                    self.write("'");
14147                }
14148            }
14149            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
14150            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
14151            // becomes string value '\\'), so we should NOT re-escape backslashes.
14152            // We only need to escape single quotes.
14153            Some(DialectType::Snowflake) => {
14154                self.write("'");
14155                for c in s.chars() {
14156                    match c {
14157                        '\'' => self.write("\\'"),
14158                        // Backslashes are already escaped in the tokenized string, don't re-escape
14159                        // Only escape special characters that might not have been escaped
14160                        '\n' => self.write("\\n"),
14161                        '\r' => self.write("\\r"),
14162                        '\t' => self.write("\\t"),
14163                        _ => self.output.push(c),
14164                    }
14165                }
14166                self.write("'");
14167            }
14168            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
14169            Some(DialectType::PostgreSQL) => {
14170                self.write("'");
14171                for c in s.chars() {
14172                    match c {
14173                        '\'' => self.write("''"),
14174                        _ => self.output.push(c),
14175                    }
14176                }
14177                self.write("'");
14178            }
14179            // Redshift: Uses backslash escaping for single quotes
14180            Some(DialectType::Redshift) => {
14181                self.write("'");
14182                for c in s.chars() {
14183                    match c {
14184                        '\'' => self.write("\\'"),
14185                        _ => self.output.push(c),
14186                    }
14187                }
14188                self.write("'");
14189            }
14190            // Oracle: Uses standard double single-quote escaping
14191            Some(DialectType::Oracle) => {
14192                self.write("'");
14193                self.write(&s.replace('\'', "''"));
14194                self.write("'");
14195            }
14196            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
14197            // backslash escaping for backslashes and special characters
14198            Some(DialectType::ClickHouse) => {
14199                self.write("'");
14200                for c in s.chars() {
14201                    match c {
14202                        '\'' => self.write("''"),
14203                        '\\' => self.write("\\\\"),
14204                        '\n' => self.write("\\n"),
14205                        '\r' => self.write("\\r"),
14206                        '\t' => self.write("\\t"),
14207                        '\0' => self.write("\\0"),
14208                        '\x07' => self.write("\\a"),
14209                        '\x08' => self.write("\\b"),
14210                        '\x0C' => self.write("\\f"),
14211                        '\x0B' => self.write("\\v"),
14212                        // Non-printable characters: emit as \xNN hex escapes
14213                        c if c.is_control() || (c as u32) < 0x20 => {
14214                            let byte = c as u32;
14215                            if byte < 256 {
14216                                self.write(&format!("\\x{:02X}", byte));
14217                            } else {
14218                                self.output.push(c);
14219                            }
14220                        }
14221                        _ => self.output.push(c),
14222                    }
14223                }
14224                self.write("'");
14225            }
14226            // Default: SQL standard double single quotes (works for most dialects)
14227            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
14228            _ => {
14229                self.write("'");
14230                self.write(&s.replace('\'', "''"));
14231                self.write("'");
14232            }
14233        }
14234        Ok(())
14235    }
14236
14237    /// Write a byte string with proper escaping for BigQuery-style byte literals
14238    /// Escapes characters as \xNN hex escapes where needed
14239    fn write_escaped_byte_string(&mut self, s: &str) {
14240        for c in s.chars() {
14241            match c {
14242                // Escape single quotes
14243                '\'' => self.write("\\'"),
14244                // Escape backslashes
14245                '\\' => self.write("\\\\"),
14246                // Keep all printable characters (including non-ASCII) as-is
14247                _ if !c.is_control() => self.output.push(c),
14248                // Escape control characters as hex
14249                _ => {
14250                    let byte = c as u32;
14251                    if byte < 256 {
14252                        self.write(&format!("\\x{:02x}", byte));
14253                    } else {
14254                        // For unicode characters, write each UTF-8 byte
14255                        for b in c.to_string().as_bytes() {
14256                            self.write(&format!("\\x{:02x}", b));
14257                        }
14258                    }
14259                }
14260            }
14261        }
14262    }
14263
14264    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
14265        use crate::dialects::DialectType;
14266
14267        // Different dialects have different boolean literal formats
14268        match self.config.dialect {
14269            // SQL Server typically uses 1/0 for boolean literals in many contexts
14270            // However, TRUE/FALSE also works in modern versions
14271            Some(DialectType::TSQL) => {
14272                self.write(if b.value { "1" } else { "0" });
14273            }
14274            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
14275            Some(DialectType::Oracle) => {
14276                self.write(if b.value { "1" } else { "0" });
14277            }
14278            // MySQL accepts TRUE/FALSE as aliases for 1/0
14279            Some(DialectType::MySQL) => {
14280                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
14281            }
14282            // Most other dialects support TRUE/FALSE
14283            _ => {
14284                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
14285            }
14286        }
14287        Ok(())
14288    }
14289
14290    /// Generate an identifier that's used as an alias name
14291    /// This quotes reserved keywords in addition to already-quoted identifiers
14292    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
14293        let name = &id.name;
14294        let quote_style = &self.config.identifier_quote_style;
14295
14296        // For aliases, quote if:
14297        // 1. The identifier was explicitly quoted in the source
14298        // 2. The identifier is a reserved keyword for the current dialect
14299        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
14300
14301        // Normalize identifier if configured
14302        let output_name = if self.config.normalize_identifiers && !id.quoted {
14303            name.to_lowercase()
14304        } else {
14305            name.to_string()
14306        };
14307
14308        if needs_quoting {
14309            // Escape any quote characters within the identifier
14310            let escaped_name = if quote_style.start == quote_style.end {
14311                output_name.replace(
14312                    quote_style.end,
14313                    &format!("{}{}", quote_style.end, quote_style.end),
14314                )
14315            } else {
14316                output_name.replace(
14317                    quote_style.end,
14318                    &format!("{}{}", quote_style.end, quote_style.end),
14319                )
14320            };
14321            self.write(&format!(
14322                "{}{}{}",
14323                quote_style.start, escaped_name, quote_style.end
14324            ));
14325        } else {
14326            self.write(&output_name);
14327        }
14328
14329        // Output trailing comments
14330        for comment in &id.trailing_comments {
14331            self.write(" ");
14332            self.write_formatted_comment(comment);
14333        }
14334        Ok(())
14335    }
14336
14337    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
14338        use crate::dialects::DialectType;
14339
14340        let name = &id.name;
14341
14342        // For Athena, use backticks in Hive context, double quotes in Trino context
14343        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
14344            && self.athena_hive_context
14345        {
14346            &IdentifierQuoteStyle::BACKTICK
14347        } else {
14348            &self.config.identifier_quote_style
14349        };
14350
14351        // Quote if:
14352        // 1. The identifier was explicitly quoted in the source
14353        // 2. The identifier is a reserved keyword for the current dialect
14354        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
14355        // This matches Python sqlglot's identifier_sql behavior
14356        // Also quote identifiers starting with digits if the target dialect doesn't support them
14357        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
14358        let needs_digit_quoting = starts_with_digit
14359            && !self.config.identifiers_can_start_with_digit
14360            && self.config.dialect.is_some();
14361        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
14362            && name.len() > 2
14363            && (name.starts_with("0x") || name.starts_with("0X"))
14364            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
14365        let needs_quoting = id.quoted
14366            || self.is_reserved_keyword(name)
14367            || self.config.always_quote_identifiers
14368            || needs_digit_quoting
14369            || mysql_invalid_hex_identifier;
14370
14371        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
14372        // When quoted, we need to output `name`(16) not `name(16)`
14373        let (base_name, suffix) = if needs_quoting {
14374            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
14375            if let Some(paren_pos) = name.find('(') {
14376                let base = &name[..paren_pos];
14377                let rest = &name[paren_pos..];
14378                // Verify it looks like (digits) or (digits) ASC/DESC
14379                if rest.starts_with('(')
14380                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
14381                {
14382                    // Check if content between parens is all digits
14383                    let close_paren = rest.find(')').unwrap_or(rest.len());
14384                    let inside = &rest[1..close_paren];
14385                    if inside.chars().all(|c| c.is_ascii_digit()) {
14386                        (base.to_string(), rest.to_string())
14387                    } else {
14388                        (name.to_string(), String::new())
14389                    }
14390                } else {
14391                    (name.to_string(), String::new())
14392                }
14393            } else if name.ends_with(" ASC") {
14394                let base = &name[..name.len() - 4];
14395                (base.to_string(), " ASC".to_string())
14396            } else if name.ends_with(" DESC") {
14397                let base = &name[..name.len() - 5];
14398                (base.to_string(), " DESC".to_string())
14399            } else {
14400                (name.to_string(), String::new())
14401            }
14402        } else {
14403            (name.to_string(), String::new())
14404        };
14405
14406        // Normalize identifier if configured, with special handling for Exasol
14407        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
14408        // should be uppercased when not already quoted (to match Python sqlglot behavior)
14409        let output_name = if self.config.normalize_identifiers && !id.quoted {
14410            base_name.to_lowercase()
14411        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
14412            && !id.quoted
14413            && self.is_reserved_keyword(name)
14414        {
14415            // Exasol: uppercase reserved keywords when quoting them
14416            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
14417            base_name.to_uppercase()
14418        } else {
14419            base_name
14420        };
14421
14422        if needs_quoting {
14423            // Escape any quote characters within the identifier
14424            let escaped_name = if quote_style.start == quote_style.end {
14425                // Same start/end char (e.g., " or `) - double the quote char
14426                output_name.replace(
14427                    quote_style.end,
14428                    &format!("{}{}", quote_style.end, quote_style.end),
14429                )
14430            } else {
14431                // Different start/end (e.g., [ and ]) - escape only the end char
14432                output_name.replace(
14433                    quote_style.end,
14434                    &format!("{}{}", quote_style.end, quote_style.end),
14435                )
14436            };
14437            self.write(&format!(
14438                "{}{}{}{}",
14439                quote_style.start, escaped_name, quote_style.end, suffix
14440            ));
14441        } else {
14442            self.write(&output_name);
14443        }
14444
14445        // Output trailing comments
14446        for comment in &id.trailing_comments {
14447            self.write(" ");
14448            self.write_formatted_comment(comment);
14449        }
14450        Ok(())
14451    }
14452
14453    fn generate_column(&mut self, col: &Column) -> Result<()> {
14454        use crate::dialects::DialectType;
14455
14456        if let Some(table) = &col.table {
14457            // Exasol special case: LOCAL as column table prefix should NOT be quoted
14458            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
14459            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
14460            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
14461                && !table.quoted
14462                && table.name.eq_ignore_ascii_case("LOCAL");
14463
14464            if is_exasol_local_prefix {
14465                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
14466                self.write("LOCAL");
14467            } else {
14468                self.generate_identifier(table)?;
14469            }
14470            self.write(".");
14471        }
14472        self.generate_identifier(&col.name)?;
14473        // Oracle-style join marker (+)
14474        // Only output if dialect supports it (Oracle, Exasol)
14475        if col.join_mark && self.config.supports_column_join_marks {
14476            self.write(" (+)");
14477        }
14478        // Output trailing comments
14479        for comment in &col.trailing_comments {
14480            self.write_space();
14481            self.write_formatted_comment(comment);
14482        }
14483        Ok(())
14484    }
14485
14486    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
14487    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
14488    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
14489        use crate::dialects::DialectType;
14490        use crate::expressions::PseudocolumnType;
14491
14492        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
14493        if pc.kind == PseudocolumnType::Sysdate
14494            && !matches!(
14495                self.config.dialect,
14496                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
14497            )
14498        {
14499            self.write_keyword("CURRENT_TIMESTAMP");
14500            // Add () for dialects that expect it
14501            if matches!(
14502                self.config.dialect,
14503                Some(DialectType::MySQL)
14504                    | Some(DialectType::ClickHouse)
14505                    | Some(DialectType::Spark)
14506                    | Some(DialectType::Databricks)
14507                    | Some(DialectType::Hive)
14508            ) {
14509                self.write("()");
14510            }
14511        } else {
14512            self.write(pc.kind.as_str());
14513        }
14514        Ok(())
14515    }
14516
14517    /// Generate CONNECT BY clause (Oracle hierarchical queries)
14518    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
14519        use crate::dialects::DialectType;
14520
14521        // Generate native CONNECT BY for Oracle and Snowflake
14522        // For other dialects, add a comment noting manual conversion needed
14523        let supports_connect_by = matches!(
14524            self.config.dialect,
14525            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
14526        );
14527
14528        if !supports_connect_by && self.config.dialect.is_some() {
14529            // Add comment for unsupported dialects
14530            if self.config.pretty {
14531                self.write_newline();
14532            } else {
14533                self.write_space();
14534            }
14535            self.write("/* CONNECT BY requires manual conversion to recursive CTE */");
14536        }
14537
14538        // Generate START WITH if present (before CONNECT BY)
14539        if let Some(start) = &connect.start {
14540            if self.config.pretty {
14541                self.write_newline();
14542            } else {
14543                self.write_space();
14544            }
14545            self.write_keyword("START WITH");
14546            self.write_space();
14547            self.generate_expression(start)?;
14548        }
14549
14550        // Generate CONNECT BY
14551        if self.config.pretty {
14552            self.write_newline();
14553        } else {
14554            self.write_space();
14555        }
14556        self.write_keyword("CONNECT BY");
14557        if connect.nocycle {
14558            self.write_space();
14559            self.write_keyword("NOCYCLE");
14560        }
14561        self.write_space();
14562        self.generate_expression(&connect.connect)?;
14563
14564        Ok(())
14565    }
14566
14567    /// Generate Connect expression (for Expression::Connect variant)
14568    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
14569        self.generate_connect(connect)
14570    }
14571
14572    /// Generate PRIOR expression
14573    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
14574        self.write_keyword("PRIOR");
14575        self.write_space();
14576        self.generate_expression(&prior.this)?;
14577        Ok(())
14578    }
14579
14580    /// Generate CONNECT_BY_ROOT function
14581    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
14582    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
14583        self.write_keyword("CONNECT_BY_ROOT");
14584        self.write_space();
14585        self.generate_expression(&cbr.this)?;
14586        Ok(())
14587    }
14588
14589    /// Generate MATCH_RECOGNIZE clause
14590    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
14591        use crate::dialects::DialectType;
14592
14593        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
14594        let supports_match_recognize = matches!(
14595            self.config.dialect,
14596            Some(DialectType::Oracle)
14597                | Some(DialectType::Snowflake)
14598                | Some(DialectType::Presto)
14599                | Some(DialectType::Trino)
14600        );
14601
14602        // Generate the source table first
14603        if let Some(source) = &mr.this {
14604            self.generate_expression(source)?;
14605        }
14606
14607        if !supports_match_recognize {
14608            self.write("/* MATCH_RECOGNIZE not supported in this dialect */");
14609            return Ok(());
14610        }
14611
14612        // In pretty mode, MATCH_RECOGNIZE should be on a new line
14613        if self.config.pretty {
14614            self.write_newline();
14615        } else {
14616            self.write_space();
14617        }
14618
14619        self.write_keyword("MATCH_RECOGNIZE");
14620        self.write(" (");
14621
14622        if self.config.pretty {
14623            self.indent_level += 1;
14624        }
14625
14626        let mut needs_separator = false;
14627
14628        // PARTITION BY
14629        if let Some(partition_by) = &mr.partition_by {
14630            if !partition_by.is_empty() {
14631                if self.config.pretty {
14632                    self.write_newline();
14633                    self.write_indent();
14634                }
14635                self.write_keyword("PARTITION BY");
14636                self.write_space();
14637                for (i, expr) in partition_by.iter().enumerate() {
14638                    if i > 0 {
14639                        self.write(", ");
14640                    }
14641                    self.generate_expression(expr)?;
14642                }
14643                needs_separator = true;
14644            }
14645        }
14646
14647        // ORDER BY
14648        if let Some(order_by) = &mr.order_by {
14649            if !order_by.is_empty() {
14650                if needs_separator {
14651                    if self.config.pretty {
14652                        self.write_newline();
14653                        self.write_indent();
14654                    } else {
14655                        self.write_space();
14656                    }
14657                } else if self.config.pretty {
14658                    self.write_newline();
14659                    self.write_indent();
14660                }
14661                self.write_keyword("ORDER BY");
14662                // In pretty mode, put each ORDER BY column on a new indented line
14663                if self.config.pretty {
14664                    self.indent_level += 1;
14665                    for (i, ordered) in order_by.iter().enumerate() {
14666                        if i > 0 {
14667                            self.write(",");
14668                        }
14669                        self.write_newline();
14670                        self.write_indent();
14671                        self.generate_ordered(ordered)?;
14672                    }
14673                    self.indent_level -= 1;
14674                } else {
14675                    self.write_space();
14676                    for (i, ordered) in order_by.iter().enumerate() {
14677                        if i > 0 {
14678                            self.write(", ");
14679                        }
14680                        self.generate_ordered(ordered)?;
14681                    }
14682                }
14683                needs_separator = true;
14684            }
14685        }
14686
14687        // MEASURES
14688        if let Some(measures) = &mr.measures {
14689            if !measures.is_empty() {
14690                if needs_separator {
14691                    if self.config.pretty {
14692                        self.write_newline();
14693                        self.write_indent();
14694                    } else {
14695                        self.write_space();
14696                    }
14697                } else if self.config.pretty {
14698                    self.write_newline();
14699                    self.write_indent();
14700                }
14701                self.write_keyword("MEASURES");
14702                // In pretty mode, put each MEASURE on a new indented line
14703                if self.config.pretty {
14704                    self.indent_level += 1;
14705                    for (i, measure) in measures.iter().enumerate() {
14706                        if i > 0 {
14707                            self.write(",");
14708                        }
14709                        self.write_newline();
14710                        self.write_indent();
14711                        // Handle RUNNING/FINAL prefix
14712                        if let Some(semantics) = &measure.window_frame {
14713                            match semantics {
14714                                MatchRecognizeSemantics::Running => {
14715                                    self.write_keyword("RUNNING");
14716                                    self.write_space();
14717                                }
14718                                MatchRecognizeSemantics::Final => {
14719                                    self.write_keyword("FINAL");
14720                                    self.write_space();
14721                                }
14722                            }
14723                        }
14724                        self.generate_expression(&measure.this)?;
14725                    }
14726                    self.indent_level -= 1;
14727                } else {
14728                    self.write_space();
14729                    for (i, measure) in measures.iter().enumerate() {
14730                        if i > 0 {
14731                            self.write(", ");
14732                        }
14733                        // Handle RUNNING/FINAL prefix
14734                        if let Some(semantics) = &measure.window_frame {
14735                            match semantics {
14736                                MatchRecognizeSemantics::Running => {
14737                                    self.write_keyword("RUNNING");
14738                                    self.write_space();
14739                                }
14740                                MatchRecognizeSemantics::Final => {
14741                                    self.write_keyword("FINAL");
14742                                    self.write_space();
14743                                }
14744                            }
14745                        }
14746                        self.generate_expression(&measure.this)?;
14747                    }
14748                }
14749                needs_separator = true;
14750            }
14751        }
14752
14753        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
14754        if let Some(rows) = &mr.rows {
14755            if needs_separator {
14756                if self.config.pretty {
14757                    self.write_newline();
14758                    self.write_indent();
14759                } else {
14760                    self.write_space();
14761                }
14762            } else if self.config.pretty {
14763                self.write_newline();
14764                self.write_indent();
14765            }
14766            match rows {
14767                MatchRecognizeRows::OneRowPerMatch => {
14768                    self.write_keyword("ONE ROW PER MATCH");
14769                }
14770                MatchRecognizeRows::AllRowsPerMatch => {
14771                    self.write_keyword("ALL ROWS PER MATCH");
14772                }
14773                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
14774                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
14775                }
14776                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
14777                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
14778                }
14779                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
14780                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
14781                }
14782            }
14783            needs_separator = true;
14784        }
14785
14786        // AFTER MATCH SKIP
14787        if let Some(after) = &mr.after {
14788            if needs_separator {
14789                if self.config.pretty {
14790                    self.write_newline();
14791                    self.write_indent();
14792                } else {
14793                    self.write_space();
14794                }
14795            } else if self.config.pretty {
14796                self.write_newline();
14797                self.write_indent();
14798            }
14799            match after {
14800                MatchRecognizeAfter::PastLastRow => {
14801                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
14802                }
14803                MatchRecognizeAfter::ToNextRow => {
14804                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
14805                }
14806                MatchRecognizeAfter::ToFirst(ident) => {
14807                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
14808                    self.write_space();
14809                    self.generate_identifier(ident)?;
14810                }
14811                MatchRecognizeAfter::ToLast(ident) => {
14812                    self.write_keyword("AFTER MATCH SKIP TO LAST");
14813                    self.write_space();
14814                    self.generate_identifier(ident)?;
14815                }
14816            }
14817            needs_separator = true;
14818        }
14819
14820        // PATTERN
14821        if let Some(pattern) = &mr.pattern {
14822            if needs_separator {
14823                if self.config.pretty {
14824                    self.write_newline();
14825                    self.write_indent();
14826                } else {
14827                    self.write_space();
14828                }
14829            } else if self.config.pretty {
14830                self.write_newline();
14831                self.write_indent();
14832            }
14833            self.write_keyword("PATTERN");
14834            self.write_space();
14835            self.write("(");
14836            self.write(pattern);
14837            self.write(")");
14838            needs_separator = true;
14839        }
14840
14841        // DEFINE
14842        if let Some(define) = &mr.define {
14843            if !define.is_empty() {
14844                if needs_separator {
14845                    if self.config.pretty {
14846                        self.write_newline();
14847                        self.write_indent();
14848                    } else {
14849                        self.write_space();
14850                    }
14851                } else if self.config.pretty {
14852                    self.write_newline();
14853                    self.write_indent();
14854                }
14855                self.write_keyword("DEFINE");
14856                // In pretty mode, put each DEFINE on a new indented line
14857                if self.config.pretty {
14858                    self.indent_level += 1;
14859                    for (i, (name, expr)) in define.iter().enumerate() {
14860                        if i > 0 {
14861                            self.write(",");
14862                        }
14863                        self.write_newline();
14864                        self.write_indent();
14865                        self.generate_identifier(name)?;
14866                        self.write(" AS ");
14867                        self.generate_expression(expr)?;
14868                    }
14869                    self.indent_level -= 1;
14870                } else {
14871                    self.write_space();
14872                    for (i, (name, expr)) in define.iter().enumerate() {
14873                        if i > 0 {
14874                            self.write(", ");
14875                        }
14876                        self.generate_identifier(name)?;
14877                        self.write(" AS ");
14878                        self.generate_expression(expr)?;
14879                    }
14880                }
14881            }
14882        }
14883
14884        if self.config.pretty {
14885            self.indent_level -= 1;
14886            self.write_newline();
14887        }
14888        self.write(")");
14889
14890        // Alias - only include AS if it was explicitly present in the input
14891        if let Some(alias) = &mr.alias {
14892            self.write(" ");
14893            if mr.alias_explicit_as {
14894                self.write_keyword("AS");
14895                self.write(" ");
14896            }
14897            self.generate_identifier(alias)?;
14898        }
14899
14900        Ok(())
14901    }
14902
14903    /// Generate a query hint /*+ ... */
14904    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
14905        use crate::dialects::DialectType;
14906
14907        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
14908        let supports_hints = matches!(
14909            self.config.dialect,
14910            None |  // No dialect = preserve everything
14911            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
14912            Some(DialectType::Spark) | Some(DialectType::Hive) |
14913            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
14914        );
14915
14916        if !supports_hints || hint.expressions.is_empty() {
14917            return Ok(());
14918        }
14919
14920        // First, expand raw hint text into individual hint strings
14921        // This handles the case where the parser stored multiple hints as a single raw string
14922        let mut hint_strings: Vec<String> = Vec::new();
14923        for expr in &hint.expressions {
14924            match expr {
14925                HintExpression::Raw(text) => {
14926                    // Parse raw hint text into individual hint function calls
14927                    let parsed = self.parse_raw_hint_text(text);
14928                    hint_strings.extend(parsed);
14929                }
14930                _ => {
14931                    hint_strings.push(self.hint_expression_to_string(expr)?);
14932                }
14933            }
14934        }
14935
14936        // In pretty mode with multiple hints, always use multiline format
14937        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
14938        // always joins with newlines in pretty mode
14939        let use_multiline = self.config.pretty && hint_strings.len() > 1;
14940
14941        if use_multiline {
14942            // Pretty print with each hint on its own line
14943            self.write(" /*+ ");
14944            for (i, hint_str) in hint_strings.iter().enumerate() {
14945                if i > 0 {
14946                    self.write_newline();
14947                    self.write("  "); // 2-space indent within hint block
14948                }
14949                self.write(hint_str);
14950            }
14951            self.write(" */");
14952        } else {
14953            // Single line format
14954            self.write(" /*+ ");
14955            let sep = match self.config.dialect {
14956                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
14957                _ => " ",
14958            };
14959            for (i, hint_str) in hint_strings.iter().enumerate() {
14960                if i > 0 {
14961                    self.write(sep);
14962                }
14963                self.write(hint_str);
14964            }
14965            self.write(" */");
14966        }
14967
14968        Ok(())
14969    }
14970
14971    /// Parse raw hint text into individual hint function calls
14972    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
14973    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
14974    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
14975        let mut results = Vec::new();
14976        let mut chars = text.chars().peekable();
14977        let mut current = String::new();
14978        let mut paren_depth = 0;
14979        let mut has_unparseable_content = false;
14980        let mut position_after_last_function = 0;
14981        let mut char_position = 0;
14982
14983        while let Some(c) = chars.next() {
14984            char_position += c.len_utf8();
14985            match c {
14986                '(' => {
14987                    paren_depth += 1;
14988                    current.push(c);
14989                }
14990                ')' => {
14991                    paren_depth -= 1;
14992                    current.push(c);
14993                    // When we close the outer parenthesis, we've completed a hint function
14994                    if paren_depth == 0 {
14995                        let trimmed = current.trim().to_string();
14996                        if !trimmed.is_empty() {
14997                            // Format this hint for pretty printing if needed
14998                            let formatted = self.format_hint_function(&trimmed);
14999                            results.push(formatted);
15000                        }
15001                        current.clear();
15002                        position_after_last_function = char_position;
15003                    }
15004                }
15005                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
15006                    // Space/comma/whitespace outside parentheses - skip
15007                }
15008                _ if paren_depth == 0 => {
15009                    // Character outside parentheses - accumulate for potential hint name
15010                    current.push(c);
15011                }
15012                _ => {
15013                    current.push(c);
15014                }
15015            }
15016        }
15017
15018        // Check if there's remaining text after the last function call
15019        let remaining_text = text[position_after_last_function..].trim();
15020        if !remaining_text.is_empty() {
15021            // Check if it looks like valid hint function names
15022            // Valid hint identifiers typically are uppercase alphanumeric with underscores
15023            // If we see multiple words without parens, it's likely unparseable
15024            let words: Vec<&str> = remaining_text.split_whitespace().collect();
15025            let looks_like_hint_functions = words.iter().all(|word| {
15026                // A valid hint name followed by opening paren, or a standalone uppercase identifier
15027                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
15028            });
15029
15030            if !looks_like_hint_functions && words.len() > 1 {
15031                has_unparseable_content = true;
15032            }
15033        }
15034
15035        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
15036        if has_unparseable_content {
15037            return vec![text.trim().to_string()];
15038        }
15039
15040        // If we couldn't parse anything, return the original text as a single hint
15041        if results.is_empty() {
15042            results.push(text.trim().to_string());
15043        }
15044
15045        results
15046    }
15047
15048    /// Format a hint function for pretty printing
15049    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
15050    fn format_hint_function(&self, hint: &str) -> String {
15051        if !self.config.pretty {
15052            return hint.to_string();
15053        }
15054
15055        // Try to parse NAME(args) pattern
15056        if let Some(paren_pos) = hint.find('(') {
15057            if hint.ends_with(')') {
15058                let name = &hint[..paren_pos];
15059                let args_str = &hint[paren_pos + 1..hint.len() - 1];
15060
15061                // Parse arguments (space-separated for Oracle hints)
15062                let args: Vec<&str> = args_str.split_whitespace().collect();
15063
15064                // Calculate total width of arguments
15065                let total_args_width: usize =
15066                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
15067
15068                // If too wide, format on multiple lines
15069                if total_args_width > self.config.max_text_width && !args.is_empty() {
15070                    let mut result = format!("{}(\n", name);
15071                    for arg in &args {
15072                        result.push_str("    "); // 4-space indent for args
15073                        result.push_str(arg);
15074                        result.push('\n');
15075                    }
15076                    result.push_str("  )"); // 2-space indent for closing paren
15077                    return result;
15078                }
15079            }
15080        }
15081
15082        hint.to_string()
15083    }
15084
15085    /// Convert a hint expression to a string, handling multiline formatting for long arguments
15086    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
15087        match expr {
15088            HintExpression::Function { name, args } => {
15089                // Generate each argument to a string
15090                let arg_strings: Vec<String> = args
15091                    .iter()
15092                    .map(|arg| {
15093                        let mut gen = Generator::with_config(self.config.clone());
15094                        gen.generate_expression(arg)?;
15095                        Ok(gen.output)
15096                    })
15097                    .collect::<Result<Vec<_>>>()?;
15098
15099                // Oracle hints use space-separated arguments, not comma-separated
15100                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
15101                    + arg_strings.len().saturating_sub(1); // spaces between args
15102
15103                // Check if function args need multiline formatting
15104                // Use too_wide check for argument formatting
15105                let args_multiline =
15106                    self.config.pretty && total_args_width > self.config.max_text_width;
15107
15108                if args_multiline && !arg_strings.is_empty() {
15109                    // Multiline format for long argument lists
15110                    let mut result = format!("{}(\n", name);
15111                    for arg_str in &arg_strings {
15112                        result.push_str("    "); // 4-space indent for args
15113                        result.push_str(arg_str);
15114                        result.push('\n');
15115                    }
15116                    result.push_str("  )"); // 2-space indent for closing paren
15117                    Ok(result)
15118                } else {
15119                    // Single line format with space-separated args (Oracle style)
15120                    let args_str = arg_strings.join(" ");
15121                    Ok(format!("{}({})", name, args_str))
15122                }
15123            }
15124            HintExpression::Identifier(name) => Ok(name.clone()),
15125            HintExpression::Raw(text) => {
15126                // For pretty printing, try to format the raw text
15127                if self.config.pretty {
15128                    Ok(self.format_hint_function(text))
15129                } else {
15130                    Ok(text.clone())
15131                }
15132            }
15133        }
15134    }
15135
15136    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
15137        // PostgreSQL ONLY modifier: prevents scanning child tables
15138        if table.only {
15139            self.write_keyword("ONLY");
15140            self.write_space();
15141        }
15142
15143        // Check for Snowflake IDENTIFIER() function
15144        if let Some(ref identifier_func) = table.identifier_func {
15145            self.generate_expression(identifier_func)?;
15146        } else {
15147            if let Some(catalog) = &table.catalog {
15148                self.generate_identifier(catalog)?;
15149                self.write(".");
15150            }
15151            if let Some(schema) = &table.schema {
15152                self.generate_identifier(schema)?;
15153                self.write(".");
15154            }
15155            self.generate_identifier(&table.name)?;
15156        }
15157
15158        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
15159        if let Some(changes) = &table.changes {
15160            self.write(" ");
15161            self.generate_changes(changes)?;
15162        }
15163
15164        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
15165        if !table.partitions.is_empty() {
15166            self.write_space();
15167            self.write_keyword("PARTITION");
15168            self.write("(");
15169            for (i, partition) in table.partitions.iter().enumerate() {
15170                if i > 0 {
15171                    self.write(", ");
15172                }
15173                self.generate_identifier(partition)?;
15174            }
15175            self.write(")");
15176        }
15177
15178        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
15179        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
15180        if table.changes.is_none() {
15181            if let Some(when) = &table.when {
15182                self.write_space();
15183                self.generate_historical_data(when)?;
15184            }
15185        }
15186
15187        // Output TSQL FOR SYSTEM_TIME temporal clause
15188        if let Some(ref system_time) = table.system_time {
15189            self.write_space();
15190            self.write(system_time);
15191        }
15192
15193        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
15194        if let Some(ref version) = table.version {
15195            self.write_space();
15196            self.generate_version(version)?;
15197        }
15198
15199        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
15200        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
15201        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
15202        let alias_post_tablesample = self.config.alias_post_tablesample;
15203
15204        if alias_post_tablesample {
15205            // TABLESAMPLE before alias (Oracle, Hive, Spark)
15206            self.generate_table_sample_clause(table)?;
15207        }
15208
15209        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
15210        // For SQLite, INDEXED BY hints come after the alias, so skip here
15211        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
15212            && table.hints.iter().any(|h| {
15213                if let Expression::Identifier(id) = h {
15214                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
15215                } else {
15216                    false
15217                }
15218            });
15219        if !table.hints.is_empty() && !is_sqlite_hint {
15220            for hint in &table.hints {
15221                self.write_space();
15222                self.generate_expression(hint)?;
15223            }
15224        }
15225
15226        if let Some(alias) = &table.alias {
15227            self.write_space();
15228            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
15229            // Generic mode and most dialects always use AS for table aliases
15230            let always_use_as = self.config.dialect.is_none()
15231                || matches!(
15232                    self.config.dialect,
15233                    Some(DialectType::Generic)
15234                        | Some(DialectType::PostgreSQL)
15235                        | Some(DialectType::Redshift)
15236                        | Some(DialectType::Snowflake)
15237                        | Some(DialectType::BigQuery)
15238                        | Some(DialectType::Presto)
15239                        | Some(DialectType::Trino)
15240                        | Some(DialectType::TSQL)
15241                        | Some(DialectType::Fabric)
15242                        | Some(DialectType::MySQL)
15243                        | Some(DialectType::Spark)
15244                        | Some(DialectType::Hive)
15245                        | Some(DialectType::SQLite)
15246                        | Some(DialectType::Drill)
15247                );
15248            let is_stage_ref = table.name.name.starts_with('@');
15249            // Oracle never uses AS for table aliases
15250            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
15251            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
15252                self.write_keyword("AS");
15253                self.write_space();
15254            }
15255            self.generate_identifier(alias)?;
15256
15257            // Output column aliases if present: AS t(c1, c2)
15258            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
15259            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
15260                self.write("(");
15261                for (i, col_alias) in table.column_aliases.iter().enumerate() {
15262                    if i > 0 {
15263                        self.write(", ");
15264                    }
15265                    self.generate_identifier(col_alias)?;
15266                }
15267                self.write(")");
15268            }
15269        }
15270
15271        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
15272        if !alias_post_tablesample {
15273            self.generate_table_sample_clause(table)?;
15274        }
15275
15276        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
15277        if is_sqlite_hint {
15278            for hint in &table.hints {
15279                self.write_space();
15280                self.generate_expression(hint)?;
15281            }
15282        }
15283
15284        // ClickHouse FINAL modifier
15285        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
15286            self.write_space();
15287            self.write_keyword("FINAL");
15288        }
15289
15290        // Output trailing comments
15291        for comment in &table.trailing_comments {
15292            self.write_space();
15293            self.write_formatted_comment(comment);
15294        }
15295
15296        Ok(())
15297    }
15298
15299    /// Helper to output TABLESAMPLE clause for a table reference
15300    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
15301        if let Some(ref ts) = table.table_sample {
15302            self.write_space();
15303            if ts.is_using_sample {
15304                self.write_keyword("USING SAMPLE");
15305            } else {
15306                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
15307                self.write_keyword(self.config.tablesample_keywords);
15308            }
15309            self.generate_sample_body(ts)?;
15310            // Seed for table-level sample - use dialect's configured keyword
15311            if let Some(ref seed) = ts.seed {
15312                self.write_space();
15313                self.write_keyword(self.config.tablesample_seed_keyword);
15314                self.write(" (");
15315                self.generate_expression(seed)?;
15316                self.write(")");
15317            }
15318        }
15319        Ok(())
15320    }
15321
15322    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
15323        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
15324        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
15325
15326        if sr.quoted {
15327            self.write("'");
15328        }
15329
15330        self.write(&sr.name);
15331        if let Some(path) = &sr.path {
15332            self.write(path);
15333        }
15334
15335        if sr.quoted {
15336            self.write("'");
15337        }
15338
15339        // Output FILE_FORMAT and PATTERN if present
15340        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
15341        if has_options {
15342            self.write(" (");
15343            let mut first = true;
15344
15345            if let Some(file_format) = &sr.file_format {
15346                if !first {
15347                    self.write(", ");
15348                }
15349                self.write_keyword("FILE_FORMAT");
15350                self.write(" => ");
15351                self.generate_expression(file_format)?;
15352                first = false;
15353            }
15354
15355            if let Some(pattern) = &sr.pattern {
15356                if !first {
15357                    self.write(", ");
15358                }
15359                self.write_keyword("PATTERN");
15360                self.write(" => '");
15361                self.write(pattern);
15362                self.write("'");
15363            }
15364
15365            self.write(")");
15366        }
15367        Ok(())
15368    }
15369
15370    fn generate_star(&mut self, star: &Star) -> Result<()> {
15371        use crate::dialects::DialectType;
15372
15373        if let Some(table) = &star.table {
15374            self.generate_identifier(table)?;
15375            self.write(".");
15376        }
15377        self.write("*");
15378
15379        // Generate EXCLUDE/EXCEPT clause based on dialect
15380        if let Some(except) = &star.except {
15381            if !except.is_empty() {
15382                self.write_space();
15383                // Use dialect-appropriate keyword
15384                match self.config.dialect {
15385                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
15386                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
15387                        self.write_keyword("EXCLUDE")
15388                    }
15389                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
15390                }
15391                self.write(" (");
15392                for (i, col) in except.iter().enumerate() {
15393                    if i > 0 {
15394                        self.write(", ");
15395                    }
15396                    self.generate_identifier(col)?;
15397                }
15398                self.write(")");
15399            }
15400        }
15401
15402        // Generate REPLACE clause
15403        if let Some(replace) = &star.replace {
15404            if !replace.is_empty() {
15405                self.write_space();
15406                self.write_keyword("REPLACE");
15407                self.write(" (");
15408                for (i, alias) in replace.iter().enumerate() {
15409                    if i > 0 {
15410                        self.write(", ");
15411                    }
15412                    self.generate_expression(&alias.this)?;
15413                    self.write_space();
15414                    self.write_keyword("AS");
15415                    self.write_space();
15416                    self.generate_identifier(&alias.alias)?;
15417                }
15418                self.write(")");
15419            }
15420        }
15421
15422        // Generate RENAME clause (Snowflake specific)
15423        if let Some(rename) = &star.rename {
15424            if !rename.is_empty() {
15425                self.write_space();
15426                self.write_keyword("RENAME");
15427                self.write(" (");
15428                for (i, (old_name, new_name)) in rename.iter().enumerate() {
15429                    if i > 0 {
15430                        self.write(", ");
15431                    }
15432                    self.generate_identifier(old_name)?;
15433                    self.write_space();
15434                    self.write_keyword("AS");
15435                    self.write_space();
15436                    self.generate_identifier(new_name)?;
15437                }
15438                self.write(")");
15439            }
15440        }
15441
15442        // Output trailing comments
15443        for comment in &star.trailing_comments {
15444            self.write_space();
15445            self.write_formatted_comment(comment);
15446        }
15447
15448        Ok(())
15449    }
15450
15451    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
15452    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
15453        self.write("{");
15454        match expr {
15455            Expression::Star(star) => {
15456                // Generate the star (table.* or just * with optional EXCLUDE)
15457                self.generate_star(star)?;
15458            }
15459            Expression::ILike(ilike) => {
15460                // {* ILIKE 'pattern'} syntax
15461                self.generate_expression(&ilike.left)?;
15462                self.write_space();
15463                self.write_keyword("ILIKE");
15464                self.write_space();
15465                self.generate_expression(&ilike.right)?;
15466            }
15467            _ => {
15468                self.generate_expression(expr)?;
15469            }
15470        }
15471        self.write("}");
15472        Ok(())
15473    }
15474
15475    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
15476        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
15477        // to avoid duplication (comments are captured as both Column.trailing_comments
15478        // and Alias.pre_alias_comments during parsing)
15479        match &alias.this {
15480            Expression::Column(col) => {
15481                // Generate column without trailing comments - they're in pre_alias_comments
15482                if let Some(table) = &col.table {
15483                    self.generate_identifier(table)?;
15484                    self.write(".");
15485                }
15486                self.generate_identifier(&col.name)?;
15487            }
15488            _ => {
15489                self.generate_expression(&alias.this)?;
15490            }
15491        }
15492
15493        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
15494        // moves pre-alias comments to after the alias. When there are also trailing_comments,
15495        // keep pre-alias comments in their original position (between expression and AS).
15496        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
15497            for comment in &alias.pre_alias_comments {
15498                self.write_space();
15499                self.write_formatted_comment(comment);
15500            }
15501        }
15502
15503        use crate::dialects::DialectType;
15504
15505        // Determine if we should skip AS keyword for table-valued function aliases
15506        // Oracle and some other dialects don't use AS for table aliases
15507        // Note: We specifically use TableFromRows here, NOT Function, because Function
15508        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
15509        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
15510        let is_table_source = matches!(
15511            &alias.this,
15512            Expression::JSONTable(_)
15513                | Expression::XMLTable(_)
15514                | Expression::TableFromRows(_)
15515                | Expression::Unnest(_)
15516                | Expression::MatchRecognize(_)
15517                | Expression::Select(_)
15518                | Expression::Subquery(_)
15519                | Expression::Paren(_)
15520        );
15521        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
15522        let skip_as = is_table_source && dialect_skips_table_alias_as;
15523
15524        self.write_space();
15525        if !skip_as {
15526            self.write_keyword("AS");
15527            self.write_space();
15528        }
15529
15530        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
15531        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
15532
15533        // Check if we have column aliases only (no table alias name)
15534        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
15535            // Generate AS (col1, col2, ...)
15536            self.write("(");
15537            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
15538                if i > 0 {
15539                    self.write(", ");
15540                }
15541                self.generate_alias_identifier(col_alias)?;
15542            }
15543            self.write(")");
15544        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
15545            // Generate AS alias(col1, col2, ...)
15546            self.generate_alias_identifier(&alias.alias)?;
15547            self.write("(");
15548            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
15549                if i > 0 {
15550                    self.write(", ");
15551                }
15552                self.generate_alias_identifier(col_alias)?;
15553            }
15554            self.write(")");
15555        } else {
15556            // Simple alias (or BigQuery without column aliases)
15557            self.generate_alias_identifier(&alias.alias)?;
15558        }
15559
15560        // Output trailing comments (comments after the alias)
15561        for comment in &alias.trailing_comments {
15562            self.write_space();
15563            self.write_formatted_comment(comment);
15564        }
15565
15566        // Output pre-alias comments: when there are no trailing_comments, sqlglot
15567        // moves pre-alias comments to after the alias. When there are trailing_comments,
15568        // the pre-alias comments were already lost (consumed as column trailing comments
15569        // that were then used as pre_alias_comments). We always emit them after alias.
15570        if alias.trailing_comments.is_empty() {
15571            for comment in &alias.pre_alias_comments {
15572                self.write_space();
15573                self.write_formatted_comment(comment);
15574            }
15575        }
15576
15577        Ok(())
15578    }
15579
15580    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
15581        use crate::dialects::DialectType;
15582
15583        // SingleStore uses :> syntax
15584        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
15585            self.generate_expression(&cast.this)?;
15586            self.write(" :> ");
15587            self.generate_data_type(&cast.to)?;
15588            return Ok(());
15589        }
15590
15591        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
15592        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
15593            let is_unknown_type = matches!(cast.to, DataType::Unknown)
15594                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
15595            if is_unknown_type {
15596                if let Some(format) = &cast.format {
15597                    self.write_keyword("CAST");
15598                    self.write("(");
15599                    self.generate_expression(&cast.this)?;
15600                    self.write_space();
15601                    self.write_keyword("AS");
15602                    self.write_space();
15603                    self.write_keyword("FORMAT");
15604                    self.write_space();
15605                    self.generate_expression(format)?;
15606                    self.write(")");
15607                    return Ok(());
15608                }
15609            }
15610        }
15611
15612        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
15613        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
15614        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
15615            if let Some(format) = &cast.format {
15616                // Check if target type is DATE or TIMESTAMP
15617                let is_date = matches!(cast.to, DataType::Date);
15618                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
15619
15620                if is_date || is_timestamp {
15621                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
15622                    self.write_keyword(func_name);
15623                    self.write("(");
15624                    self.generate_expression(&cast.this)?;
15625                    self.write(", ");
15626
15627                    // Normalize format string for Oracle (HH -> HH12)
15628                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
15629                    if let Expression::Literal(Literal::String(fmt_str)) = format.as_ref() {
15630                        let normalized = self.normalize_oracle_format(fmt_str);
15631                        self.write("'");
15632                        self.write(&normalized);
15633                        self.write("'");
15634                    } else {
15635                        self.generate_expression(format)?;
15636                    }
15637
15638                    self.write(")");
15639                    return Ok(());
15640                }
15641            }
15642        }
15643
15644        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
15645        // This preserves sqlglot's typed inline array literal output.
15646        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
15647            if let Expression::Array(arr) = &cast.this {
15648                self.generate_data_type(&cast.to)?;
15649                // Output just the bracket content [values] without the ARRAY prefix
15650                self.write("[");
15651                for (i, expr) in arr.expressions.iter().enumerate() {
15652                    if i > 0 {
15653                        self.write(", ");
15654                    }
15655                    self.generate_expression(expr)?;
15656                }
15657                self.write("]");
15658                return Ok(());
15659            }
15660            if matches!(&cast.this, Expression::ArrayFunc(_)) {
15661                self.generate_data_type(&cast.to)?;
15662                self.generate_expression(&cast.this)?;
15663                return Ok(());
15664            }
15665        }
15666
15667        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
15668        // convert the inner Struct to ROW(values...) format
15669        if matches!(
15670            self.config.dialect,
15671            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
15672        ) {
15673            if let Expression::Struct(ref s) = cast.this {
15674                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
15675                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
15676                    self.write_keyword("CAST");
15677                    self.write("(");
15678                    self.generate_struct_as_row(s)?;
15679                    self.write_space();
15680                    self.write_keyword("AS");
15681                    self.write_space();
15682                    self.generate_data_type(&cast.to)?;
15683                    self.write(")");
15684                    return Ok(());
15685                }
15686            }
15687        }
15688
15689        // Determine if we should use :: syntax based on dialect
15690        // PostgreSQL prefers :: for identity, most others prefer CAST()
15691        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
15692
15693        if use_double_colon {
15694            // PostgreSQL :: syntax: expr::type
15695            self.generate_expression(&cast.this)?;
15696            self.write("::");
15697            self.generate_data_type(&cast.to)?;
15698        } else {
15699            // Standard CAST() syntax
15700            self.write_keyword("CAST");
15701            self.write("(");
15702            self.generate_expression(&cast.this)?;
15703            self.write_space();
15704            self.write_keyword("AS");
15705            self.write_space();
15706            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
15707            // This matches Python sqlglot's CAST_MAPPING behavior
15708            if matches!(
15709                self.config.dialect,
15710                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
15711            ) {
15712                match &cast.to {
15713                    DataType::Custom { ref name } => {
15714                        let upper = name.to_uppercase();
15715                        match upper.as_str() {
15716                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" | "LONGBLOB" | "MEDIUMBLOB"
15717                            | "TINYBLOB" => {
15718                                self.write_keyword("CHAR");
15719                            }
15720                            _ => {
15721                                self.generate_data_type(&cast.to)?;
15722                            }
15723                        }
15724                    }
15725                    DataType::VarChar { length, .. } => {
15726                        // MySQL CAST: VARCHAR -> CHAR
15727                        self.write_keyword("CHAR");
15728                        if let Some(n) = length {
15729                            self.write(&format!("({})", n));
15730                        }
15731                    }
15732                    DataType::Text => {
15733                        // MySQL CAST: TEXT -> CHAR
15734                        self.write_keyword("CHAR");
15735                    }
15736                    DataType::Timestamp {
15737                        precision,
15738                        timezone: false,
15739                    } => {
15740                        // MySQL CAST: TIMESTAMP -> DATETIME
15741                        self.write_keyword("DATETIME");
15742                        if let Some(p) = precision {
15743                            self.write(&format!("({})", p));
15744                        }
15745                    }
15746                    _ => {
15747                        self.generate_data_type(&cast.to)?;
15748                    }
15749                }
15750            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
15751                // Snowflake CAST: STRING -> VARCHAR
15752                match &cast.to {
15753                    DataType::String { length } => {
15754                        self.write_keyword("VARCHAR");
15755                        if let Some(n) = length {
15756                            self.write(&format!("({})", n));
15757                        }
15758                    }
15759                    _ => {
15760                        self.generate_data_type(&cast.to)?;
15761                    }
15762                }
15763            } else {
15764                self.generate_data_type(&cast.to)?;
15765            }
15766
15767            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
15768            if let Some(default) = &cast.default {
15769                self.write_space();
15770                self.write_keyword("DEFAULT");
15771                self.write_space();
15772                self.generate_expression(default)?;
15773                self.write_space();
15774                self.write_keyword("ON");
15775                self.write_space();
15776                self.write_keyword("CONVERSION");
15777                self.write_space();
15778                self.write_keyword("ERROR");
15779            }
15780
15781            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
15782            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
15783            if let Some(format) = &cast.format {
15784                // Check if Oracle dialect - use comma syntax
15785                if matches!(
15786                    self.config.dialect,
15787                    Some(crate::dialects::DialectType::Oracle)
15788                ) {
15789                    self.write(", ");
15790                } else {
15791                    self.write_space();
15792                    self.write_keyword("FORMAT");
15793                    self.write_space();
15794                }
15795                self.generate_expression(format)?;
15796            }
15797
15798            self.write(")");
15799            // Output trailing comments
15800            for comment in &cast.trailing_comments {
15801                self.write_space();
15802                self.write_formatted_comment(comment);
15803            }
15804        }
15805        Ok(())
15806    }
15807
15808    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
15809    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
15810    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
15811        self.write_keyword("ROW");
15812        self.write("(");
15813        for (i, (_, expr)) in s.fields.iter().enumerate() {
15814            if i > 0 {
15815                self.write(", ");
15816            }
15817            // Recursively convert inner Struct to ROW format
15818            if let Expression::Struct(ref inner_s) = expr {
15819                self.generate_struct_as_row(inner_s)?;
15820            } else {
15821                self.generate_expression(expr)?;
15822            }
15823        }
15824        self.write(")");
15825        Ok(())
15826    }
15827
15828    /// Normalize Oracle date/time format strings
15829    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
15830    fn normalize_oracle_format(&self, format: &str) -> String {
15831        // Replace standalone HH with HH12 (but not HH12 or HH24)
15832        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
15833        let mut result = String::new();
15834        let chars: Vec<char> = format.chars().collect();
15835        let mut i = 0;
15836
15837        while i < chars.len() {
15838            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
15839                // Check what follows HH
15840                if i + 2 < chars.len() {
15841                    let next = chars[i + 2];
15842                    if next == '1' || next == '2' {
15843                        // This is HH12 or HH24, keep as is
15844                        result.push('H');
15845                        result.push('H');
15846                        i += 2;
15847                        continue;
15848                    }
15849                }
15850                // Standalone HH -> HH12
15851                result.push_str("HH12");
15852                i += 2;
15853            } else {
15854                result.push(chars[i]);
15855                i += 1;
15856            }
15857        }
15858
15859        result
15860    }
15861
15862    /// Check if the current dialect prefers :: cast syntax
15863    /// Note: Python sqlglot normalizes all :: to CAST() for output, even for PostgreSQL
15864    /// So we return false for all dialects to match Python sqlglot's behavior
15865    fn dialect_prefers_double_colon(&self) -> bool {
15866        // Python sqlglot normalizes :: syntax to CAST() for all dialects
15867        // Even PostgreSQL outputs CAST() not ::
15868        false
15869    }
15870
15871    /// Generate MOD function - uses % operator for Snowflake/MySQL/Presto/Trino, MOD() for others
15872    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
15873        use crate::dialects::DialectType;
15874
15875        // Snowflake, MySQL, Presto, Trino, PostgreSQL, and DuckDB prefer x % y instead of MOD(x, y)
15876        let use_percent_operator = matches!(
15877            self.config.dialect,
15878            Some(DialectType::Snowflake)
15879                | Some(DialectType::MySQL)
15880                | Some(DialectType::Presto)
15881                | Some(DialectType::Trino)
15882                | Some(DialectType::PostgreSQL)
15883                | Some(DialectType::DuckDB)
15884                | Some(DialectType::Hive)
15885                | Some(DialectType::Spark)
15886                | Some(DialectType::Databricks)
15887                | Some(DialectType::Athena)
15888        );
15889
15890        if use_percent_operator {
15891            // Wrap complex expressions in parens to preserve precedence
15892            // Since % has higher precedence than +/-, we need parens for Add/Sub on either side
15893            let needs_paren = |e: &Expression| matches!(e, Expression::Add(_) | Expression::Sub(_));
15894            if needs_paren(&f.this) {
15895                self.write("(");
15896                self.generate_expression(&f.this)?;
15897                self.write(")");
15898            } else {
15899                self.generate_expression(&f.this)?;
15900            }
15901            self.write(" % ");
15902            if needs_paren(&f.expression) {
15903                self.write("(");
15904                self.generate_expression(&f.expression)?;
15905                self.write(")");
15906            } else {
15907                self.generate_expression(&f.expression)?;
15908            }
15909            Ok(())
15910        } else {
15911            self.generate_binary_func("MOD", &f.this, &f.expression)
15912        }
15913    }
15914
15915    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
15916    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
15917        use crate::dialects::DialectType;
15918
15919        // Snowflake normalizes IFNULL to COALESCE
15920        let func_name = match self.config.dialect {
15921            Some(DialectType::Snowflake) => "COALESCE",
15922            _ => "IFNULL",
15923        };
15924
15925        self.generate_binary_func(func_name, &f.this, &f.expression)
15926    }
15927
15928    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
15929    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
15930        // Use original function name if preserved (for identity tests)
15931        if let Some(ref original_name) = f.original_name {
15932            return self.generate_binary_func(original_name, &f.this, &f.expression);
15933        }
15934
15935        // Otherwise, use dialect-specific function names
15936        use crate::dialects::DialectType;
15937        let func_name = match self.config.dialect {
15938            Some(DialectType::Snowflake)
15939            | Some(DialectType::ClickHouse)
15940            | Some(DialectType::PostgreSQL)
15941            | Some(DialectType::Presto)
15942            | Some(DialectType::Trino)
15943            | Some(DialectType::Athena)
15944            | Some(DialectType::DuckDB)
15945            | Some(DialectType::BigQuery)
15946            | Some(DialectType::Spark)
15947            | Some(DialectType::Databricks)
15948            | Some(DialectType::Hive) => "COALESCE",
15949            Some(DialectType::MySQL)
15950            | Some(DialectType::Doris)
15951            | Some(DialectType::StarRocks)
15952            | Some(DialectType::SingleStore)
15953            | Some(DialectType::TiDB) => "IFNULL",
15954            _ => "NVL",
15955        };
15956
15957        self.generate_binary_func(func_name, &f.this, &f.expression)
15958    }
15959
15960    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
15961    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
15962        use crate::dialects::DialectType;
15963
15964        // Snowflake normalizes STDDEV_SAMP to STDDEV
15965        let func_name = match self.config.dialect {
15966            Some(DialectType::Snowflake) => "STDDEV",
15967            _ => "STDDEV_SAMP",
15968        };
15969
15970        self.generate_agg_func(func_name, f)
15971    }
15972
15973    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
15974        self.generate_expression(&coll.this)?;
15975        self.write_space();
15976        self.write_keyword("COLLATE");
15977        self.write_space();
15978        if coll.quoted {
15979            // Single-quoted string: COLLATE 'de_DE'
15980            self.write("'");
15981            self.write(&coll.collation);
15982            self.write("'");
15983        } else if coll.double_quoted {
15984            // Double-quoted identifier: COLLATE "de_DE"
15985            self.write("\"");
15986            self.write(&coll.collation);
15987            self.write("\"");
15988        } else {
15989            // Unquoted identifier: COLLATE de_DE
15990            self.write(&coll.collation);
15991        }
15992        Ok(())
15993    }
15994
15995    fn generate_case(&mut self, case: &Case) -> Result<()> {
15996        // In pretty mode, decide whether to expand based on total text width
15997        let multiline_case = if self.config.pretty {
15998            // Build the flat representation to check width
15999            let mut statements: Vec<String> = Vec::new();
16000            let operand_str = if let Some(operand) = &case.operand {
16001                let s = self.generate_to_string(operand)?;
16002                statements.push(format!("CASE {}", s));
16003                s
16004            } else {
16005                statements.push("CASE".to_string());
16006                String::new()
16007            };
16008            let _ = operand_str;
16009            for (condition, result) in &case.whens {
16010                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
16011                statements.push(format!("THEN {}", self.generate_to_string(result)?));
16012            }
16013            if let Some(else_) = &case.else_ {
16014                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
16015            }
16016            statements.push("END".to_string());
16017            self.too_wide(&statements)
16018        } else {
16019            false
16020        };
16021
16022        self.write_keyword("CASE");
16023        if let Some(operand) = &case.operand {
16024            self.write_space();
16025            self.generate_expression(operand)?;
16026        }
16027        if multiline_case {
16028            self.indent_level += 1;
16029        }
16030        for (condition, result) in &case.whens {
16031            if multiline_case {
16032                self.write_newline();
16033                self.write_indent();
16034            } else {
16035                self.write_space();
16036            }
16037            self.write_keyword("WHEN");
16038            self.write_space();
16039            self.generate_expression(condition)?;
16040            if multiline_case {
16041                self.write_newline();
16042                self.write_indent();
16043            } else {
16044                self.write_space();
16045            }
16046            self.write_keyword("THEN");
16047            self.write_space();
16048            self.generate_expression(result)?;
16049        }
16050        if let Some(else_) = &case.else_ {
16051            if multiline_case {
16052                self.write_newline();
16053                self.write_indent();
16054            } else {
16055                self.write_space();
16056            }
16057            self.write_keyword("ELSE");
16058            self.write_space();
16059            self.generate_expression(else_)?;
16060        }
16061        if multiline_case {
16062            self.indent_level -= 1;
16063            self.write_newline();
16064            self.write_indent();
16065        } else {
16066            self.write_space();
16067        }
16068        self.write_keyword("END");
16069        // Emit any comments that were attached to the CASE keyword
16070        for comment in &case.comments {
16071            self.write(" ");
16072            self.write_formatted_comment(comment);
16073        }
16074        Ok(())
16075    }
16076
16077    fn generate_function(&mut self, func: &Function) -> Result<()> {
16078        // Normalize function name based on dialect settings
16079        let normalized_name = self.normalize_func_name(&func.name);
16080        let upper_name = func.name.to_uppercase();
16081
16082        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
16083        if matches!(self.config.dialect, Some(DialectType::DuckDB))
16084            && upper_name == "ARRAY_CONSTRUCT_COMPACT"
16085        {
16086            self.write("LIST_FILTER(");
16087            self.write("[");
16088            for (i, arg) in func.args.iter().enumerate() {
16089                if i > 0 {
16090                    self.write(", ");
16091                }
16092                self.generate_expression(arg)?;
16093            }
16094            self.write("], _u -> NOT _u IS NULL)");
16095            return Ok(());
16096        }
16097
16098        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
16099        if upper_name == "STRUCT"
16100            && !matches!(
16101                self.config.dialect,
16102                Some(DialectType::BigQuery)
16103                    | Some(DialectType::Spark)
16104                    | Some(DialectType::Databricks)
16105                    | Some(DialectType::Hive)
16106                    | None
16107            )
16108        {
16109            return self.generate_struct_function_cross_dialect(func);
16110        }
16111
16112        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
16113        // This is an internal marker function for ::? JSON path syntax
16114        if upper_name == "__SS_JSON_PATH_QMARK__" && func.args.len() == 2 {
16115            self.generate_expression(&func.args[0])?;
16116            self.write("::?");
16117            // Extract the key from the string literal
16118            if let Expression::Literal(crate::expressions::Literal::String(key)) = &func.args[1] {
16119                self.write(key);
16120            } else {
16121                self.generate_expression(&func.args[1])?;
16122            }
16123            return Ok(());
16124        }
16125
16126        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
16127        if upper_name == "__PG_BITWISE_XOR__" && func.args.len() == 2 {
16128            self.generate_expression(&func.args[0])?;
16129            self.write(" # ");
16130            self.generate_expression(&func.args[1])?;
16131            return Ok(());
16132        }
16133
16134        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
16135        if matches!(
16136            self.config.dialect,
16137            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
16138        ) && upper_name == "TRY"
16139            && func.args.len() == 1
16140        {
16141            self.generate_expression(&func.args[0])?;
16142            return Ok(());
16143        }
16144
16145        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
16146        if self.config.dialect == Some(DialectType::ClickHouse)
16147            && upper_name == "TOSTARTOFDAY"
16148            && func.args.len() == 1
16149        {
16150            self.write("dateTrunc('DAY', ");
16151            self.generate_expression(&func.args[0])?;
16152            self.write(")");
16153            return Ok(());
16154        }
16155
16156        // Redshift: CONCAT(a, b, ...) -> a || b || ...
16157        if self.config.dialect == Some(DialectType::Redshift)
16158            && upper_name == "CONCAT"
16159            && func.args.len() >= 2
16160        {
16161            for (i, arg) in func.args.iter().enumerate() {
16162                if i > 0 {
16163                    self.write(" || ");
16164                }
16165                self.generate_expression(arg)?;
16166            }
16167            return Ok(());
16168        }
16169
16170        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
16171        if self.config.dialect == Some(DialectType::Redshift)
16172            && upper_name == "CONCAT_WS"
16173            && func.args.len() >= 2
16174        {
16175            let sep = &func.args[0];
16176            for (i, arg) in func.args.iter().skip(1).enumerate() {
16177                if i > 0 {
16178                    self.write(" || ");
16179                    self.generate_expression(sep)?;
16180                    self.write(" || ");
16181                }
16182                self.generate_expression(arg)?;
16183            }
16184            return Ok(());
16185        }
16186
16187        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
16188        // Unit should be unquoted uppercase identifier
16189        if self.config.dialect == Some(DialectType::Redshift)
16190            && (upper_name == "DATEDIFF" || upper_name == "DATE_DIFF")
16191            && func.args.len() == 3
16192        {
16193            self.write_keyword("DATEDIFF");
16194            self.write("(");
16195            // First arg is unit - normalize to unquoted uppercase
16196            self.write_redshift_date_part(&func.args[0]);
16197            self.write(", ");
16198            self.generate_expression(&func.args[1])?;
16199            self.write(", ");
16200            self.generate_expression(&func.args[2])?;
16201            self.write(")");
16202            return Ok(());
16203        }
16204
16205        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
16206        // Unit should be unquoted uppercase identifier
16207        if self.config.dialect == Some(DialectType::Redshift)
16208            && (upper_name == "DATEADD" || upper_name == "DATE_ADD")
16209            && func.args.len() == 3
16210        {
16211            self.write_keyword("DATEADD");
16212            self.write("(");
16213            // First arg is unit - normalize to unquoted uppercase
16214            self.write_redshift_date_part(&func.args[0]);
16215            self.write(", ");
16216            self.generate_expression(&func.args[1])?;
16217            self.write(", ");
16218            self.generate_expression(&func.args[2])?;
16219            self.write(")");
16220            return Ok(());
16221        }
16222
16223        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function (dropping args)
16224        if upper_name == "UUID_STRING"
16225            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
16226        {
16227            let func_name = match self.config.dialect {
16228                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
16229                Some(DialectType::BigQuery) => "GENERATE_UUID",
16230                _ => "UUID",
16231            };
16232            self.write_keyword(func_name);
16233            self.write("()");
16234            return Ok(());
16235        }
16236
16237        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
16238        // Unit should be quoted uppercase string
16239        if self.config.dialect == Some(DialectType::Redshift)
16240            && upper_name == "DATE_TRUNC"
16241            && func.args.len() == 2
16242        {
16243            self.write_keyword("DATE_TRUNC");
16244            self.write("(");
16245            // First arg is unit - normalize to quoted uppercase
16246            self.write_redshift_date_part_quoted(&func.args[0]);
16247            self.write(", ");
16248            self.generate_expression(&func.args[1])?;
16249            self.write(")");
16250            return Ok(());
16251        }
16252
16253        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
16254        if matches!(
16255            self.config.dialect,
16256            Some(DialectType::TSQL) | Some(DialectType::Fabric)
16257        ) && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16258            && func.args.len() == 2
16259        {
16260            self.write_keyword("DATEPART");
16261            self.write("(");
16262            self.generate_expression(&func.args[0])?;
16263            self.write(", ");
16264            self.generate_expression(&func.args[1])?;
16265            self.write(")");
16266            return Ok(());
16267        }
16268
16269        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
16270        if matches!(
16271            self.config.dialect,
16272            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
16273        ) && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16274            && func.args.len() == 2
16275        {
16276            self.write_keyword("EXTRACT");
16277            self.write("(");
16278            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
16279            match &func.args[0] {
16280                Expression::Literal(crate::expressions::Literal::String(s)) => {
16281                    self.write(&s.to_lowercase());
16282                }
16283                _ => self.generate_expression(&func.args[0])?,
16284            }
16285            self.write_space();
16286            self.write_keyword("FROM");
16287            self.write_space();
16288            self.generate_expression(&func.args[1])?;
16289            self.write(")");
16290            return Ok(());
16291        }
16292
16293        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
16294        // Also DATE literals in Dremio should be CAST(...AS DATE)
16295        if self.config.dialect == Some(DialectType::Dremio)
16296            && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16297            && func.args.len() == 2
16298        {
16299            self.write_keyword("EXTRACT");
16300            self.write("(");
16301            self.generate_expression(&func.args[0])?;
16302            self.write_space();
16303            self.write_keyword("FROM");
16304            self.write_space();
16305            // For Dremio, DATE literals should become CAST('value' AS DATE)
16306            self.generate_dremio_date_expression(&func.args[1])?;
16307            self.write(")");
16308            return Ok(());
16309        }
16310
16311        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
16312        if self.config.dialect == Some(DialectType::Dremio)
16313            && upper_name == "CURRENT_DATE_UTC"
16314            && func.args.is_empty()
16315        {
16316            self.write_keyword("CURRENT_DATE_UTC");
16317            return Ok(());
16318        }
16319
16320        // Dremio: DATETYPE(year, month, day) transformation
16321        // - If all args are integer literals: DATE('YYYY-MM-DD')
16322        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
16323        if self.config.dialect == Some(DialectType::Dremio)
16324            && upper_name == "DATETYPE"
16325            && func.args.len() == 3
16326        {
16327            // Helper function to extract integer from number literal
16328            fn get_int_literal(expr: &Expression) -> Option<i64> {
16329                if let Expression::Literal(crate::expressions::Literal::Number(s)) = expr {
16330                    s.parse::<i64>().ok()
16331                } else {
16332                    None
16333                }
16334            }
16335
16336            // Check if all arguments are integer literals
16337            if let (Some(year), Some(month), Some(day)) = (
16338                get_int_literal(&func.args[0]),
16339                get_int_literal(&func.args[1]),
16340                get_int_literal(&func.args[2]),
16341            ) {
16342                // All are integer literals: DATE('YYYY-MM-DD')
16343                self.write_keyword("DATE");
16344                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
16345                return Ok(());
16346            }
16347
16348            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
16349            self.write_keyword("CAST");
16350            self.write("(");
16351            self.write_keyword("CONCAT");
16352            self.write("(");
16353            self.generate_expression(&func.args[0])?;
16354            self.write(", '-', ");
16355            self.generate_expression(&func.args[1])?;
16356            self.write(", '-', ");
16357            self.generate_expression(&func.args[2])?;
16358            self.write(")");
16359            self.write_space();
16360            self.write_keyword("AS");
16361            self.write_space();
16362            self.write_keyword("DATE");
16363            self.write(")");
16364            return Ok(());
16365        }
16366
16367        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
16368        // when it's not an integer literal
16369        let is_presto_like = matches!(
16370            self.config.dialect,
16371            Some(DialectType::Presto) | Some(DialectType::Trino)
16372        );
16373        if is_presto_like && upper_name == "DATE_ADD" && func.args.len() == 3 {
16374            self.write_keyword("DATE_ADD");
16375            self.write("(");
16376            // First arg: unit (pass through as-is, e.g., 'DAY')
16377            self.generate_expression(&func.args[0])?;
16378            self.write(", ");
16379            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
16380            let interval = &func.args[1];
16381            let needs_cast = !self.returns_integer_type(interval);
16382            if needs_cast {
16383                self.write_keyword("CAST");
16384                self.write("(");
16385            }
16386            self.generate_expression(interval)?;
16387            if needs_cast {
16388                self.write_space();
16389                self.write_keyword("AS");
16390                self.write_space();
16391                self.write_keyword("BIGINT");
16392                self.write(")");
16393            }
16394            self.write(", ");
16395            // Third arg: date
16396            self.generate_expression(&func.args[2])?;
16397            self.write(")");
16398            return Ok(());
16399        }
16400
16401        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
16402        let use_brackets = func.use_bracket_syntax;
16403
16404        // Special case: functions WITH ORDINALITY need special output order
16405        // Input: FUNC(args) WITH ORDINALITY
16406        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
16407        // Output must be: FUNC(args) WITH ORDINALITY
16408        let has_ordinality = upper_name.ends_with(" WITH ORDINALITY");
16409        let output_name = if has_ordinality {
16410            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
16411            self.normalize_func_name(base_name)
16412        } else {
16413            normalized_name.clone()
16414        };
16415
16416        // For qualified names (schema.function or object.method), preserve original case
16417        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
16418        if func.name.contains('.') && !has_ordinality {
16419            // Don't normalize qualified functions - preserve original case
16420            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
16421            if func.quoted {
16422                self.write("`");
16423                self.write(&func.name);
16424                self.write("`");
16425            } else {
16426                self.write(&func.name);
16427            }
16428        } else {
16429            self.write(&output_name);
16430        }
16431
16432        // If no_parens is true and there are no args, output just the function name
16433        // Unless the target dialect requires parens for this function
16434        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
16435            let needs_parens = match upper_name.as_str() {
16436                "CURRENT_USER" | "SESSION_USER" | "SYSTEM_USER" => matches!(
16437                    self.config.dialect,
16438                    Some(DialectType::Snowflake)
16439                        | Some(DialectType::Spark)
16440                        | Some(DialectType::Databricks)
16441                        | Some(DialectType::Hive)
16442                ),
16443                _ => false,
16444            };
16445            !needs_parens
16446        };
16447        if force_parens {
16448            // Output trailing comments
16449            for comment in &func.trailing_comments {
16450                self.write_space();
16451                self.write_formatted_comment(comment);
16452            }
16453            return Ok(());
16454        }
16455
16456        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
16457        if upper_name == "CUBE" || upper_name == "ROLLUP" || upper_name == "GROUPING SETS" {
16458            self.write(" (");
16459        } else if use_brackets {
16460            self.write("[");
16461        } else {
16462            self.write("(");
16463        }
16464        if func.distinct {
16465            self.write_keyword("DISTINCT");
16466            self.write_space();
16467        }
16468
16469        // Check if arguments should be split onto multiple lines (pretty + too wide)
16470        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
16471            && (upper_name == "TABLE" || upper_name == "FLATTEN");
16472        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
16473        let is_grouping_func =
16474            upper_name == "GROUPING SETS" || upper_name == "CUBE" || upper_name == "ROLLUP";
16475        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
16476            if is_grouping_func {
16477                true
16478            } else {
16479                // Pre-render arguments to check total width
16480                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
16481                for arg in &func.args {
16482                    let mut temp_gen = Generator::with_config(self.config.clone());
16483                    temp_gen.config.pretty = false; // Don't recurse into pretty
16484                    temp_gen.generate_expression(arg)?;
16485                    expr_strings.push(temp_gen.output);
16486                }
16487                self.too_wide(&expr_strings)
16488            }
16489        } else {
16490            false
16491        };
16492
16493        if should_split {
16494            // Split onto multiple lines
16495            self.write_newline();
16496            self.indent_level += 1;
16497            for (i, arg) in func.args.iter().enumerate() {
16498                self.write_indent();
16499                self.generate_expression(arg)?;
16500                if i + 1 < func.args.len() {
16501                    self.write(",");
16502                }
16503                self.write_newline();
16504            }
16505            self.indent_level -= 1;
16506            self.write_indent();
16507        } else {
16508            // All on one line
16509            for (i, arg) in func.args.iter().enumerate() {
16510                if i > 0 {
16511                    self.write(", ");
16512                }
16513                self.generate_expression(arg)?;
16514            }
16515        }
16516
16517        if use_brackets {
16518            self.write("]");
16519        } else {
16520            self.write(")");
16521        }
16522        // Append WITH ORDINALITY after closing paren for table-valued functions
16523        if has_ordinality {
16524            self.write_space();
16525            self.write_keyword("WITH ORDINALITY");
16526        }
16527        // Output trailing comments
16528        for comment in &func.trailing_comments {
16529            self.write_space();
16530            self.write_formatted_comment(comment);
16531        }
16532        Ok(())
16533    }
16534
16535    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
16536        // Normalize function name based on dialect settings
16537        let mut normalized_name = self.normalize_func_name(&func.name);
16538
16539        // Dialect-specific name mappings for aggregate functions
16540        let upper = normalized_name.to_uppercase();
16541        if upper == "MAX_BY" || upper == "MIN_BY" {
16542            let is_max = upper == "MAX_BY";
16543            match self.config.dialect {
16544                Some(DialectType::ClickHouse) => {
16545                    normalized_name = if is_max {
16546                        "argMax".to_string()
16547                    } else {
16548                        "argMin".to_string()
16549                    };
16550                }
16551                Some(DialectType::DuckDB) => {
16552                    normalized_name = if is_max {
16553                        "ARG_MAX".to_string()
16554                    } else {
16555                        "ARG_MIN".to_string()
16556                    };
16557                }
16558                _ => {}
16559            }
16560        }
16561        self.write(&normalized_name);
16562        self.write("(");
16563        if func.distinct {
16564            self.write_keyword("DISTINCT");
16565            self.write_space();
16566        }
16567
16568        // Check if we need to transform multi-arg COUNT DISTINCT
16569        // When dialect doesn't support multi_arg_distinct, transform:
16570        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
16571        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
16572        let needs_multi_arg_transform =
16573            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
16574
16575        if needs_multi_arg_transform {
16576            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
16577            self.write_keyword("CASE");
16578            for arg in &func.args {
16579                self.write_space();
16580                self.write_keyword("WHEN");
16581                self.write_space();
16582                self.generate_expression(arg)?;
16583                self.write_space();
16584                self.write_keyword("IS NULL THEN NULL");
16585            }
16586            self.write_space();
16587            self.write_keyword("ELSE");
16588            self.write(" (");
16589            for (i, arg) in func.args.iter().enumerate() {
16590                if i > 0 {
16591                    self.write(", ");
16592                }
16593                self.generate_expression(arg)?;
16594            }
16595            self.write(")");
16596            self.write_space();
16597            self.write_keyword("END");
16598        } else {
16599            for (i, arg) in func.args.iter().enumerate() {
16600                if i > 0 {
16601                    self.write(", ");
16602                }
16603                self.generate_expression(arg)?;
16604            }
16605        }
16606
16607        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
16608        if self.config.ignore_nulls_in_func
16609            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
16610        {
16611            if let Some(ignore) = func.ignore_nulls {
16612                self.write_space();
16613                if ignore {
16614                    self.write_keyword("IGNORE NULLS");
16615                } else {
16616                    self.write_keyword("RESPECT NULLS");
16617                }
16618            }
16619        }
16620
16621        // ORDER BY inside aggregate
16622        if !func.order_by.is_empty() {
16623            self.write_space();
16624            self.write_keyword("ORDER BY");
16625            self.write_space();
16626            for (i, ord) in func.order_by.iter().enumerate() {
16627                if i > 0 {
16628                    self.write(", ");
16629                }
16630                self.generate_ordered(ord)?;
16631            }
16632        }
16633
16634        // LIMIT inside aggregate
16635        if let Some(limit) = &func.limit {
16636            self.write_space();
16637            self.write_keyword("LIMIT");
16638            self.write_space();
16639            // Check if this is a Tuple representing LIMIT offset, count
16640            if let Expression::Tuple(t) = limit.as_ref() {
16641                if t.expressions.len() == 2 {
16642                    self.generate_expression(&t.expressions[0])?;
16643                    self.write(", ");
16644                    self.generate_expression(&t.expressions[1])?;
16645                } else {
16646                    self.generate_expression(limit)?;
16647                }
16648            } else {
16649                self.generate_expression(limit)?;
16650            }
16651        }
16652
16653        self.write(")");
16654
16655        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
16656        if !self.config.ignore_nulls_in_func
16657            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
16658        {
16659            if let Some(ignore) = func.ignore_nulls {
16660                self.write_space();
16661                if ignore {
16662                    self.write_keyword("IGNORE NULLS");
16663                } else {
16664                    self.write_keyword("RESPECT NULLS");
16665                }
16666            }
16667        }
16668
16669        if let Some(filter) = &func.filter {
16670            self.write_space();
16671            self.write_keyword("FILTER");
16672            self.write("(");
16673            self.write_keyword("WHERE");
16674            self.write_space();
16675            self.generate_expression(filter)?;
16676            self.write(")");
16677        }
16678
16679        Ok(())
16680    }
16681
16682    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
16683        self.generate_expression(&wf.this)?;
16684
16685        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
16686        if let Some(keep) = &wf.keep {
16687            self.write_space();
16688            self.write_keyword("KEEP");
16689            self.write(" (");
16690            self.write_keyword("DENSE_RANK");
16691            self.write_space();
16692            if keep.first {
16693                self.write_keyword("FIRST");
16694            } else {
16695                self.write_keyword("LAST");
16696            }
16697            self.write_space();
16698            self.write_keyword("ORDER BY");
16699            self.write_space();
16700            for (i, ord) in keep.order_by.iter().enumerate() {
16701                if i > 0 {
16702                    self.write(", ");
16703                }
16704                self.generate_ordered(ord)?;
16705            }
16706            self.write(")");
16707        }
16708
16709        // Check if there's any OVER clause content
16710        let has_over = !wf.over.partition_by.is_empty()
16711            || !wf.over.order_by.is_empty()
16712            || wf.over.frame.is_some()
16713            || wf.over.window_name.is_some();
16714
16715        // Only output OVER if there's actual window specification (not just KEEP alone)
16716        if has_over {
16717            self.write_space();
16718            self.write_keyword("OVER");
16719
16720            // Check if this is just a bare named window reference (no parens needed)
16721            let has_specs = !wf.over.partition_by.is_empty()
16722                || !wf.over.order_by.is_empty()
16723                || wf.over.frame.is_some();
16724
16725            if wf.over.window_name.is_some() && !has_specs {
16726                // OVER window_name (without parentheses)
16727                self.write_space();
16728                self.write(&wf.over.window_name.as_ref().unwrap().name);
16729            } else {
16730                // OVER (...) or OVER (window_name ...)
16731                self.write(" (");
16732                self.generate_over(&wf.over)?;
16733                self.write(")");
16734            }
16735        } else if wf.keep.is_none() {
16736            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
16737            self.write_space();
16738            self.write_keyword("OVER");
16739            self.write(" ()");
16740        }
16741
16742        Ok(())
16743    }
16744
16745    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
16746    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
16747        self.generate_expression(&wg.this)?;
16748        self.write_space();
16749        self.write_keyword("WITHIN GROUP");
16750        self.write(" (");
16751        self.write_keyword("ORDER BY");
16752        self.write_space();
16753        for (i, ord) in wg.order_by.iter().enumerate() {
16754            if i > 0 {
16755                self.write(", ");
16756            }
16757            self.generate_ordered(ord)?;
16758        }
16759        self.write(")");
16760        Ok(())
16761    }
16762
16763    /// Generate the contents of an OVER clause (without parentheses)
16764    fn generate_over(&mut self, over: &Over) -> Result<()> {
16765        let mut has_content = false;
16766
16767        // Named window reference
16768        if let Some(name) = &over.window_name {
16769            self.write(&name.name);
16770            has_content = true;
16771        }
16772
16773        // PARTITION BY
16774        if !over.partition_by.is_empty() {
16775            if has_content {
16776                self.write_space();
16777            }
16778            self.write_keyword("PARTITION BY");
16779            self.write_space();
16780            for (i, expr) in over.partition_by.iter().enumerate() {
16781                if i > 0 {
16782                    self.write(", ");
16783                }
16784                self.generate_expression(expr)?;
16785            }
16786            has_content = true;
16787        }
16788
16789        // ORDER BY
16790        if !over.order_by.is_empty() {
16791            if has_content {
16792                self.write_space();
16793            }
16794            self.write_keyword("ORDER BY");
16795            self.write_space();
16796            for (i, ordered) in over.order_by.iter().enumerate() {
16797                if i > 0 {
16798                    self.write(", ");
16799                }
16800                self.generate_ordered(ordered)?;
16801            }
16802            has_content = true;
16803        }
16804
16805        // Window frame
16806        if let Some(frame) = &over.frame {
16807            if has_content {
16808                self.write_space();
16809            }
16810            self.generate_window_frame(frame)?;
16811        }
16812
16813        Ok(())
16814    }
16815
16816    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
16817        // Exasol uses lowercase for frame kind (rows/range/groups)
16818        let lowercase_frame = self.config.lowercase_window_frame_keywords;
16819
16820        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
16821        if !lowercase_frame {
16822            if let Some(kind_text) = &frame.kind_text {
16823                self.write(kind_text);
16824            } else {
16825                match frame.kind {
16826                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
16827                    WindowFrameKind::Range => self.write_keyword("RANGE"),
16828                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
16829                }
16830            }
16831        } else {
16832            match frame.kind {
16833                WindowFrameKind::Rows => self.write("rows"),
16834                WindowFrameKind::Range => self.write("range"),
16835                WindowFrameKind::Groups => self.write("groups"),
16836            }
16837        }
16838
16839        // Use BETWEEN format only when there's an explicit end bound,
16840        // or when normalize_window_frame_between is enabled and the start is a directional bound
16841        self.write_space();
16842        let should_normalize = self.config.normalize_window_frame_between
16843            && frame.end.is_none()
16844            && matches!(
16845                frame.start,
16846                WindowFrameBound::Preceding(_)
16847                    | WindowFrameBound::Following(_)
16848                    | WindowFrameBound::UnboundedPreceding
16849                    | WindowFrameBound::UnboundedFollowing
16850            );
16851
16852        if let Some(end) = &frame.end {
16853            // BETWEEN format: RANGE BETWEEN start AND end
16854            self.write_keyword("BETWEEN");
16855            self.write_space();
16856            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16857            self.write_space();
16858            self.write_keyword("AND");
16859            self.write_space();
16860            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
16861        } else if should_normalize {
16862            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
16863            self.write_keyword("BETWEEN");
16864            self.write_space();
16865            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16866            self.write_space();
16867            self.write_keyword("AND");
16868            self.write_space();
16869            self.write_keyword("CURRENT ROW");
16870        } else {
16871            // Single bound format: RANGE CURRENT ROW
16872            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16873        }
16874
16875        // EXCLUDE clause
16876        if let Some(exclude) = &frame.exclude {
16877            self.write_space();
16878            self.write_keyword("EXCLUDE");
16879            self.write_space();
16880            match exclude {
16881                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
16882                WindowFrameExclude::Group => self.write_keyword("GROUP"),
16883                WindowFrameExclude::Ties => self.write_keyword("TIES"),
16884                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
16885            }
16886        }
16887
16888        Ok(())
16889    }
16890
16891    fn generate_window_frame_bound(
16892        &mut self,
16893        bound: &WindowFrameBound,
16894        side_text: Option<&str>,
16895    ) -> Result<()> {
16896        // Exasol uses lowercase for preceding/following
16897        let lowercase_frame = self.config.lowercase_window_frame_keywords;
16898
16899        match bound {
16900            WindowFrameBound::CurrentRow => {
16901                self.write_keyword("CURRENT ROW");
16902            }
16903            WindowFrameBound::UnboundedPreceding => {
16904                self.write_keyword("UNBOUNDED");
16905                self.write_space();
16906                if lowercase_frame {
16907                    self.write("preceding");
16908                } else if let Some(text) = side_text {
16909                    self.write(text);
16910                } else {
16911                    self.write_keyword("PRECEDING");
16912                }
16913            }
16914            WindowFrameBound::UnboundedFollowing => {
16915                self.write_keyword("UNBOUNDED");
16916                self.write_space();
16917                if lowercase_frame {
16918                    self.write("following");
16919                } else if let Some(text) = side_text {
16920                    self.write(text);
16921                } else {
16922                    self.write_keyword("FOLLOWING");
16923                }
16924            }
16925            WindowFrameBound::Preceding(expr) => {
16926                self.generate_expression(expr)?;
16927                self.write_space();
16928                if lowercase_frame {
16929                    self.write("preceding");
16930                } else if let Some(text) = side_text {
16931                    self.write(text);
16932                } else {
16933                    self.write_keyword("PRECEDING");
16934                }
16935            }
16936            WindowFrameBound::Following(expr) => {
16937                self.generate_expression(expr)?;
16938                self.write_space();
16939                if lowercase_frame {
16940                    self.write("following");
16941                } else if let Some(text) = side_text {
16942                    self.write(text);
16943                } else {
16944                    self.write_keyword("FOLLOWING");
16945                }
16946            }
16947            WindowFrameBound::BarePreceding => {
16948                if lowercase_frame {
16949                    self.write("preceding");
16950                } else if let Some(text) = side_text {
16951                    self.write(text);
16952                } else {
16953                    self.write_keyword("PRECEDING");
16954                }
16955            }
16956            WindowFrameBound::BareFollowing => {
16957                if lowercase_frame {
16958                    self.write("following");
16959                } else if let Some(text) = side_text {
16960                    self.write(text);
16961                } else {
16962                    self.write_keyword("FOLLOWING");
16963                }
16964            }
16965            WindowFrameBound::Value(expr) => {
16966                // Bare numeric bound without PRECEDING/FOLLOWING
16967                self.generate_expression(expr)?;
16968            }
16969        }
16970        Ok(())
16971    }
16972
16973    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
16974        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
16975        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
16976        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
16977            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
16978            && !matches!(&interval.this, Some(Expression::Literal(_)));
16979
16980        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
16981        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
16982        if self.config.single_string_interval {
16983            if let (
16984                Some(Expression::Literal(Literal::String(ref val))),
16985                Some(IntervalUnitSpec::Simple {
16986                    ref unit,
16987                    ref use_plural,
16988                }),
16989            ) = (&interval.this, &interval.unit)
16990            {
16991                self.write_keyword("INTERVAL");
16992                self.write_space();
16993                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
16994                let unit_str = self.interval_unit_str(unit, effective_plural);
16995                self.write("'");
16996                self.write(val);
16997                self.write(" ");
16998                self.write(&unit_str);
16999                self.write("'");
17000                return Ok(());
17001            }
17002        }
17003
17004        if !skip_interval_keyword {
17005            self.write_keyword("INTERVAL");
17006        }
17007
17008        // Generate value if present
17009        if let Some(ref value) = interval.this {
17010            if !skip_interval_keyword {
17011                self.write_space();
17012            }
17013            // If the value is a complex expression (not a literal/column/function call)
17014            // and there's a unit, wrap it in parentheses
17015            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
17016            let needs_parens = interval.unit.is_some()
17017                && matches!(
17018                    value,
17019                    Expression::Add(_)
17020                        | Expression::Sub(_)
17021                        | Expression::Mul(_)
17022                        | Expression::Div(_)
17023                        | Expression::Mod(_)
17024                        | Expression::BitwiseAnd(_)
17025                        | Expression::BitwiseOr(_)
17026                        | Expression::BitwiseXor(_)
17027                );
17028            if needs_parens {
17029                self.write("(");
17030            }
17031            self.generate_expression(value)?;
17032            if needs_parens {
17033                self.write(")");
17034            }
17035        }
17036
17037        // Generate unit if present
17038        if let Some(ref unit_spec) = interval.unit {
17039            self.write_space();
17040            self.write_interval_unit_spec(unit_spec)?;
17041        }
17042
17043        Ok(())
17044    }
17045
17046    /// Return the string representation of an interval unit
17047    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
17048        match (unit, use_plural) {
17049            (IntervalUnit::Year, false) => "YEAR",
17050            (IntervalUnit::Year, true) => "YEARS",
17051            (IntervalUnit::Quarter, false) => "QUARTER",
17052            (IntervalUnit::Quarter, true) => "QUARTERS",
17053            (IntervalUnit::Month, false) => "MONTH",
17054            (IntervalUnit::Month, true) => "MONTHS",
17055            (IntervalUnit::Week, false) => "WEEK",
17056            (IntervalUnit::Week, true) => "WEEKS",
17057            (IntervalUnit::Day, false) => "DAY",
17058            (IntervalUnit::Day, true) => "DAYS",
17059            (IntervalUnit::Hour, false) => "HOUR",
17060            (IntervalUnit::Hour, true) => "HOURS",
17061            (IntervalUnit::Minute, false) => "MINUTE",
17062            (IntervalUnit::Minute, true) => "MINUTES",
17063            (IntervalUnit::Second, false) => "SECOND",
17064            (IntervalUnit::Second, true) => "SECONDS",
17065            (IntervalUnit::Millisecond, false) => "MILLISECOND",
17066            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
17067            (IntervalUnit::Microsecond, false) => "MICROSECOND",
17068            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
17069            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
17070            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
17071        }
17072    }
17073
17074    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
17075        match unit_spec {
17076            IntervalUnitSpec::Simple { unit, use_plural } => {
17077                // If dialect doesn't allow plural forms, force singular
17078                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
17079                self.write_simple_interval_unit(unit, effective_plural);
17080            }
17081            IntervalUnitSpec::Span(span) => {
17082                self.write_simple_interval_unit(&span.this, false);
17083                self.write_space();
17084                self.write_keyword("TO");
17085                self.write_space();
17086                self.write_simple_interval_unit(&span.expression, false);
17087            }
17088            IntervalUnitSpec::ExprSpan(span) => {
17089                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
17090                self.generate_expression(&span.this)?;
17091                self.write_space();
17092                self.write_keyword("TO");
17093                self.write_space();
17094                self.generate_expression(&span.expression)?;
17095            }
17096            IntervalUnitSpec::Expr(expr) => {
17097                self.generate_expression(expr)?;
17098            }
17099        }
17100        Ok(())
17101    }
17102
17103    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
17104        // Output interval unit, respecting plural preference
17105        match (unit, use_plural) {
17106            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
17107            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
17108            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
17109            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
17110            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
17111            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
17112            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
17113            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
17114            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
17115            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
17116            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
17117            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
17118            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
17119            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
17120            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
17121            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
17122            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
17123            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
17124            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
17125            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
17126            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
17127            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
17128        }
17129    }
17130
17131    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
17132    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
17133    fn write_redshift_date_part(&mut self, expr: &Expression) {
17134        let part_str = self.extract_date_part_string(expr);
17135        if let Some(part) = part_str {
17136            let normalized = self.normalize_date_part(&part);
17137            self.write_keyword(&normalized);
17138        } else {
17139            // If we can't extract a date part string, fall back to generating the expression
17140            let _ = self.generate_expression(expr);
17141        }
17142    }
17143
17144    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
17145    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
17146    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
17147        let part_str = self.extract_date_part_string(expr);
17148        if let Some(part) = part_str {
17149            let normalized = self.normalize_date_part(&part);
17150            self.write("'");
17151            self.write(&normalized);
17152            self.write("'");
17153        } else {
17154            // If we can't extract a date part string, fall back to generating the expression
17155            let _ = self.generate_expression(expr);
17156        }
17157    }
17158
17159    /// Extract date part string from expression (handles string literals and identifiers)
17160    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
17161        match expr {
17162            Expression::Literal(crate::expressions::Literal::String(s)) => Some(s.clone()),
17163            Expression::Identifier(id) => Some(id.name.clone()),
17164            Expression::Column(col) if col.table.is_none() => {
17165                // Simple column reference without table prefix, treat as identifier
17166                Some(col.name.name.clone())
17167            }
17168            _ => None,
17169        }
17170    }
17171
17172    /// Normalize date part to uppercase singular form
17173    /// days -> DAY, months -> MONTH, etc.
17174    fn normalize_date_part(&self, part: &str) -> String {
17175        let lower = part.to_lowercase();
17176        match lower.as_str() {
17177            "day" | "days" | "d" => "DAY".to_string(),
17178            "month" | "months" | "mon" | "mm" => "MONTH".to_string(),
17179            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
17180            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
17181            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
17182            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
17183            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
17184            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
17185            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
17186            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
17187            _ => part.to_uppercase(),
17188        }
17189    }
17190
17191    fn write_datetime_field(&mut self, field: &DateTimeField) {
17192        match field {
17193            DateTimeField::Year => self.write_keyword("YEAR"),
17194            DateTimeField::Month => self.write_keyword("MONTH"),
17195            DateTimeField::Day => self.write_keyword("DAY"),
17196            DateTimeField::Hour => self.write_keyword("HOUR"),
17197            DateTimeField::Minute => self.write_keyword("MINUTE"),
17198            DateTimeField::Second => self.write_keyword("SECOND"),
17199            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
17200            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
17201            DateTimeField::DayOfWeek => {
17202                let name = match self.config.dialect {
17203                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
17204                    _ => "DOW",
17205                };
17206                self.write_keyword(name);
17207            }
17208            DateTimeField::DayOfYear => {
17209                let name = match self.config.dialect {
17210                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
17211                    _ => "DOY",
17212                };
17213                self.write_keyword(name);
17214            }
17215            DateTimeField::Week => self.write_keyword("WEEK"),
17216            DateTimeField::WeekWithModifier(modifier) => {
17217                self.write_keyword("WEEK");
17218                self.write("(");
17219                self.write(modifier);
17220                self.write(")");
17221            }
17222            DateTimeField::Quarter => self.write_keyword("QUARTER"),
17223            DateTimeField::Epoch => self.write_keyword("EPOCH"),
17224            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
17225            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
17226            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
17227            DateTimeField::Date => self.write_keyword("DATE"),
17228            DateTimeField::Time => self.write_keyword("TIME"),
17229            DateTimeField::Custom(name) => self.write(name),
17230        }
17231    }
17232
17233    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
17234    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
17235        match field {
17236            DateTimeField::Year => self.write("year"),
17237            DateTimeField::Month => self.write("month"),
17238            DateTimeField::Day => self.write("day"),
17239            DateTimeField::Hour => self.write("hour"),
17240            DateTimeField::Minute => self.write("minute"),
17241            DateTimeField::Second => self.write("second"),
17242            DateTimeField::Millisecond => self.write("millisecond"),
17243            DateTimeField::Microsecond => self.write("microsecond"),
17244            DateTimeField::DayOfWeek => self.write("dow"),
17245            DateTimeField::DayOfYear => self.write("doy"),
17246            DateTimeField::Week => self.write("week"),
17247            DateTimeField::WeekWithModifier(modifier) => {
17248                self.write("week(");
17249                self.write(modifier);
17250                self.write(")");
17251            }
17252            DateTimeField::Quarter => self.write("quarter"),
17253            DateTimeField::Epoch => self.write("epoch"),
17254            DateTimeField::Timezone => self.write("timezone"),
17255            DateTimeField::TimezoneHour => self.write("timezone_hour"),
17256            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
17257            DateTimeField::Date => self.write("date"),
17258            DateTimeField::Time => self.write("time"),
17259            DateTimeField::Custom(name) => self.write(name),
17260        }
17261    }
17262
17263    // Helper function generators
17264
17265    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
17266        self.write_keyword(name);
17267        self.write("(");
17268        self.generate_expression(arg)?;
17269        self.write(")");
17270        Ok(())
17271    }
17272
17273    /// Generate a unary function, using the original name if available for round-trip preservation
17274    fn generate_unary_func(
17275        &mut self,
17276        default_name: &str,
17277        f: &crate::expressions::UnaryFunc,
17278    ) -> Result<()> {
17279        let name = f.original_name.as_deref().unwrap_or(default_name);
17280        self.write_keyword(name);
17281        self.write("(");
17282        self.generate_expression(&f.this)?;
17283        self.write(")");
17284        Ok(())
17285    }
17286
17287    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
17288    fn generate_sqrt_cbrt(
17289        &mut self,
17290        f: &crate::expressions::UnaryFunc,
17291        func_name: &str,
17292        _op: &str,
17293    ) -> Result<()> {
17294        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
17295        // Always use function syntax for consistency
17296        self.write_keyword(func_name);
17297        self.write("(");
17298        self.generate_expression(&f.this)?;
17299        self.write(")");
17300        Ok(())
17301    }
17302
17303    fn generate_binary_func(
17304        &mut self,
17305        name: &str,
17306        arg1: &Expression,
17307        arg2: &Expression,
17308    ) -> Result<()> {
17309        self.write_keyword(name);
17310        self.write("(");
17311        self.generate_expression(arg1)?;
17312        self.write(", ");
17313        self.generate_expression(arg2)?;
17314        self.write(")");
17315        Ok(())
17316    }
17317
17318    /// Generate CHAR/CHR function with optional USING charset
17319    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
17320    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
17321    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
17322        // Use stored name if available, otherwise default to CHAR
17323        let func_name = f.name.as_deref().unwrap_or("CHAR");
17324        self.write_keyword(func_name);
17325        self.write("(");
17326        for (i, arg) in f.args.iter().enumerate() {
17327            if i > 0 {
17328                self.write(", ");
17329            }
17330            self.generate_expression(arg)?;
17331        }
17332        if let Some(ref charset) = f.charset {
17333            self.write(" ");
17334            self.write_keyword("USING");
17335            self.write(" ");
17336            self.write(charset);
17337        }
17338        self.write(")");
17339        Ok(())
17340    }
17341
17342    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
17343        use crate::dialects::DialectType;
17344
17345        match self.config.dialect {
17346            Some(DialectType::Teradata) => {
17347                // Teradata uses ** operator for exponentiation
17348                self.generate_expression(&f.this)?;
17349                self.write(" ** ");
17350                self.generate_expression(&f.expression)?;
17351                Ok(())
17352            }
17353            _ => {
17354                // Other dialects use POWER function
17355                self.generate_binary_func("POWER", &f.this, &f.expression)
17356            }
17357        }
17358    }
17359
17360    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
17361        self.write_func_name(name);
17362        self.write("(");
17363        for (i, arg) in args.iter().enumerate() {
17364            if i > 0 {
17365                self.write(", ");
17366            }
17367            self.generate_expression(arg)?;
17368        }
17369        self.write(")");
17370        Ok(())
17371    }
17372
17373    // String function generators
17374
17375    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
17376        self.write_keyword("CONCAT_WS");
17377        self.write("(");
17378        self.generate_expression(&f.separator)?;
17379        for expr in &f.expressions {
17380            self.write(", ");
17381            self.generate_expression(expr)?;
17382        }
17383        self.write(")");
17384        Ok(())
17385    }
17386
17387    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
17388        // Oracle uses SUBSTR; most others use SUBSTRING
17389        let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
17390        if is_oracle {
17391            self.write_keyword("SUBSTR");
17392        } else {
17393            self.write_keyword("SUBSTRING");
17394        }
17395        self.write("(");
17396        self.generate_expression(&f.this)?;
17397        // PostgreSQL always uses FROM/FOR syntax
17398        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
17399        // Spark/Hive use comma syntax, not FROM/FOR syntax
17400        let use_comma_syntax = matches!(
17401            self.config.dialect,
17402            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
17403        );
17404        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
17405            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
17406            self.write_space();
17407            self.write_keyword("FROM");
17408            self.write_space();
17409            self.generate_expression(&f.start)?;
17410            if let Some(length) = &f.length {
17411                self.write_space();
17412                self.write_keyword("FOR");
17413                self.write_space();
17414                self.generate_expression(length)?;
17415            }
17416        } else {
17417            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
17418            self.write(", ");
17419            self.generate_expression(&f.start)?;
17420            if let Some(length) = &f.length {
17421                self.write(", ");
17422                self.generate_expression(length)?;
17423            }
17424        }
17425        self.write(")");
17426        Ok(())
17427    }
17428
17429    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
17430        self.write_keyword("OVERLAY");
17431        self.write("(");
17432        self.generate_expression(&f.this)?;
17433        self.write_space();
17434        self.write_keyword("PLACING");
17435        self.write_space();
17436        self.generate_expression(&f.replacement)?;
17437        self.write_space();
17438        self.write_keyword("FROM");
17439        self.write_space();
17440        self.generate_expression(&f.from)?;
17441        if let Some(length) = &f.length {
17442            self.write_space();
17443            self.write_keyword("FOR");
17444            self.write_space();
17445            self.generate_expression(length)?;
17446        }
17447        self.write(")");
17448        Ok(())
17449    }
17450
17451    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
17452        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
17453        // when no characters are specified (PostgreSQL style)
17454        if f.position_explicit && f.characters.is_none() {
17455            match f.position {
17456                TrimPosition::Leading => {
17457                    self.write_keyword("LTRIM");
17458                    self.write("(");
17459                    self.generate_expression(&f.this)?;
17460                    self.write(")");
17461                    return Ok(());
17462                }
17463                TrimPosition::Trailing => {
17464                    self.write_keyword("RTRIM");
17465                    self.write("(");
17466                    self.generate_expression(&f.this)?;
17467                    self.write(")");
17468                    return Ok(());
17469                }
17470                TrimPosition::Both => {
17471                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
17472                    // Fall through to standard TRIM handling
17473                }
17474            }
17475        }
17476
17477        self.write_keyword("TRIM");
17478        self.write("(");
17479        // When BOTH is specified without trim characters, simplify to just TRIM(str)
17480        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
17481        let force_standard = f.characters.is_some()
17482            && !f.sql_standard_syntax
17483            && matches!(
17484                self.config.dialect,
17485                Some(DialectType::Hive)
17486                    | Some(DialectType::Spark)
17487                    | Some(DialectType::Databricks)
17488                    | Some(DialectType::ClickHouse)
17489            );
17490        let use_standard = (f.sql_standard_syntax || force_standard)
17491            && !(f.position_explicit
17492                && f.characters.is_none()
17493                && matches!(f.position, TrimPosition::Both));
17494        if use_standard {
17495            // SQL standard syntax: TRIM(BOTH chars FROM str)
17496            // Only output position if it was explicitly specified
17497            if f.position_explicit {
17498                match f.position {
17499                    TrimPosition::Both => self.write_keyword("BOTH"),
17500                    TrimPosition::Leading => self.write_keyword("LEADING"),
17501                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
17502                }
17503                self.write_space();
17504            }
17505            if let Some(chars) = &f.characters {
17506                self.generate_expression(chars)?;
17507                self.write_space();
17508            }
17509            self.write_keyword("FROM");
17510            self.write_space();
17511            self.generate_expression(&f.this)?;
17512        } else {
17513            // Simple function syntax: TRIM(str) or TRIM(str, chars)
17514            self.generate_expression(&f.this)?;
17515            if let Some(chars) = &f.characters {
17516                self.write(", ");
17517                self.generate_expression(chars)?;
17518            }
17519        }
17520        self.write(")");
17521        Ok(())
17522    }
17523
17524    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
17525        self.write_keyword("REPLACE");
17526        self.write("(");
17527        self.generate_expression(&f.this)?;
17528        self.write(", ");
17529        self.generate_expression(&f.old)?;
17530        self.write(", ");
17531        self.generate_expression(&f.new)?;
17532        self.write(")");
17533        Ok(())
17534    }
17535
17536    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
17537        self.write_keyword(name);
17538        self.write("(");
17539        self.generate_expression(&f.this)?;
17540        self.write(", ");
17541        self.generate_expression(&f.length)?;
17542        self.write(")");
17543        Ok(())
17544    }
17545
17546    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
17547        self.write_keyword("REPEAT");
17548        self.write("(");
17549        self.generate_expression(&f.this)?;
17550        self.write(", ");
17551        self.generate_expression(&f.times)?;
17552        self.write(")");
17553        Ok(())
17554    }
17555
17556    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
17557        self.write_keyword(name);
17558        self.write("(");
17559        self.generate_expression(&f.this)?;
17560        self.write(", ");
17561        self.generate_expression(&f.length)?;
17562        if let Some(fill) = &f.fill {
17563            self.write(", ");
17564            self.generate_expression(fill)?;
17565        }
17566        self.write(")");
17567        Ok(())
17568    }
17569
17570    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
17571        self.write_keyword("SPLIT");
17572        self.write("(");
17573        self.generate_expression(&f.this)?;
17574        self.write(", ");
17575        self.generate_expression(&f.delimiter)?;
17576        self.write(")");
17577        Ok(())
17578    }
17579
17580    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
17581        use crate::dialects::DialectType;
17582        // PostgreSQL uses ~ operator for regex matching
17583        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
17584            self.generate_expression(&f.this)?;
17585            self.write(" ~ ");
17586            self.generate_expression(&f.pattern)?;
17587        } else if matches!(
17588            self.config.dialect,
17589            Some(DialectType::SingleStore)
17590                | Some(DialectType::Spark)
17591                | Some(DialectType::Hive)
17592                | Some(DialectType::Databricks)
17593        ) && f.flags.is_none()
17594        {
17595            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
17596            self.generate_expression(&f.this)?;
17597            self.write_keyword(" RLIKE ");
17598            self.generate_expression(&f.pattern)?;
17599        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
17600            // StarRocks uses REGEXP function syntax
17601            self.write_keyword("REGEXP");
17602            self.write("(");
17603            self.generate_expression(&f.this)?;
17604            self.write(", ");
17605            self.generate_expression(&f.pattern)?;
17606            if let Some(flags) = &f.flags {
17607                self.write(", ");
17608                self.generate_expression(flags)?;
17609            }
17610            self.write(")");
17611        } else {
17612            self.write_keyword("REGEXP_LIKE");
17613            self.write("(");
17614            self.generate_expression(&f.this)?;
17615            self.write(", ");
17616            self.generate_expression(&f.pattern)?;
17617            if let Some(flags) = &f.flags {
17618                self.write(", ");
17619                self.generate_expression(flags)?;
17620            }
17621            self.write(")");
17622        }
17623        Ok(())
17624    }
17625
17626    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
17627        self.write_keyword("REGEXP_REPLACE");
17628        self.write("(");
17629        self.generate_expression(&f.this)?;
17630        self.write(", ");
17631        self.generate_expression(&f.pattern)?;
17632        self.write(", ");
17633        self.generate_expression(&f.replacement)?;
17634        if let Some(flags) = &f.flags {
17635            self.write(", ");
17636            self.generate_expression(flags)?;
17637        }
17638        self.write(")");
17639        Ok(())
17640    }
17641
17642    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
17643        self.write_keyword("REGEXP_EXTRACT");
17644        self.write("(");
17645        self.generate_expression(&f.this)?;
17646        self.write(", ");
17647        self.generate_expression(&f.pattern)?;
17648        if let Some(group) = &f.group {
17649            self.write(", ");
17650            self.generate_expression(group)?;
17651        }
17652        self.write(")");
17653        Ok(())
17654    }
17655
17656    // Math function generators
17657
17658    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
17659        self.write_keyword("ROUND");
17660        self.write("(");
17661        self.generate_expression(&f.this)?;
17662        if let Some(decimals) = &f.decimals {
17663            self.write(", ");
17664            self.generate_expression(decimals)?;
17665        }
17666        self.write(")");
17667        Ok(())
17668    }
17669
17670    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
17671        self.write_keyword("FLOOR");
17672        self.write("(");
17673        self.generate_expression(&f.this)?;
17674        // Handle Druid-style FLOOR(time TO unit) syntax
17675        if let Some(to) = &f.to {
17676            self.write(" ");
17677            self.write_keyword("TO");
17678            self.write(" ");
17679            self.generate_expression(to)?;
17680        } else if let Some(scale) = &f.scale {
17681            self.write(", ");
17682            self.generate_expression(scale)?;
17683        }
17684        self.write(")");
17685        Ok(())
17686    }
17687
17688    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
17689        self.write_keyword("CEIL");
17690        self.write("(");
17691        self.generate_expression(&f.this)?;
17692        // Handle Druid-style CEIL(time TO unit) syntax
17693        if let Some(to) = &f.to {
17694            self.write(" ");
17695            self.write_keyword("TO");
17696            self.write(" ");
17697            self.generate_expression(to)?;
17698        } else if let Some(decimals) = &f.decimals {
17699            self.write(", ");
17700            self.generate_expression(decimals)?;
17701        }
17702        self.write(")");
17703        Ok(())
17704    }
17705
17706    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
17707        use crate::expressions::Literal;
17708
17709        if let Some(base) = &f.base {
17710            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
17711            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
17712            if self.is_log_base_none() {
17713                if matches!(base, Expression::Literal(Literal::Number(s)) if s == "2") {
17714                    self.write_func_name("LOG2");
17715                    self.write("(");
17716                    self.generate_expression(&f.this)?;
17717                    self.write(")");
17718                    return Ok(());
17719                } else if matches!(base, Expression::Literal(Literal::Number(s)) if s == "10") {
17720                    self.write_func_name("LOG10");
17721                    self.write("(");
17722                    self.generate_expression(&f.this)?;
17723                    self.write(")");
17724                    return Ok(());
17725                }
17726                // Other bases: fall through to LOG(base, value) — best effort
17727            }
17728
17729            self.write_func_name("LOG");
17730            self.write("(");
17731            if self.is_log_value_first() {
17732                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
17733                self.generate_expression(&f.this)?;
17734                self.write(", ");
17735                self.generate_expression(base)?;
17736            } else {
17737                // Default (PostgreSQL, etc.): LOG(base, value)
17738                self.generate_expression(base)?;
17739                self.write(", ");
17740                self.generate_expression(&f.this)?;
17741            }
17742            self.write(")");
17743        } else {
17744            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
17745            self.write_func_name("LOG");
17746            self.write("(");
17747            self.generate_expression(&f.this)?;
17748            self.write(")");
17749        }
17750        Ok(())
17751    }
17752
17753    /// Whether the target dialect uses LOG(value, base) order (value first).
17754    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
17755    fn is_log_value_first(&self) -> bool {
17756        use crate::dialects::DialectType;
17757        matches!(
17758            self.config.dialect,
17759            Some(DialectType::BigQuery)
17760                | Some(DialectType::TSQL)
17761                | Some(DialectType::Tableau)
17762                | Some(DialectType::Fabric)
17763        )
17764    }
17765
17766    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
17767    /// Presto, Trino, ClickHouse, Athena.
17768    fn is_log_base_none(&self) -> bool {
17769        use crate::dialects::DialectType;
17770        matches!(
17771            self.config.dialect,
17772            Some(DialectType::Presto)
17773                | Some(DialectType::Trino)
17774                | Some(DialectType::ClickHouse)
17775                | Some(DialectType::Athena)
17776        )
17777    }
17778
17779    // Date/time function generators
17780
17781    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
17782        self.write_keyword("CURRENT_TIME");
17783        if let Some(precision) = f.precision {
17784            self.write(&format!("({})", precision));
17785        }
17786        Ok(())
17787    }
17788
17789    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
17790        use crate::dialects::DialectType;
17791
17792        // Oracle/Redshift SYSDATE handling
17793        if f.sysdate {
17794            match self.config.dialect {
17795                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
17796                    self.write_keyword("SYSDATE");
17797                    return Ok(());
17798                }
17799                Some(DialectType::Snowflake) => {
17800                    // Snowflake uses SYSDATE() function
17801                    self.write_keyword("SYSDATE");
17802                    self.write("()");
17803                    return Ok(());
17804                }
17805                _ => {
17806                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
17807                }
17808            }
17809        }
17810
17811        self.write_keyword("CURRENT_TIMESTAMP");
17812        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
17813        if let Some(precision) = f.precision {
17814            self.write(&format!("({})", precision));
17815        } else if matches!(
17816            self.config.dialect,
17817            Some(crate::dialects::DialectType::MySQL)
17818                | Some(crate::dialects::DialectType::SingleStore)
17819                | Some(crate::dialects::DialectType::TiDB)
17820                | Some(crate::dialects::DialectType::Spark)
17821                | Some(crate::dialects::DialectType::Hive)
17822                | Some(crate::dialects::DialectType::Databricks)
17823                | Some(crate::dialects::DialectType::ClickHouse)
17824                | Some(crate::dialects::DialectType::BigQuery)
17825                | Some(crate::dialects::DialectType::Snowflake)
17826        ) {
17827            self.write("()");
17828        }
17829        Ok(())
17830    }
17831
17832    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
17833        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
17834        if self.config.dialect == Some(DialectType::Exasol) {
17835            self.write_keyword("CONVERT_TZ");
17836            self.write("(");
17837            self.generate_expression(&f.this)?;
17838            self.write(", 'UTC', ");
17839            self.generate_expression(&f.zone)?;
17840            self.write(")");
17841            return Ok(());
17842        }
17843
17844        self.generate_expression(&f.this)?;
17845        self.write_space();
17846        self.write_keyword("AT TIME ZONE");
17847        self.write_space();
17848        self.generate_expression(&f.zone)?;
17849        Ok(())
17850    }
17851
17852    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
17853        use crate::dialects::DialectType;
17854
17855        // Presto/Trino use DATE_ADD('unit', interval, date) format
17856        // with the interval cast to BIGINT when needed
17857        let is_presto_like = matches!(
17858            self.config.dialect,
17859            Some(DialectType::Presto) | Some(DialectType::Trino)
17860        );
17861
17862        if is_presto_like {
17863            self.write_keyword(name);
17864            self.write("(");
17865            // Unit as string literal
17866            self.write("'");
17867            self.write_simple_interval_unit(&f.unit, false);
17868            self.write("'");
17869            self.write(", ");
17870            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
17871            let needs_cast = !self.returns_integer_type(&f.interval);
17872            if needs_cast {
17873                self.write_keyword("CAST");
17874                self.write("(");
17875            }
17876            self.generate_expression(&f.interval)?;
17877            if needs_cast {
17878                self.write_space();
17879                self.write_keyword("AS");
17880                self.write_space();
17881                self.write_keyword("BIGINT");
17882                self.write(")");
17883            }
17884            self.write(", ");
17885            self.generate_expression(&f.this)?;
17886            self.write(")");
17887        } else {
17888            self.write_keyword(name);
17889            self.write("(");
17890            self.generate_expression(&f.this)?;
17891            self.write(", ");
17892            self.write_keyword("INTERVAL");
17893            self.write_space();
17894            self.generate_expression(&f.interval)?;
17895            self.write_space();
17896            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
17897            self.write(")");
17898        }
17899        Ok(())
17900    }
17901
17902    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
17903    /// This is a heuristic to avoid full type inference
17904    fn returns_integer_type(&self, expr: &Expression) -> bool {
17905        use crate::expressions::{DataType, Literal};
17906        match expr {
17907            // Integer literals (no decimal point)
17908            Expression::Literal(Literal::Number(n)) => !n.contains('.'),
17909
17910            // FLOOR(x) returns integer if x is integer
17911            Expression::Floor(f) => self.returns_integer_type(&f.this),
17912
17913            // ROUND(x) returns integer if x is integer
17914            Expression::Round(f) => {
17915                // Only if no decimals arg or it's returning an integer
17916                f.decimals.is_none() && self.returns_integer_type(&f.this)
17917            }
17918
17919            // SIGN returns integer if input is integer
17920            Expression::Sign(f) => self.returns_integer_type(&f.this),
17921
17922            // ABS returns the same type as input
17923            Expression::Abs(f) => self.returns_integer_type(&f.this),
17924
17925            // Arithmetic operations on integers return integers
17926            Expression::Mul(op) => {
17927                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
17928            }
17929            Expression::Add(op) => {
17930                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
17931            }
17932            Expression::Sub(op) => {
17933                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
17934            }
17935            Expression::Mod(op) => self.returns_integer_type(&op.left),
17936
17937            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
17938            Expression::Cast(c) => matches!(
17939                &c.to,
17940                DataType::BigInt { .. }
17941                    | DataType::Int { .. }
17942                    | DataType::SmallInt { .. }
17943                    | DataType::TinyInt { .. }
17944            ),
17945
17946            // Negation: -x returns integer if x is integer
17947            Expression::Neg(op) => self.returns_integer_type(&op.this),
17948
17949            // Parenthesized expression
17950            Expression::Paren(p) => self.returns_integer_type(&p.this),
17951
17952            // Column references and most expressions are assumed to need casting
17953            // since we don't have full type information
17954            _ => false,
17955        }
17956    }
17957
17958    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
17959        self.write_keyword("DATEDIFF");
17960        self.write("(");
17961        if let Some(unit) = &f.unit {
17962            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
17963            self.write(", ");
17964        }
17965        self.generate_expression(&f.this)?;
17966        self.write(", ");
17967        self.generate_expression(&f.expression)?;
17968        self.write(")");
17969        Ok(())
17970    }
17971
17972    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
17973        self.write_keyword("DATE_TRUNC");
17974        self.write("('");
17975        self.write_datetime_field(&f.unit);
17976        self.write("', ");
17977        self.generate_expression(&f.this)?;
17978        self.write(")");
17979        Ok(())
17980    }
17981
17982    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
17983        use crate::dialects::DialectType;
17984        use crate::expressions::DateTimeField;
17985
17986        self.write_keyword("LAST_DAY");
17987        self.write("(");
17988        self.generate_expression(&f.this)?;
17989        if let Some(unit) = &f.unit {
17990            self.write(", ");
17991            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
17992            // WEEK(SUNDAY) -> WEEK
17993            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
17994                if let DateTimeField::WeekWithModifier(_) = unit {
17995                    self.write_keyword("WEEK");
17996                } else {
17997                    self.write_datetime_field(unit);
17998                }
17999            } else {
18000                self.write_datetime_field(unit);
18001            }
18002        }
18003        self.write(")");
18004        Ok(())
18005    }
18006
18007    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
18008        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
18009        if matches!(
18010            self.config.dialect,
18011            Some(DialectType::TSQL) | Some(DialectType::Fabric)
18012        ) {
18013            self.write_keyword("DATEPART");
18014            self.write("(");
18015            self.write_datetime_field(&f.field);
18016            self.write(", ");
18017            self.generate_expression(&f.this)?;
18018            self.write(")");
18019            return Ok(());
18020        }
18021        self.write_keyword("EXTRACT");
18022        self.write("(");
18023        // Hive/Spark use lowercase datetime fields in EXTRACT
18024        if matches!(
18025            self.config.dialect,
18026            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
18027        ) {
18028            self.write_datetime_field_lower(&f.field);
18029        } else {
18030            self.write_datetime_field(&f.field);
18031        }
18032        self.write_space();
18033        self.write_keyword("FROM");
18034        self.write_space();
18035        self.generate_expression(&f.this)?;
18036        self.write(")");
18037        Ok(())
18038    }
18039
18040    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
18041        self.write_keyword("TO_DATE");
18042        self.write("(");
18043        self.generate_expression(&f.this)?;
18044        if let Some(format) = &f.format {
18045            self.write(", ");
18046            self.generate_expression(format)?;
18047        }
18048        self.write(")");
18049        Ok(())
18050    }
18051
18052    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
18053        self.write_keyword("TO_TIMESTAMP");
18054        self.write("(");
18055        self.generate_expression(&f.this)?;
18056        if let Some(format) = &f.format {
18057            self.write(", ");
18058            self.generate_expression(format)?;
18059        }
18060        self.write(")");
18061        Ok(())
18062    }
18063
18064    // Control flow function generators
18065
18066    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
18067        use crate::dialects::DialectType;
18068
18069        // Generic mode: normalize IF to CASE WHEN
18070        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
18071            self.write_keyword("CASE WHEN");
18072            self.write_space();
18073            self.generate_expression(&f.condition)?;
18074            self.write_space();
18075            self.write_keyword("THEN");
18076            self.write_space();
18077            self.generate_expression(&f.true_value)?;
18078            if let Some(false_val) = &f.false_value {
18079                self.write_space();
18080                self.write_keyword("ELSE");
18081                self.write_space();
18082                self.generate_expression(false_val)?;
18083            }
18084            self.write_space();
18085            self.write_keyword("END");
18086            return Ok(());
18087        }
18088
18089        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
18090        if self.config.dialect == Some(DialectType::Exasol) {
18091            self.write_keyword("IF");
18092            self.write_space();
18093            self.generate_expression(&f.condition)?;
18094            self.write_space();
18095            self.write_keyword("THEN");
18096            self.write_space();
18097            self.generate_expression(&f.true_value)?;
18098            if let Some(false_val) = &f.false_value {
18099                self.write_space();
18100                self.write_keyword("ELSE");
18101                self.write_space();
18102                self.generate_expression(false_val)?;
18103            }
18104            self.write_space();
18105            self.write_keyword("ENDIF");
18106            return Ok(());
18107        }
18108
18109        // Choose function name based on target dialect
18110        let func_name = match self.config.dialect {
18111            Some(DialectType::Snowflake) => "IFF",
18112            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
18113            Some(DialectType::Drill) => "`IF`",
18114            _ => "IF",
18115        };
18116        self.write(func_name);
18117        self.write("(");
18118        self.generate_expression(&f.condition)?;
18119        self.write(", ");
18120        self.generate_expression(&f.true_value)?;
18121        if let Some(false_val) = &f.false_value {
18122            self.write(", ");
18123            self.generate_expression(false_val)?;
18124        }
18125        self.write(")");
18126        Ok(())
18127    }
18128
18129    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
18130        self.write_keyword("NVL2");
18131        self.write("(");
18132        self.generate_expression(&f.this)?;
18133        self.write(", ");
18134        self.generate_expression(&f.true_value)?;
18135        self.write(", ");
18136        self.generate_expression(&f.false_value)?;
18137        self.write(")");
18138        Ok(())
18139    }
18140
18141    // Typed aggregate function generators
18142
18143    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
18144        // Use normalize_functions for COUNT to respect ClickHouse case preservation
18145        let count_name = match self.config.normalize_functions {
18146            NormalizeFunctions::Upper => "COUNT".to_string(),
18147            NormalizeFunctions::Lower => "count".to_string(),
18148            NormalizeFunctions::None => f
18149                .original_name
18150                .clone()
18151                .unwrap_or_else(|| "COUNT".to_string()),
18152        };
18153        self.write(&count_name);
18154        self.write("(");
18155        if f.distinct {
18156            self.write_keyword("DISTINCT");
18157            self.write_space();
18158        }
18159        if f.star {
18160            self.write("*");
18161        } else if let Some(ref expr) = f.this {
18162            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
18163            if let Expression::Tuple(tuple) = expr {
18164                // Check if we need to transform multi-arg COUNT DISTINCT
18165                // When dialect doesn't support multi_arg_distinct, transform:
18166                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
18167                let needs_transform =
18168                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
18169
18170                if needs_transform {
18171                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
18172                    self.write_keyword("CASE");
18173                    for e in &tuple.expressions {
18174                        self.write_space();
18175                        self.write_keyword("WHEN");
18176                        self.write_space();
18177                        self.generate_expression(e)?;
18178                        self.write_space();
18179                        self.write_keyword("IS NULL THEN NULL");
18180                    }
18181                    self.write_space();
18182                    self.write_keyword("ELSE");
18183                    self.write(" (");
18184                    for (i, e) in tuple.expressions.iter().enumerate() {
18185                        if i > 0 {
18186                            self.write(", ");
18187                        }
18188                        self.generate_expression(e)?;
18189                    }
18190                    self.write(")");
18191                    self.write_space();
18192                    self.write_keyword("END");
18193                } else {
18194                    for (i, e) in tuple.expressions.iter().enumerate() {
18195                        if i > 0 {
18196                            self.write(", ");
18197                        }
18198                        self.generate_expression(e)?;
18199                    }
18200                }
18201            } else {
18202                self.generate_expression(expr)?;
18203            }
18204        }
18205        // RESPECT NULLS / IGNORE NULLS
18206        if let Some(ignore) = f.ignore_nulls {
18207            self.write_space();
18208            if ignore {
18209                self.write_keyword("IGNORE NULLS");
18210            } else {
18211                self.write_keyword("RESPECT NULLS");
18212            }
18213        }
18214        self.write(")");
18215        if let Some(ref filter) = f.filter {
18216            self.write_space();
18217            self.write_keyword("FILTER");
18218            self.write("(");
18219            self.write_keyword("WHERE");
18220            self.write_space();
18221            self.generate_expression(filter)?;
18222            self.write(")");
18223        }
18224        Ok(())
18225    }
18226
18227    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
18228        // Apply function name normalization based on config
18229        let func_name = match self.config.normalize_functions {
18230            NormalizeFunctions::Upper => name.to_uppercase(),
18231            NormalizeFunctions::Lower => name.to_lowercase(),
18232            NormalizeFunctions::None => {
18233                // Use the original function name from parsing if available,
18234                // otherwise fall back to lowercase of the hardcoded constant
18235                if let Some(ref original) = f.name {
18236                    original.clone()
18237                } else {
18238                    name.to_lowercase()
18239                }
18240            }
18241        };
18242        self.write(&func_name);
18243        self.write("(");
18244        if f.distinct {
18245            self.write_keyword("DISTINCT");
18246            self.write_space();
18247        }
18248        // Skip generating the expression if it's a NULL placeholder for zero-arg aggregates like MODE()
18249        if !matches!(f.this, Expression::Null(_)) {
18250            self.generate_expression(&f.this)?;
18251        }
18252        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
18253        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
18254        if self.config.ignore_nulls_in_func
18255            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18256        {
18257            match f.ignore_nulls {
18258                Some(true) => {
18259                    self.write_space();
18260                    self.write_keyword("IGNORE NULLS");
18261                }
18262                Some(false) => {
18263                    self.write_space();
18264                    self.write_keyword("RESPECT NULLS");
18265                }
18266                None => {}
18267            }
18268        }
18269        // Generate HAVING MAX/MIN if present (BigQuery syntax)
18270        // e.g., ANY_VALUE(fruit HAVING MAX sold)
18271        if let Some((ref expr, is_max)) = f.having_max {
18272            self.write_space();
18273            self.write_keyword("HAVING");
18274            self.write_space();
18275            if is_max {
18276                self.write_keyword("MAX");
18277            } else {
18278                self.write_keyword("MIN");
18279            }
18280            self.write_space();
18281            self.generate_expression(expr)?;
18282        }
18283        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
18284        if !f.order_by.is_empty() {
18285            self.write_space();
18286            self.write_keyword("ORDER BY");
18287            self.write_space();
18288            for (i, ord) in f.order_by.iter().enumerate() {
18289                if i > 0 {
18290                    self.write(", ");
18291                }
18292                self.generate_ordered(ord)?;
18293            }
18294        }
18295        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
18296        if let Some(ref limit) = f.limit {
18297            self.write_space();
18298            self.write_keyword("LIMIT");
18299            self.write_space();
18300            // Check if this is a Tuple representing LIMIT offset, count
18301            if let Expression::Tuple(t) = limit.as_ref() {
18302                if t.expressions.len() == 2 {
18303                    self.generate_expression(&t.expressions[0])?;
18304                    self.write(", ");
18305                    self.generate_expression(&t.expressions[1])?;
18306                } else {
18307                    self.generate_expression(limit)?;
18308                }
18309            } else {
18310                self.generate_expression(limit)?;
18311            }
18312        }
18313        self.write(")");
18314        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
18315        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
18316        if !self.config.ignore_nulls_in_func
18317            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18318        {
18319            match f.ignore_nulls {
18320                Some(true) => {
18321                    self.write_space();
18322                    self.write_keyword("IGNORE NULLS");
18323                }
18324                Some(false) => {
18325                    self.write_space();
18326                    self.write_keyword("RESPECT NULLS");
18327                }
18328                None => {}
18329            }
18330        }
18331        if let Some(ref filter) = f.filter {
18332            self.write_space();
18333            self.write_keyword("FILTER");
18334            self.write("(");
18335            self.write_keyword("WHERE");
18336            self.write_space();
18337            self.generate_expression(filter)?;
18338            self.write(")");
18339        }
18340        Ok(())
18341    }
18342
18343    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
18344        self.write_keyword("GROUP_CONCAT");
18345        self.write("(");
18346        if f.distinct {
18347            self.write_keyword("DISTINCT");
18348            self.write_space();
18349        }
18350        self.generate_expression(&f.this)?;
18351        if let Some(ref order_by) = f.order_by {
18352            self.write_space();
18353            self.write_keyword("ORDER BY");
18354            self.write_space();
18355            for (i, ord) in order_by.iter().enumerate() {
18356                if i > 0 {
18357                    self.write(", ");
18358                }
18359                self.generate_ordered(ord)?;
18360            }
18361        }
18362        if let Some(ref sep) = f.separator {
18363            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
18364            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
18365            if matches!(
18366                self.config.dialect,
18367                Some(crate::dialects::DialectType::SQLite)
18368            ) {
18369                self.write(", ");
18370                self.generate_expression(sep)?;
18371            } else {
18372                self.write_space();
18373                self.write_keyword("SEPARATOR");
18374                self.write_space();
18375                self.generate_expression(sep)?;
18376            }
18377        }
18378        self.write(")");
18379        if let Some(ref filter) = f.filter {
18380            self.write_space();
18381            self.write_keyword("FILTER");
18382            self.write("(");
18383            self.write_keyword("WHERE");
18384            self.write_space();
18385            self.generate_expression(filter)?;
18386            self.write(")");
18387        }
18388        Ok(())
18389    }
18390
18391    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
18392        let is_tsql = matches!(
18393            self.config.dialect,
18394            Some(crate::dialects::DialectType::TSQL)
18395        );
18396        self.write_keyword("STRING_AGG");
18397        self.write("(");
18398        if f.distinct {
18399            self.write_keyword("DISTINCT");
18400            self.write_space();
18401        }
18402        self.generate_expression(&f.this)?;
18403        if let Some(ref separator) = f.separator {
18404            self.write(", ");
18405            self.generate_expression(separator)?;
18406        }
18407        // For TSQL, ORDER BY goes in WITHIN GROUP clause after the closing paren
18408        if !is_tsql {
18409            if let Some(ref order_by) = f.order_by {
18410                self.write_space();
18411                self.write_keyword("ORDER BY");
18412                self.write_space();
18413                for (i, ord) in order_by.iter().enumerate() {
18414                    if i > 0 {
18415                        self.write(", ");
18416                    }
18417                    self.generate_ordered(ord)?;
18418                }
18419            }
18420        }
18421        if let Some(ref limit) = f.limit {
18422            self.write_space();
18423            self.write_keyword("LIMIT");
18424            self.write_space();
18425            self.generate_expression(limit)?;
18426        }
18427        self.write(")");
18428        // TSQL uses WITHIN GROUP (ORDER BY ...) after the function call
18429        if is_tsql {
18430            if let Some(ref order_by) = f.order_by {
18431                self.write_space();
18432                self.write_keyword("WITHIN GROUP");
18433                self.write(" (");
18434                self.write_keyword("ORDER BY");
18435                self.write_space();
18436                for (i, ord) in order_by.iter().enumerate() {
18437                    if i > 0 {
18438                        self.write(", ");
18439                    }
18440                    self.generate_ordered(ord)?;
18441                }
18442                self.write(")");
18443            }
18444        }
18445        if let Some(ref filter) = f.filter {
18446            self.write_space();
18447            self.write_keyword("FILTER");
18448            self.write("(");
18449            self.write_keyword("WHERE");
18450            self.write_space();
18451            self.generate_expression(filter)?;
18452            self.write(")");
18453        }
18454        Ok(())
18455    }
18456
18457    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
18458        use crate::dialects::DialectType;
18459        self.write_keyword("LISTAGG");
18460        self.write("(");
18461        if f.distinct {
18462            self.write_keyword("DISTINCT");
18463            self.write_space();
18464        }
18465        self.generate_expression(&f.this)?;
18466        if let Some(ref sep) = f.separator {
18467            self.write(", ");
18468            self.generate_expression(sep)?;
18469        } else if matches!(
18470            self.config.dialect,
18471            Some(DialectType::Trino) | Some(DialectType::Presto)
18472        ) {
18473            // Trino/Presto require explicit separator; default to ','
18474            self.write(", ','");
18475        }
18476        if let Some(ref overflow) = f.on_overflow {
18477            self.write_space();
18478            self.write_keyword("ON OVERFLOW");
18479            self.write_space();
18480            match overflow {
18481                ListAggOverflow::Error => self.write_keyword("ERROR"),
18482                ListAggOverflow::Truncate { filler, with_count } => {
18483                    self.write_keyword("TRUNCATE");
18484                    if let Some(ref fill) = filler {
18485                        self.write_space();
18486                        self.generate_expression(fill)?;
18487                    }
18488                    if *with_count {
18489                        self.write_space();
18490                        self.write_keyword("WITH COUNT");
18491                    } else {
18492                        self.write_space();
18493                        self.write_keyword("WITHOUT COUNT");
18494                    }
18495                }
18496            }
18497        }
18498        self.write(")");
18499        if let Some(ref order_by) = f.order_by {
18500            self.write_space();
18501            self.write_keyword("WITHIN GROUP");
18502            self.write(" (");
18503            self.write_keyword("ORDER BY");
18504            self.write_space();
18505            for (i, ord) in order_by.iter().enumerate() {
18506                if i > 0 {
18507                    self.write(", ");
18508                }
18509                self.generate_ordered(ord)?;
18510            }
18511            self.write(")");
18512        }
18513        if let Some(ref filter) = f.filter {
18514            self.write_space();
18515            self.write_keyword("FILTER");
18516            self.write("(");
18517            self.write_keyword("WHERE");
18518            self.write_space();
18519            self.generate_expression(filter)?;
18520            self.write(")");
18521        }
18522        Ok(())
18523    }
18524
18525    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
18526        self.write_keyword("SUM_IF");
18527        self.write("(");
18528        self.generate_expression(&f.this)?;
18529        self.write(", ");
18530        self.generate_expression(&f.condition)?;
18531        self.write(")");
18532        if let Some(ref filter) = f.filter {
18533            self.write_space();
18534            self.write_keyword("FILTER");
18535            self.write("(");
18536            self.write_keyword("WHERE");
18537            self.write_space();
18538            self.generate_expression(filter)?;
18539            self.write(")");
18540        }
18541        Ok(())
18542    }
18543
18544    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
18545        self.write_keyword("APPROX_PERCENTILE");
18546        self.write("(");
18547        self.generate_expression(&f.this)?;
18548        self.write(", ");
18549        self.generate_expression(&f.percentile)?;
18550        if let Some(ref acc) = f.accuracy {
18551            self.write(", ");
18552            self.generate_expression(acc)?;
18553        }
18554        self.write(")");
18555        if let Some(ref filter) = f.filter {
18556            self.write_space();
18557            self.write_keyword("FILTER");
18558            self.write("(");
18559            self.write_keyword("WHERE");
18560            self.write_space();
18561            self.generate_expression(filter)?;
18562            self.write(")");
18563        }
18564        Ok(())
18565    }
18566
18567    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
18568        self.write_keyword(name);
18569        self.write("(");
18570        self.generate_expression(&f.percentile)?;
18571        self.write(")");
18572        if let Some(ref order_by) = f.order_by {
18573            self.write_space();
18574            self.write_keyword("WITHIN GROUP");
18575            self.write(" (");
18576            self.write_keyword("ORDER BY");
18577            self.write_space();
18578            self.generate_expression(&f.this)?;
18579            for ord in order_by.iter() {
18580                if ord.desc {
18581                    self.write_space();
18582                    self.write_keyword("DESC");
18583                }
18584            }
18585            self.write(")");
18586        }
18587        if let Some(ref filter) = f.filter {
18588            self.write_space();
18589            self.write_keyword("FILTER");
18590            self.write("(");
18591            self.write_keyword("WHERE");
18592            self.write_space();
18593            self.generate_expression(filter)?;
18594            self.write(")");
18595        }
18596        Ok(())
18597    }
18598
18599    // Window function generators
18600
18601    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
18602        self.write_keyword("NTILE");
18603        self.write("(");
18604        if let Some(num_buckets) = &f.num_buckets {
18605            self.generate_expression(num_buckets)?;
18606        }
18607        if let Some(order_by) = &f.order_by {
18608            self.write_keyword(" ORDER BY ");
18609            for (i, ob) in order_by.iter().enumerate() {
18610                if i > 0 {
18611                    self.write(", ");
18612                }
18613                self.generate_ordered(ob)?;
18614            }
18615        }
18616        self.write(")");
18617        Ok(())
18618    }
18619
18620    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
18621        self.write_keyword(name);
18622        self.write("(");
18623        self.generate_expression(&f.this)?;
18624        if let Some(ref offset) = f.offset {
18625            self.write(", ");
18626            self.generate_expression(offset)?;
18627            if let Some(ref default) = f.default {
18628                self.write(", ");
18629                self.generate_expression(default)?;
18630            }
18631        }
18632        // IGNORE NULLS inside parens for dialects like BigQuery
18633        if f.ignore_nulls && self.config.ignore_nulls_in_func {
18634            self.write_space();
18635            self.write_keyword("IGNORE NULLS");
18636        }
18637        self.write(")");
18638        // IGNORE NULLS outside parens for other dialects
18639        if f.ignore_nulls && !self.config.ignore_nulls_in_func {
18640            self.write_space();
18641            self.write_keyword("IGNORE NULLS");
18642        }
18643        Ok(())
18644    }
18645
18646    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
18647        self.write_keyword(name);
18648        self.write("(");
18649        self.generate_expression(&f.this)?;
18650        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
18651        if self.config.ignore_nulls_in_func {
18652            match f.ignore_nulls {
18653                Some(true) => {
18654                    self.write_space();
18655                    self.write_keyword("IGNORE NULLS");
18656                }
18657                Some(false) => {
18658                    self.write_space();
18659                    self.write_keyword("RESPECT NULLS");
18660                }
18661                None => {}
18662            }
18663        }
18664        self.write(")");
18665        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
18666        if !self.config.ignore_nulls_in_func {
18667            match f.ignore_nulls {
18668                Some(true) => {
18669                    self.write_space();
18670                    self.write_keyword("IGNORE NULLS");
18671                }
18672                Some(false) => {
18673                    self.write_space();
18674                    self.write_keyword("RESPECT NULLS");
18675                }
18676                None => {}
18677            }
18678        }
18679        Ok(())
18680    }
18681
18682    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
18683        self.write_keyword("NTH_VALUE");
18684        self.write("(");
18685        self.generate_expression(&f.this)?;
18686        self.write(", ");
18687        self.generate_expression(&f.offset)?;
18688        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
18689        if self.config.ignore_nulls_in_func {
18690            match f.ignore_nulls {
18691                Some(true) => {
18692                    self.write_space();
18693                    self.write_keyword("IGNORE NULLS");
18694                }
18695                Some(false) => {
18696                    self.write_space();
18697                    self.write_keyword("RESPECT NULLS");
18698                }
18699                None => {}
18700            }
18701        }
18702        self.write(")");
18703        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
18704        if matches!(
18705            self.config.dialect,
18706            Some(crate::dialects::DialectType::Snowflake)
18707        ) {
18708            match f.from_first {
18709                Some(true) => {
18710                    self.write_space();
18711                    self.write_keyword("FROM FIRST");
18712                }
18713                Some(false) => {
18714                    self.write_space();
18715                    self.write_keyword("FROM LAST");
18716                }
18717                None => {}
18718            }
18719        }
18720        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
18721        if !self.config.ignore_nulls_in_func {
18722            match f.ignore_nulls {
18723                Some(true) => {
18724                    self.write_space();
18725                    self.write_keyword("IGNORE NULLS");
18726                }
18727                Some(false) => {
18728                    self.write_space();
18729                    self.write_keyword("RESPECT NULLS");
18730                }
18731                None => {}
18732            }
18733        }
18734        Ok(())
18735    }
18736
18737    // Additional string function generators
18738
18739    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
18740        // Standard syntax: POSITION(substr IN str)
18741        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
18742        if matches!(
18743            self.config.dialect,
18744            Some(crate::dialects::DialectType::ClickHouse)
18745        ) {
18746            self.write_keyword("POSITION");
18747            self.write("(");
18748            self.generate_expression(&f.string)?;
18749            self.write(", ");
18750            self.generate_expression(&f.substring)?;
18751            if let Some(ref start) = f.start {
18752                self.write(", ");
18753                self.generate_expression(start)?;
18754            }
18755            self.write(")");
18756            return Ok(());
18757        }
18758
18759        self.write_keyword("POSITION");
18760        self.write("(");
18761        self.generate_expression(&f.substring)?;
18762        self.write_space();
18763        self.write_keyword("IN");
18764        self.write_space();
18765        self.generate_expression(&f.string)?;
18766        if let Some(ref start) = f.start {
18767            self.write(", ");
18768            self.generate_expression(start)?;
18769        }
18770        self.write(")");
18771        Ok(())
18772    }
18773
18774    // Additional math function generators
18775
18776    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
18777        // Teradata RANDOM(lower, upper)
18778        if f.lower.is_some() || f.upper.is_some() {
18779            self.write_keyword("RANDOM");
18780            self.write("(");
18781            if let Some(ref lower) = f.lower {
18782                self.generate_expression(lower)?;
18783            }
18784            if let Some(ref upper) = f.upper {
18785                self.write(", ");
18786                self.generate_expression(upper)?;
18787            }
18788            self.write(")");
18789            return Ok(());
18790        }
18791        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
18792        let func_name = match self.config.dialect {
18793            Some(crate::dialects::DialectType::Snowflake)
18794            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
18795            _ => "RAND",
18796        };
18797        self.write_keyword(func_name);
18798        self.write("(");
18799        // DuckDB doesn't support seeded RANDOM, so skip the seed
18800        if !matches!(
18801            self.config.dialect,
18802            Some(crate::dialects::DialectType::DuckDB)
18803        ) {
18804            if let Some(ref seed) = f.seed {
18805                self.generate_expression(seed)?;
18806            }
18807        }
18808        self.write(")");
18809        Ok(())
18810    }
18811
18812    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
18813        self.write_keyword("TRUNCATE");
18814        self.write("(");
18815        self.generate_expression(&f.this)?;
18816        if let Some(ref decimals) = f.decimals {
18817            self.write(", ");
18818            self.generate_expression(decimals)?;
18819        }
18820        self.write(")");
18821        Ok(())
18822    }
18823
18824    // Control flow generators
18825
18826    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
18827        self.write_keyword("DECODE");
18828        self.write("(");
18829        self.generate_expression(&f.this)?;
18830        for (search, result) in &f.search_results {
18831            self.write(", ");
18832            self.generate_expression(search)?;
18833            self.write(", ");
18834            self.generate_expression(result)?;
18835        }
18836        if let Some(ref default) = f.default {
18837            self.write(", ");
18838            self.generate_expression(default)?;
18839        }
18840        self.write(")");
18841        Ok(())
18842    }
18843
18844    // Date/time function generators
18845
18846    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
18847        self.write_keyword(name);
18848        self.write("(");
18849        self.generate_expression(&f.this)?;
18850        self.write(", ");
18851        self.generate_expression(&f.format)?;
18852        self.write(")");
18853        Ok(())
18854    }
18855
18856    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
18857        self.write_keyword("FROM_UNIXTIME");
18858        self.write("(");
18859        self.generate_expression(&f.this)?;
18860        if let Some(ref format) = f.format {
18861            self.write(", ");
18862            self.generate_expression(format)?;
18863        }
18864        self.write(")");
18865        Ok(())
18866    }
18867
18868    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
18869        self.write_keyword("UNIX_TIMESTAMP");
18870        self.write("(");
18871        if let Some(ref expr) = f.this {
18872            self.generate_expression(expr)?;
18873            if let Some(ref format) = f.format {
18874                self.write(", ");
18875                self.generate_expression(format)?;
18876            }
18877        } else if matches!(
18878            self.config.dialect,
18879            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
18880        ) {
18881            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
18882            self.write_keyword("CURRENT_TIMESTAMP");
18883            self.write("()");
18884        }
18885        self.write(")");
18886        Ok(())
18887    }
18888
18889    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
18890        self.write_keyword("MAKE_DATE");
18891        self.write("(");
18892        self.generate_expression(&f.year)?;
18893        self.write(", ");
18894        self.generate_expression(&f.month)?;
18895        self.write(", ");
18896        self.generate_expression(&f.day)?;
18897        self.write(")");
18898        Ok(())
18899    }
18900
18901    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
18902        self.write_keyword("MAKE_TIMESTAMP");
18903        self.write("(");
18904        self.generate_expression(&f.year)?;
18905        self.write(", ");
18906        self.generate_expression(&f.month)?;
18907        self.write(", ");
18908        self.generate_expression(&f.day)?;
18909        self.write(", ");
18910        self.generate_expression(&f.hour)?;
18911        self.write(", ");
18912        self.generate_expression(&f.minute)?;
18913        self.write(", ");
18914        self.generate_expression(&f.second)?;
18915        if let Some(ref tz) = f.timezone {
18916            self.write(", ");
18917            self.generate_expression(tz)?;
18918        }
18919        self.write(")");
18920        Ok(())
18921    }
18922
18923    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
18924    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
18925        match expr {
18926            Expression::Struct(s) => {
18927                if s.fields.iter().all(|(name, _)| name.is_some()) {
18928                    Some(
18929                        s.fields
18930                            .iter()
18931                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
18932                            .collect(),
18933                    )
18934                } else {
18935                    None
18936                }
18937            }
18938            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
18939                // Check if all args are Alias (named fields)
18940                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
18941                    Some(
18942                        f.args
18943                            .iter()
18944                            .filter_map(|a| {
18945                                if let Expression::Alias(alias) = a {
18946                                    Some(alias.alias.name.clone())
18947                                } else {
18948                                    None
18949                                }
18950                            })
18951                            .collect(),
18952                    )
18953                } else {
18954                    None
18955                }
18956            }
18957            _ => None,
18958        }
18959    }
18960
18961    /// Check if a struct expression has any unnamed fields
18962    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
18963        match expr {
18964            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
18965            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
18966                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
18967            }
18968            _ => false,
18969        }
18970    }
18971
18972    /// Get the field count of a struct expression
18973    fn struct_field_count(expr: &Expression) -> usize {
18974        match expr {
18975            Expression::Struct(s) => s.fields.len(),
18976            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => f.args.len(),
18977            _ => 0,
18978        }
18979    }
18980
18981    /// Apply field names to an unnamed struct expression, producing a new expression with names
18982    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
18983        match expr {
18984            Expression::Struct(s) => {
18985                let mut new_fields = Vec::with_capacity(s.fields.len());
18986                for (i, (name, value)) in s.fields.iter().enumerate() {
18987                    if name.is_none() && i < field_names.len() {
18988                        new_fields.push((Some(field_names[i].clone()), value.clone()));
18989                    } else {
18990                        new_fields.push((name.clone(), value.clone()));
18991                    }
18992                }
18993                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
18994            }
18995            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
18996                let mut new_args = Vec::with_capacity(f.args.len());
18997                for (i, arg) in f.args.iter().enumerate() {
18998                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
18999                        // Wrap the value in an Alias with the inherited name
19000                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
19001                            this: arg.clone(),
19002                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
19003                            column_aliases: Vec::new(),
19004                            pre_alias_comments: Vec::new(),
19005                            trailing_comments: Vec::new(),
19006                        })));
19007                    } else {
19008                        new_args.push(arg.clone());
19009                    }
19010                }
19011                Expression::Function(Box::new(crate::expressions::Function {
19012                    name: f.name.clone(),
19013                    args: new_args,
19014                    distinct: f.distinct,
19015                    trailing_comments: f.trailing_comments.clone(),
19016                    use_bracket_syntax: f.use_bracket_syntax,
19017                    no_parens: f.no_parens,
19018                    quoted: f.quoted,
19019                    span: None,
19020                }))
19021            }
19022            _ => expr.clone(),
19023        }
19024    }
19025
19026    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
19027    /// This implements BigQuery's implicit field name inheritance for struct arrays.
19028    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
19029    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
19030        let first = match expressions.first() {
19031            Some(e) => e,
19032            None => return expressions.to_vec(),
19033        };
19034
19035        let field_names = match Self::extract_struct_field_names(first) {
19036            Some(names) if !names.is_empty() => names,
19037            _ => return expressions.to_vec(),
19038        };
19039
19040        let mut result = Vec::with_capacity(expressions.len());
19041        for (idx, expr) in expressions.iter().enumerate() {
19042            if idx == 0 {
19043                result.push(expr.clone());
19044                continue;
19045            }
19046            // Check if this is a struct with unnamed fields that needs name propagation
19047            if Self::struct_field_count(expr) == field_names.len()
19048                && Self::struct_has_unnamed_fields(expr)
19049            {
19050                result.push(Self::apply_struct_field_names(expr, &field_names));
19051            } else {
19052                result.push(expr.clone());
19053            }
19054        }
19055        result
19056    }
19057
19058    // Array function generators
19059
19060    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
19061        // Apply struct name inheritance for target dialects that need it
19062        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
19063        let needs_inheritance = matches!(
19064            self.config.dialect,
19065            Some(DialectType::DuckDB)
19066                | Some(DialectType::Spark)
19067                | Some(DialectType::Databricks)
19068                | Some(DialectType::Hive)
19069                | Some(DialectType::Snowflake)
19070                | Some(DialectType::Presto)
19071                | Some(DialectType::Trino)
19072        );
19073        let propagated: Vec<Expression>;
19074        let expressions = if needs_inheritance && f.expressions.len() > 1 {
19075            propagated = Self::inherit_struct_field_names(&f.expressions);
19076            &propagated
19077        } else {
19078            &f.expressions
19079        };
19080
19081        // Check if elements should be split onto multiple lines (pretty + too wide)
19082        let should_split = if self.config.pretty && !expressions.is_empty() {
19083            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
19084            for expr in expressions {
19085                let mut temp_gen = Generator::with_config(self.config.clone());
19086                temp_gen.config.pretty = false;
19087                temp_gen.generate_expression(expr)?;
19088                expr_strings.push(temp_gen.output);
19089            }
19090            self.too_wide(&expr_strings)
19091        } else {
19092            false
19093        };
19094
19095        if f.bracket_notation {
19096            // For Spark/Databricks, use ARRAY(...) with parens
19097            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
19098            // For others (DuckDB, Snowflake), use bare [...]
19099            let (open, close) = match self.config.dialect {
19100                None
19101                | Some(DialectType::Generic)
19102                | Some(DialectType::Spark)
19103                | Some(DialectType::Databricks)
19104                | Some(DialectType::Hive) => {
19105                    self.write_keyword("ARRAY");
19106                    ("(", ")")
19107                }
19108                Some(DialectType::Presto)
19109                | Some(DialectType::Trino)
19110                | Some(DialectType::PostgreSQL)
19111                | Some(DialectType::Redshift)
19112                | Some(DialectType::Materialize)
19113                | Some(DialectType::RisingWave)
19114                | Some(DialectType::CockroachDB) => {
19115                    self.write_keyword("ARRAY");
19116                    ("[", "]")
19117                }
19118                _ => ("[", "]"),
19119            };
19120            self.write(open);
19121            if should_split {
19122                self.write_newline();
19123                self.indent_level += 1;
19124                for (i, expr) in expressions.iter().enumerate() {
19125                    self.write_indent();
19126                    self.generate_expression(expr)?;
19127                    if i + 1 < expressions.len() {
19128                        self.write(",");
19129                    }
19130                    self.write_newline();
19131                }
19132                self.indent_level -= 1;
19133                self.write_indent();
19134            } else {
19135                for (i, expr) in expressions.iter().enumerate() {
19136                    if i > 0 {
19137                        self.write(", ");
19138                    }
19139                    self.generate_expression(expr)?;
19140                }
19141            }
19142            self.write(close);
19143        } else {
19144            // Use LIST keyword if that was the original syntax (DuckDB)
19145            if f.use_list_keyword {
19146                self.write_keyword("LIST");
19147            } else {
19148                self.write_keyword("ARRAY");
19149            }
19150            // For Spark/Hive, always use ARRAY(...) with parens
19151            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
19152            let has_subquery = expressions
19153                .iter()
19154                .any(|e| matches!(e, Expression::Select(_)));
19155            let (open, close) = if matches!(
19156                self.config.dialect,
19157                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
19158            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
19159                && has_subquery)
19160            {
19161                ("(", ")")
19162            } else {
19163                ("[", "]")
19164            };
19165            self.write(open);
19166            if should_split {
19167                self.write_newline();
19168                self.indent_level += 1;
19169                for (i, expr) in expressions.iter().enumerate() {
19170                    self.write_indent();
19171                    self.generate_expression(expr)?;
19172                    if i + 1 < expressions.len() {
19173                        self.write(",");
19174                    }
19175                    self.write_newline();
19176                }
19177                self.indent_level -= 1;
19178                self.write_indent();
19179            } else {
19180                for (i, expr) in expressions.iter().enumerate() {
19181                    if i > 0 {
19182                        self.write(", ");
19183                    }
19184                    self.generate_expression(expr)?;
19185                }
19186            }
19187            self.write(close);
19188        }
19189        Ok(())
19190    }
19191
19192    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
19193        self.write_keyword("ARRAY_SORT");
19194        self.write("(");
19195        self.generate_expression(&f.this)?;
19196        if let Some(ref comp) = f.comparator {
19197            self.write(", ");
19198            self.generate_expression(comp)?;
19199        }
19200        self.write(")");
19201        Ok(())
19202    }
19203
19204    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
19205        self.write_keyword(name);
19206        self.write("(");
19207        self.generate_expression(&f.this)?;
19208        self.write(", ");
19209        self.generate_expression(&f.separator)?;
19210        if let Some(ref null_rep) = f.null_replacement {
19211            self.write(", ");
19212            self.generate_expression(null_rep)?;
19213        }
19214        self.write(")");
19215        Ok(())
19216    }
19217
19218    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
19219        self.write_keyword("UNNEST");
19220        self.write("(");
19221        self.generate_expression(&f.this)?;
19222        for extra in &f.expressions {
19223            self.write(", ");
19224            self.generate_expression(extra)?;
19225        }
19226        self.write(")");
19227        if f.with_ordinality {
19228            self.write_space();
19229            if self.config.unnest_with_ordinality {
19230                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
19231                self.write_keyword("WITH ORDINALITY");
19232            } else if f.offset_alias.is_some() {
19233                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
19234                // Alias (if any) comes BEFORE WITH OFFSET
19235                if let Some(ref alias) = f.alias {
19236                    self.write_keyword("AS");
19237                    self.write_space();
19238                    self.generate_identifier(alias)?;
19239                    self.write_space();
19240                }
19241                self.write_keyword("WITH OFFSET");
19242                if let Some(ref offset_alias) = f.offset_alias {
19243                    self.write_space();
19244                    self.write_keyword("AS");
19245                    self.write_space();
19246                    self.generate_identifier(offset_alias)?;
19247                }
19248            } else {
19249                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
19250                self.write_keyword("WITH OFFSET");
19251                if f.alias.is_none() {
19252                    self.write(" AS offset");
19253                }
19254            }
19255        }
19256        if let Some(ref alias) = f.alias {
19257            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
19258            let should_add_alias = if !f.with_ordinality {
19259                true
19260            } else if self.config.unnest_with_ordinality {
19261                // Presto/Trino: alias comes after WITH ORDINALITY
19262                true
19263            } else if f.offset_alias.is_some() {
19264                // BigQuery expansion: alias already handled above
19265                false
19266            } else {
19267                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
19268                true
19269            };
19270            if should_add_alias {
19271                self.write_space();
19272                self.write_keyword("AS");
19273                self.write_space();
19274                self.generate_identifier(alias)?;
19275            }
19276        }
19277        Ok(())
19278    }
19279
19280    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
19281        self.write_keyword("FILTER");
19282        self.write("(");
19283        self.generate_expression(&f.this)?;
19284        self.write(", ");
19285        self.generate_expression(&f.filter)?;
19286        self.write(")");
19287        Ok(())
19288    }
19289
19290    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
19291        self.write_keyword("TRANSFORM");
19292        self.write("(");
19293        self.generate_expression(&f.this)?;
19294        self.write(", ");
19295        self.generate_expression(&f.transform)?;
19296        self.write(")");
19297        Ok(())
19298    }
19299
19300    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
19301        self.write_keyword(name);
19302        self.write("(");
19303        self.generate_expression(&f.start)?;
19304        self.write(", ");
19305        self.generate_expression(&f.stop)?;
19306        if let Some(ref step) = f.step {
19307            self.write(", ");
19308            self.generate_expression(step)?;
19309        }
19310        self.write(")");
19311        Ok(())
19312    }
19313
19314    // Struct function generators
19315
19316    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
19317        self.write_keyword("STRUCT");
19318        self.write("(");
19319        for (i, (name, expr)) in f.fields.iter().enumerate() {
19320            if i > 0 {
19321                self.write(", ");
19322            }
19323            if let Some(ref id) = name {
19324                self.generate_identifier(id)?;
19325                self.write(" ");
19326                self.write_keyword("AS");
19327                self.write(" ");
19328            }
19329            self.generate_expression(expr)?;
19330        }
19331        self.write(")");
19332        Ok(())
19333    }
19334
19335    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
19336    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
19337        // Extract named/unnamed fields from function args
19338        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
19339        let mut names: Vec<Option<String>> = Vec::new();
19340        let mut values: Vec<&Expression> = Vec::new();
19341        let mut all_named = true;
19342
19343        for arg in &func.args {
19344            match arg {
19345                Expression::Alias(a) => {
19346                    names.push(Some(a.alias.name.clone()));
19347                    values.push(&a.this);
19348                }
19349                _ => {
19350                    names.push(None);
19351                    values.push(arg);
19352                    all_named = false;
19353                }
19354            }
19355        }
19356
19357        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
19358            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
19359            self.write("{");
19360            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19361                if i > 0 {
19362                    self.write(", ");
19363                }
19364                if let Some(n) = name {
19365                    self.write("'");
19366                    self.write(n);
19367                    self.write("'");
19368                } else {
19369                    self.write("'_");
19370                    self.write(&i.to_string());
19371                    self.write("'");
19372                }
19373                self.write(": ");
19374                self.generate_expression(value)?;
19375            }
19376            self.write("}");
19377            return Ok(());
19378        }
19379
19380        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
19381            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
19382            self.write_keyword("OBJECT_CONSTRUCT");
19383            self.write("(");
19384            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19385                if i > 0 {
19386                    self.write(", ");
19387                }
19388                if let Some(n) = name {
19389                    self.write("'");
19390                    self.write(n);
19391                    self.write("'");
19392                } else {
19393                    self.write("'_");
19394                    self.write(&i.to_string());
19395                    self.write("'");
19396                }
19397                self.write(", ");
19398                self.generate_expression(value)?;
19399            }
19400            self.write(")");
19401            return Ok(());
19402        }
19403
19404        if matches!(
19405            self.config.dialect,
19406            Some(DialectType::Presto) | Some(DialectType::Trino)
19407        ) {
19408            if all_named && !names.is_empty() {
19409                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
19410                // Need to infer types from values
19411                self.write_keyword("CAST");
19412                self.write("(");
19413                self.write_keyword("ROW");
19414                self.write("(");
19415                for (i, value) in values.iter().enumerate() {
19416                    if i > 0 {
19417                        self.write(", ");
19418                    }
19419                    self.generate_expression(value)?;
19420                }
19421                self.write(")");
19422                self.write(" ");
19423                self.write_keyword("AS");
19424                self.write(" ");
19425                self.write_keyword("ROW");
19426                self.write("(");
19427                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19428                    if i > 0 {
19429                        self.write(", ");
19430                    }
19431                    if let Some(n) = name {
19432                        self.write(n);
19433                    }
19434                    self.write(" ");
19435                    let type_str = Self::infer_sql_type_for_presto(value);
19436                    self.write_keyword(&type_str);
19437                }
19438                self.write(")");
19439                self.write(")");
19440            } else {
19441                // Unnamed: ROW(values...)
19442                self.write_keyword("ROW");
19443                self.write("(");
19444                for (i, value) in values.iter().enumerate() {
19445                    if i > 0 {
19446                        self.write(", ");
19447                    }
19448                    self.generate_expression(value)?;
19449                }
19450                self.write(")");
19451            }
19452            return Ok(());
19453        }
19454
19455        // Default: ROW(values...) for other dialects
19456        self.write_keyword("ROW");
19457        self.write("(");
19458        for (i, value) in values.iter().enumerate() {
19459            if i > 0 {
19460                self.write(", ");
19461            }
19462            self.generate_expression(value)?;
19463        }
19464        self.write(")");
19465        Ok(())
19466    }
19467
19468    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
19469    fn infer_sql_type_for_presto(expr: &Expression) -> String {
19470        match expr {
19471            Expression::Literal(crate::expressions::Literal::String(_)) => "VARCHAR".to_string(),
19472            Expression::Literal(crate::expressions::Literal::Number(n)) => {
19473                if n.contains('.') {
19474                    "DOUBLE".to_string()
19475                } else {
19476                    "INTEGER".to_string()
19477                }
19478            }
19479            Expression::Boolean(_) => "BOOLEAN".to_string(),
19480            Expression::Literal(crate::expressions::Literal::Date(_)) => "DATE".to_string(),
19481            Expression::Literal(crate::expressions::Literal::Timestamp(_)) => {
19482                "TIMESTAMP".to_string()
19483            }
19484            Expression::Literal(crate::expressions::Literal::Datetime(_)) => {
19485                "TIMESTAMP".to_string()
19486            }
19487            Expression::Array(_) | Expression::ArrayFunc(_) => {
19488                // Try to infer element type from first element
19489                "ARRAY(VARCHAR)".to_string()
19490            }
19491            // For nested structs - generate a nested ROW type by inspecting fields
19492            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
19493            Expression::Function(f) => {
19494                let up = f.name.to_uppercase();
19495                if up == "STRUCT" {
19496                    "ROW".to_string()
19497                } else if up == "CURRENT_DATE" {
19498                    "DATE".to_string()
19499                } else if up == "CURRENT_TIMESTAMP" || up == "NOW" {
19500                    "TIMESTAMP".to_string()
19501                } else {
19502                    "VARCHAR".to_string()
19503                }
19504            }
19505            _ => "VARCHAR".to_string(),
19506        }
19507    }
19508
19509    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
19510        // DuckDB uses STRUCT_EXTRACT function syntax
19511        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
19512            self.write_keyword("STRUCT_EXTRACT");
19513            self.write("(");
19514            self.generate_expression(&f.this)?;
19515            self.write(", ");
19516            // Output field name as string literal
19517            self.write("'");
19518            self.write(&f.field.name);
19519            self.write("'");
19520            self.write(")");
19521            return Ok(());
19522        }
19523        self.generate_expression(&f.this)?;
19524        self.write(".");
19525        self.generate_identifier(&f.field)
19526    }
19527
19528    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
19529        self.write_keyword("NAMED_STRUCT");
19530        self.write("(");
19531        for (i, (name, value)) in f.pairs.iter().enumerate() {
19532            if i > 0 {
19533                self.write(", ");
19534            }
19535            self.generate_expression(name)?;
19536            self.write(", ");
19537            self.generate_expression(value)?;
19538        }
19539        self.write(")");
19540        Ok(())
19541    }
19542
19543    // Map function generators
19544
19545    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
19546        if f.curly_brace_syntax {
19547            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
19548            if f.with_map_keyword {
19549                self.write_keyword("MAP");
19550                self.write(" ");
19551            }
19552            self.write("{");
19553            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
19554                if i > 0 {
19555                    self.write(", ");
19556                }
19557                self.generate_expression(key)?;
19558                self.write(": ");
19559                self.generate_expression(val)?;
19560            }
19561            self.write("}");
19562        } else {
19563            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
19564            self.write_keyword("MAP");
19565            self.write("(");
19566            self.write_keyword("ARRAY");
19567            self.write("[");
19568            for (i, key) in f.keys.iter().enumerate() {
19569                if i > 0 {
19570                    self.write(", ");
19571                }
19572                self.generate_expression(key)?;
19573            }
19574            self.write("], ");
19575            self.write_keyword("ARRAY");
19576            self.write("[");
19577            for (i, val) in f.values.iter().enumerate() {
19578                if i > 0 {
19579                    self.write(", ");
19580                }
19581                self.generate_expression(val)?;
19582            }
19583            self.write("])");
19584        }
19585        Ok(())
19586    }
19587
19588    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
19589        self.write_keyword(name);
19590        self.write("(");
19591        self.generate_expression(&f.this)?;
19592        self.write(", ");
19593        self.generate_expression(&f.transform)?;
19594        self.write(")");
19595        Ok(())
19596    }
19597
19598    // JSON function generators
19599
19600    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
19601        use crate::dialects::DialectType;
19602
19603        // Check if we should use arrow syntax (-> or ->>)
19604        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
19605
19606        if use_arrow {
19607            // Output arrow syntax: expr -> path or expr ->> path
19608            self.generate_expression(&f.this)?;
19609            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
19610                self.write(" ->> ");
19611            } else {
19612                self.write(" -> ");
19613            }
19614            self.generate_expression(&f.path)?;
19615            return Ok(());
19616        }
19617
19618        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
19619        if f.hash_arrow_syntax
19620            && matches!(
19621                self.config.dialect,
19622                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19623            )
19624        {
19625            self.generate_expression(&f.this)?;
19626            self.write(" #>> ");
19627            self.generate_expression(&f.path)?;
19628            return Ok(());
19629        }
19630
19631        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
19632        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
19633        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
19634            match name {
19635                "JSON_EXTRACT_SCALAR"
19636                | "JSON_EXTRACT_PATH_TEXT"
19637                | "JSON_EXTRACT"
19638                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
19639                _ => name,
19640            }
19641        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
19642            match name {
19643                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
19644                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
19645                _ => name,
19646            }
19647        } else {
19648            name
19649        };
19650
19651        self.write_keyword(func_name);
19652        self.write("(");
19653        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
19654        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
19655            if let Expression::Cast(ref cast) = f.this {
19656                if matches!(cast.to, crate::expressions::DataType::Json) {
19657                    self.generate_expression(&cast.this)?;
19658                } else {
19659                    self.generate_expression(&f.this)?;
19660                }
19661            } else {
19662                self.generate_expression(&f.this)?;
19663            }
19664        } else {
19665            self.generate_expression(&f.this)?;
19666        }
19667        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
19668        // decompose JSON path into separate string arguments
19669        if matches!(
19670            self.config.dialect,
19671            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19672        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
19673        {
19674            if let Expression::Literal(Literal::String(ref s)) = f.path {
19675                let parts = Self::decompose_json_path(s);
19676                for part in &parts {
19677                    self.write(", '");
19678                    self.write(part);
19679                    self.write("'");
19680                }
19681            } else {
19682                self.write(", ");
19683                self.generate_expression(&f.path)?;
19684            }
19685        } else {
19686            self.write(", ");
19687            self.generate_expression(&f.path)?;
19688        }
19689
19690        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
19691        // These go BEFORE the closing parenthesis
19692        if let Some(ref wrapper) = f.wrapper_option {
19693            self.write_space();
19694            self.write_keyword(wrapper);
19695        }
19696        if let Some(ref quotes) = f.quotes_option {
19697            self.write_space();
19698            self.write_keyword(quotes);
19699            if f.on_scalar_string {
19700                self.write_space();
19701                self.write_keyword("ON SCALAR STRING");
19702            }
19703        }
19704        if let Some(ref on_err) = f.on_error {
19705            self.write_space();
19706            self.write_keyword(on_err);
19707        }
19708        if let Some(ref ret_type) = f.returning {
19709            self.write_space();
19710            self.write_keyword("RETURNING");
19711            self.write_space();
19712            self.generate_data_type(ret_type)?;
19713        }
19714
19715        self.write(")");
19716        Ok(())
19717    }
19718
19719    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
19720    fn dialect_supports_json_arrow(&self) -> bool {
19721        use crate::dialects::DialectType;
19722        match self.config.dialect {
19723            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
19724            Some(DialectType::PostgreSQL) => true,
19725            Some(DialectType::MySQL) => true,
19726            Some(DialectType::DuckDB) => true,
19727            Some(DialectType::CockroachDB) => true,
19728            Some(DialectType::StarRocks) => true,
19729            Some(DialectType::SQLite) => true,
19730            // Other dialects use function syntax
19731            _ => false,
19732        }
19733    }
19734
19735    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
19736        use crate::dialects::DialectType;
19737
19738        // PostgreSQL uses #> operator for JSONB path extraction
19739        if matches!(
19740            self.config.dialect,
19741            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19742        ) && name == "JSON_EXTRACT_PATH"
19743        {
19744            self.generate_expression(&f.this)?;
19745            self.write(" #> ");
19746            if f.paths.len() == 1 {
19747                self.generate_expression(&f.paths[0])?;
19748            } else {
19749                // Multiple paths: ARRAY[path1, path2, ...]
19750                self.write_keyword("ARRAY");
19751                self.write("[");
19752                for (i, path) in f.paths.iter().enumerate() {
19753                    if i > 0 {
19754                        self.write(", ");
19755                    }
19756                    self.generate_expression(path)?;
19757                }
19758                self.write("]");
19759            }
19760            return Ok(());
19761        }
19762
19763        self.write_keyword(name);
19764        self.write("(");
19765        self.generate_expression(&f.this)?;
19766        for path in &f.paths {
19767            self.write(", ");
19768            self.generate_expression(path)?;
19769        }
19770        self.write(")");
19771        Ok(())
19772    }
19773
19774    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
19775        use crate::dialects::DialectType;
19776
19777        self.write_keyword("JSON_OBJECT");
19778        self.write("(");
19779        if f.star {
19780            self.write("*");
19781        } else {
19782            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
19783            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
19784            // Also respect the json_key_value_pair_sep config
19785            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
19786                || matches!(
19787                    self.config.dialect,
19788                    Some(DialectType::BigQuery)
19789                        | Some(DialectType::MySQL)
19790                        | Some(DialectType::SQLite)
19791                );
19792
19793            for (i, (key, value)) in f.pairs.iter().enumerate() {
19794                if i > 0 {
19795                    self.write(", ");
19796                }
19797                self.generate_expression(key)?;
19798                if use_comma_syntax {
19799                    self.write(", ");
19800                } else {
19801                    self.write(": ");
19802                }
19803                self.generate_expression(value)?;
19804            }
19805        }
19806        if let Some(null_handling) = f.null_handling {
19807            self.write_space();
19808            match null_handling {
19809                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
19810                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
19811            }
19812        }
19813        if f.with_unique_keys {
19814            self.write_space();
19815            self.write_keyword("WITH UNIQUE KEYS");
19816        }
19817        if let Some(ref ret_type) = f.returning_type {
19818            self.write_space();
19819            self.write_keyword("RETURNING");
19820            self.write_space();
19821            self.generate_data_type(ret_type)?;
19822            if f.format_json {
19823                self.write_space();
19824                self.write_keyword("FORMAT JSON");
19825            }
19826            if let Some(ref enc) = f.encoding {
19827                self.write_space();
19828                self.write_keyword("ENCODING");
19829                self.write_space();
19830                self.write(enc);
19831            }
19832        }
19833        self.write(")");
19834        Ok(())
19835    }
19836
19837    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
19838        self.write_keyword(name);
19839        self.write("(");
19840        self.generate_expression(&f.this)?;
19841        for (path, value) in &f.path_values {
19842            self.write(", ");
19843            self.generate_expression(path)?;
19844            self.write(", ");
19845            self.generate_expression(value)?;
19846        }
19847        self.write(")");
19848        Ok(())
19849    }
19850
19851    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
19852        self.write_keyword("JSON_ARRAYAGG");
19853        self.write("(");
19854        self.generate_expression(&f.this)?;
19855        if let Some(ref order_by) = f.order_by {
19856            self.write_space();
19857            self.write_keyword("ORDER BY");
19858            self.write_space();
19859            for (i, ord) in order_by.iter().enumerate() {
19860                if i > 0 {
19861                    self.write(", ");
19862                }
19863                self.generate_ordered(ord)?;
19864            }
19865        }
19866        if let Some(null_handling) = f.null_handling {
19867            self.write_space();
19868            match null_handling {
19869                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
19870                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
19871            }
19872        }
19873        self.write(")");
19874        if let Some(ref filter) = f.filter {
19875            self.write_space();
19876            self.write_keyword("FILTER");
19877            self.write("(");
19878            self.write_keyword("WHERE");
19879            self.write_space();
19880            self.generate_expression(filter)?;
19881            self.write(")");
19882        }
19883        Ok(())
19884    }
19885
19886    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
19887        self.write_keyword("JSON_OBJECTAGG");
19888        self.write("(");
19889        self.generate_expression(&f.key)?;
19890        self.write(": ");
19891        self.generate_expression(&f.value)?;
19892        if let Some(null_handling) = f.null_handling {
19893            self.write_space();
19894            match null_handling {
19895                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
19896                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
19897            }
19898        }
19899        self.write(")");
19900        if let Some(ref filter) = f.filter {
19901            self.write_space();
19902            self.write_keyword("FILTER");
19903            self.write("(");
19904            self.write_keyword("WHERE");
19905            self.write_space();
19906            self.generate_expression(filter)?;
19907            self.write(")");
19908        }
19909        Ok(())
19910    }
19911
19912    // Type casting/conversion generators
19913
19914    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
19915        use crate::dialects::DialectType;
19916
19917        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
19918        if self.config.dialect == Some(DialectType::Redshift) {
19919            self.write_keyword("CAST");
19920            self.write("(");
19921            self.generate_expression(&f.this)?;
19922            self.write_space();
19923            self.write_keyword("AS");
19924            self.write_space();
19925            self.generate_data_type(&f.to)?;
19926            self.write(")");
19927            return Ok(());
19928        }
19929
19930        self.write_keyword("CONVERT");
19931        self.write("(");
19932        self.generate_data_type(&f.to)?;
19933        self.write(", ");
19934        self.generate_expression(&f.this)?;
19935        if let Some(ref style) = f.style {
19936            self.write(", ");
19937            self.generate_expression(style)?;
19938        }
19939        self.write(")");
19940        Ok(())
19941    }
19942
19943    // Additional expression generators
19944
19945    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
19946        if f.colon {
19947            // DuckDB syntax: LAMBDA x : expr
19948            self.write_keyword("LAMBDA");
19949            self.write_space();
19950            for (i, param) in f.parameters.iter().enumerate() {
19951                if i > 0 {
19952                    self.write(", ");
19953                }
19954                self.generate_identifier(param)?;
19955            }
19956            self.write(" : ");
19957        } else {
19958            // Standard syntax: x -> expr or (x, y) -> expr
19959            if f.parameters.len() == 1 {
19960                self.generate_identifier(&f.parameters[0])?;
19961            } else {
19962                self.write("(");
19963                for (i, param) in f.parameters.iter().enumerate() {
19964                    if i > 0 {
19965                        self.write(", ");
19966                    }
19967                    self.generate_identifier(param)?;
19968                }
19969                self.write(")");
19970            }
19971            self.write(" -> ");
19972        }
19973        self.generate_expression(&f.body)
19974    }
19975
19976    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
19977        self.generate_identifier(&f.name)?;
19978        match f.separator {
19979            NamedArgSeparator::DArrow => self.write(" => "),
19980            NamedArgSeparator::ColonEq => self.write(" := "),
19981            NamedArgSeparator::Eq => self.write(" = "),
19982        }
19983        self.generate_expression(&f.value)
19984    }
19985
19986    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
19987        self.write_keyword(&f.prefix);
19988        self.write(" ");
19989        self.generate_expression(&f.this)
19990    }
19991
19992    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
19993        match f.style {
19994            ParameterStyle::Question => self.write("?"),
19995            ParameterStyle::Dollar => {
19996                self.write("$");
19997                if let Some(idx) = f.index {
19998                    self.write(&idx.to_string());
19999                } else if let Some(ref name) = f.name {
20000                    // Session variable like $x or $query_id
20001                    self.write(name);
20002                }
20003            }
20004            ParameterStyle::DollarBrace => {
20005                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
20006                self.write("${");
20007                if let Some(ref name) = f.name {
20008                    self.write(name);
20009                }
20010                if let Some(ref expr) = f.expression {
20011                    self.write(":");
20012                    self.write(expr);
20013                }
20014                self.write("}");
20015            }
20016            ParameterStyle::Colon => {
20017                self.write(":");
20018                if let Some(idx) = f.index {
20019                    self.write(&idx.to_string());
20020                } else if let Some(ref name) = f.name {
20021                    self.write(name);
20022                }
20023            }
20024            ParameterStyle::At => {
20025                self.write("@");
20026                if let Some(ref name) = f.name {
20027                    if f.string_quoted {
20028                        self.write("'");
20029                        self.write(name);
20030                        self.write("'");
20031                    } else if f.quoted {
20032                        self.write("\"");
20033                        self.write(name);
20034                        self.write("\"");
20035                    } else {
20036                        self.write(name);
20037                    }
20038                }
20039            }
20040            ParameterStyle::DoubleAt => {
20041                self.write("@@");
20042                if let Some(ref name) = f.name {
20043                    self.write(name);
20044                }
20045            }
20046            ParameterStyle::DoubleDollar => {
20047                self.write("$$");
20048                if let Some(ref name) = f.name {
20049                    self.write(name);
20050                }
20051            }
20052            ParameterStyle::Percent => {
20053                if let Some(ref name) = f.name {
20054                    // %(name)s format
20055                    self.write("%(");
20056                    self.write(name);
20057                    self.write(")s");
20058                } else {
20059                    // %s format
20060                    self.write("%s");
20061                }
20062            }
20063            ParameterStyle::Brace => {
20064                // Spark/Databricks widget template variable: {name}
20065                // ClickHouse query parameter may include kind: {name: Type}
20066                self.write("{");
20067                if let Some(ref name) = f.name {
20068                    self.write(name);
20069                }
20070                if let Some(ref expr) = f.expression {
20071                    self.write(": ");
20072                    self.write(expr);
20073                }
20074                self.write("}");
20075            }
20076        }
20077        Ok(())
20078    }
20079
20080    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
20081        self.write("?");
20082        if let Some(idx) = f.index {
20083            self.write(&idx.to_string());
20084        }
20085        Ok(())
20086    }
20087
20088    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
20089        if f.is_block {
20090            self.write("/*");
20091            self.write(&f.text);
20092            self.write("*/");
20093        } else {
20094            self.write("--");
20095            self.write(&f.text);
20096        }
20097        Ok(())
20098    }
20099
20100    // Additional predicate generators
20101
20102    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
20103        self.generate_expression(&f.this)?;
20104        if f.not {
20105            self.write_space();
20106            self.write_keyword("NOT");
20107        }
20108        self.write_space();
20109        self.write_keyword("SIMILAR TO");
20110        self.write_space();
20111        self.generate_expression(&f.pattern)?;
20112        if let Some(ref escape) = f.escape {
20113            self.write_space();
20114            self.write_keyword("ESCAPE");
20115            self.write_space();
20116            self.generate_expression(escape)?;
20117        }
20118        Ok(())
20119    }
20120
20121    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
20122        self.generate_expression(&f.this)?;
20123        self.write_space();
20124        // Output comparison operator if present
20125        if let Some(op) = &f.op {
20126            match op {
20127                QuantifiedOp::Eq => self.write("="),
20128                QuantifiedOp::Neq => self.write("<>"),
20129                QuantifiedOp::Lt => self.write("<"),
20130                QuantifiedOp::Lte => self.write("<="),
20131                QuantifiedOp::Gt => self.write(">"),
20132                QuantifiedOp::Gte => self.write(">="),
20133            }
20134            self.write_space();
20135        }
20136        self.write_keyword(name);
20137
20138        // If the child is a Subquery, it provides its own parens — output with space
20139        if matches!(&f.subquery, Expression::Subquery(_)) {
20140            self.write_space();
20141            self.generate_expression(&f.subquery)?;
20142        } else {
20143            self.write("(");
20144
20145            let is_statement = matches!(
20146                &f.subquery,
20147                Expression::Select(_)
20148                    | Expression::Union(_)
20149                    | Expression::Intersect(_)
20150                    | Expression::Except(_)
20151            );
20152
20153            if self.config.pretty && is_statement {
20154                self.write_newline();
20155                self.indent_level += 1;
20156                self.write_indent();
20157            }
20158            self.generate_expression(&f.subquery)?;
20159            if self.config.pretty && is_statement {
20160                self.write_newline();
20161                self.indent_level -= 1;
20162                self.write_indent();
20163            }
20164            self.write(")");
20165        }
20166        Ok(())
20167    }
20168
20169    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
20170        // Check if this is a simple binary form (this OVERLAPS expression)
20171        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
20172            self.generate_expression(this)?;
20173            self.write_space();
20174            self.write_keyword("OVERLAPS");
20175            self.write_space();
20176            self.generate_expression(expr)?;
20177        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
20178            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
20179        {
20180            // Full ANSI form: (a, b) OVERLAPS (c, d)
20181            self.write("(");
20182            self.generate_expression(ls)?;
20183            self.write(", ");
20184            self.generate_expression(le)?;
20185            self.write(")");
20186            self.write_space();
20187            self.write_keyword("OVERLAPS");
20188            self.write_space();
20189            self.write("(");
20190            self.generate_expression(rs)?;
20191            self.write(", ");
20192            self.generate_expression(re)?;
20193            self.write(")");
20194        }
20195        Ok(())
20196    }
20197
20198    // Type conversion generators
20199
20200    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
20201        use crate::dialects::DialectType;
20202
20203        // SingleStore uses !:> syntax for try cast
20204        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
20205            self.generate_expression(&cast.this)?;
20206            self.write(" !:> ");
20207            self.generate_data_type(&cast.to)?;
20208            return Ok(());
20209        }
20210
20211        // Teradata uses TRYCAST (no underscore)
20212        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
20213            self.write_keyword("TRYCAST");
20214            self.write("(");
20215            self.generate_expression(&cast.this)?;
20216            self.write_space();
20217            self.write_keyword("AS");
20218            self.write_space();
20219            self.generate_data_type(&cast.to)?;
20220            self.write(")");
20221            return Ok(());
20222        }
20223
20224        // Dialects without TRY_CAST: generate as regular CAST
20225        let keyword = if matches!(
20226            self.config.dialect,
20227            Some(DialectType::Hive)
20228                | Some(DialectType::MySQL)
20229                | Some(DialectType::SQLite)
20230                | Some(DialectType::Oracle)
20231                | Some(DialectType::ClickHouse)
20232                | Some(DialectType::Redshift)
20233                | Some(DialectType::PostgreSQL)
20234                | Some(DialectType::StarRocks)
20235                | Some(DialectType::Doris)
20236        ) {
20237            "CAST"
20238        } else {
20239            "TRY_CAST"
20240        };
20241
20242        self.write_keyword(keyword);
20243        self.write("(");
20244        self.generate_expression(&cast.this)?;
20245        self.write_space();
20246        self.write_keyword("AS");
20247        self.write_space();
20248        self.generate_data_type(&cast.to)?;
20249
20250        // Output FORMAT clause if present
20251        if let Some(format) = &cast.format {
20252            self.write_space();
20253            self.write_keyword("FORMAT");
20254            self.write_space();
20255            self.generate_expression(format)?;
20256        }
20257
20258        self.write(")");
20259        Ok(())
20260    }
20261
20262    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
20263        self.write_keyword("SAFE_CAST");
20264        self.write("(");
20265        self.generate_expression(&cast.this)?;
20266        self.write_space();
20267        self.write_keyword("AS");
20268        self.write_space();
20269        self.generate_data_type(&cast.to)?;
20270
20271        // Output FORMAT clause if present
20272        if let Some(format) = &cast.format {
20273            self.write_space();
20274            self.write_keyword("FORMAT");
20275            self.write_space();
20276            self.generate_expression(format)?;
20277        }
20278
20279        self.write(")");
20280        Ok(())
20281    }
20282
20283    // Array/struct/map access generators
20284
20285    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
20286        self.generate_expression(&s.this)?;
20287        self.write("[");
20288        self.generate_expression(&s.index)?;
20289        self.write("]");
20290        Ok(())
20291    }
20292
20293    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
20294        self.generate_expression(&d.this)?;
20295        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
20296        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
20297        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
20298            && matches!(
20299                &d.this,
20300                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
20301            );
20302        if use_colon {
20303            self.write(":");
20304        } else {
20305            self.write(".");
20306        }
20307        self.generate_identifier(&d.field)
20308    }
20309
20310    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
20311        self.generate_expression(&m.this)?;
20312        self.write(".");
20313        // Method names after a dot should not be quoted based on reserved keywords
20314        // Only quote if explicitly marked as quoted in the AST
20315        if m.method.quoted {
20316            let q = self.config.identifier_quote;
20317            self.write(&format!("{}{}{}", q, m.method.name, q));
20318        } else {
20319            self.write(&m.method.name);
20320        }
20321        self.write("(");
20322        for (i, arg) in m.args.iter().enumerate() {
20323            if i > 0 {
20324                self.write(", ");
20325            }
20326            self.generate_expression(arg)?;
20327        }
20328        self.write(")");
20329        Ok(())
20330    }
20331
20332    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
20333        // Check if we need to wrap the inner expression in parentheses
20334        // JSON arrow expressions have lower precedence than array subscript
20335        let needs_parens = matches!(
20336            &s.this,
20337            Expression::JsonExtract(f) if f.arrow_syntax
20338        ) || matches!(
20339            &s.this,
20340            Expression::JsonExtractScalar(f) if f.arrow_syntax
20341        );
20342
20343        if needs_parens {
20344            self.write("(");
20345        }
20346        self.generate_expression(&s.this)?;
20347        if needs_parens {
20348            self.write(")");
20349        }
20350        self.write("[");
20351        if let Some(start) = &s.start {
20352            self.generate_expression(start)?;
20353        }
20354        self.write(":");
20355        if let Some(end) = &s.end {
20356            self.generate_expression(end)?;
20357        }
20358        self.write("]");
20359        Ok(())
20360    }
20361
20362    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
20363        // Generate left expression, but skip trailing comments if they're already in left_comments
20364        // to avoid duplication (comments are captured as both expr.trailing_comments
20365        // and BinaryOp.left_comments during parsing)
20366        match &op.left {
20367            Expression::Column(col) => {
20368                // Generate column with trailing comments but skip them if they're
20369                // already captured in BinaryOp.left_comments to avoid duplication
20370                if let Some(table) = &col.table {
20371                    self.generate_identifier(table)?;
20372                    self.write(".");
20373                }
20374                self.generate_identifier(&col.name)?;
20375                // Oracle-style join marker (+)
20376                if col.join_mark && self.config.supports_column_join_marks {
20377                    self.write(" (+)");
20378                }
20379                // Output column trailing comments if they're not already in left_comments
20380                if op.left_comments.is_empty() {
20381                    for comment in &col.trailing_comments {
20382                        self.write_space();
20383                        self.write_formatted_comment(comment);
20384                    }
20385                }
20386            }
20387            Expression::Add(inner_op)
20388            | Expression::Sub(inner_op)
20389            | Expression::Mul(inner_op)
20390            | Expression::Div(inner_op)
20391            | Expression::Concat(inner_op) => {
20392                // Generate binary op without its trailing comments
20393                self.generate_binary_op_no_trailing(inner_op, match &op.left {
20394                    Expression::Add(_) => "+",
20395                    Expression::Sub(_) => "-",
20396                    Expression::Mul(_) => "*",
20397                    Expression::Div(_) => "/",
20398                    Expression::Concat(_) => "||",
20399                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
20400                })?;
20401            }
20402            _ => {
20403                self.generate_expression(&op.left)?;
20404            }
20405        }
20406        // Output comments after left operand
20407        for comment in &op.left_comments {
20408            self.write_space();
20409            self.write_formatted_comment(comment);
20410        }
20411        if self.config.pretty
20412            && matches!(self.config.dialect, Some(DialectType::Snowflake))
20413            && (operator == "AND" || operator == "OR")
20414        {
20415            self.write_newline();
20416            self.write_indent();
20417            self.write_keyword(operator);
20418        } else {
20419            self.write_space();
20420            if operator.chars().all(|c| c.is_alphabetic()) {
20421                self.write_keyword(operator);
20422            } else {
20423                self.write(operator);
20424            }
20425        }
20426        // Output comments after operator (before right operand)
20427        for comment in &op.operator_comments {
20428            self.write_space();
20429            self.write_formatted_comment(comment);
20430        }
20431        self.write_space();
20432        self.generate_expression(&op.right)?;
20433        // Output trailing comments after right operand
20434        for comment in &op.trailing_comments {
20435            self.write_space();
20436            self.write_formatted_comment(comment);
20437        }
20438        Ok(())
20439    }
20440
20441    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
20442        let keyword = connector.keyword();
20443        let Some(terms) = self.flatten_connector_terms(op, connector) else {
20444            return self.generate_binary_op(op, keyword);
20445        };
20446
20447        self.generate_expression(terms[0])?;
20448        for term in terms.iter().skip(1) {
20449            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
20450                self.write_newline();
20451                self.write_indent();
20452                self.write_keyword(keyword);
20453            } else {
20454                self.write_space();
20455                self.write_keyword(keyword);
20456            }
20457            self.write_space();
20458            self.generate_expression(term)?;
20459        }
20460
20461        Ok(())
20462    }
20463
20464    fn flatten_connector_terms<'a>(
20465        &self,
20466        root: &'a BinaryOp,
20467        connector: ConnectorOperator,
20468    ) -> Option<Vec<&'a Expression>> {
20469        if !root.left_comments.is_empty()
20470            || !root.operator_comments.is_empty()
20471            || !root.trailing_comments.is_empty()
20472        {
20473            return None;
20474        }
20475
20476        let mut terms = Vec::new();
20477        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
20478
20479        while let Some(expr) = stack.pop() {
20480            match (connector, expr) {
20481                (ConnectorOperator::And, Expression::And(inner))
20482                    if inner.left_comments.is_empty()
20483                        && inner.operator_comments.is_empty()
20484                        && inner.trailing_comments.is_empty() =>
20485                {
20486                    stack.push(&inner.right);
20487                    stack.push(&inner.left);
20488                }
20489                (ConnectorOperator::Or, Expression::Or(inner))
20490                    if inner.left_comments.is_empty()
20491                        && inner.operator_comments.is_empty()
20492                        && inner.trailing_comments.is_empty() =>
20493                {
20494                    stack.push(&inner.right);
20495                    stack.push(&inner.left);
20496                }
20497                _ => terms.push(expr),
20498            }
20499        }
20500
20501        if terms.len() > 1 {
20502            Some(terms)
20503        } else {
20504            None
20505        }
20506    }
20507
20508    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
20509    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
20510        self.generate_expression(&op.left)?;
20511        self.write_space();
20512        // Drill backtick-quotes ILIKE
20513        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
20514            self.write("`ILIKE`");
20515        } else {
20516            self.write_keyword(operator);
20517        }
20518        if let Some(quantifier) = &op.quantifier {
20519            self.write_space();
20520            self.write_keyword(quantifier);
20521        }
20522        self.write_space();
20523        self.generate_expression(&op.right)?;
20524        if let Some(escape) = &op.escape {
20525            self.write_space();
20526            self.write_keyword("ESCAPE");
20527            self.write_space();
20528            self.generate_expression(escape)?;
20529        }
20530        Ok(())
20531    }
20532
20533    /// Generate null-safe equality
20534    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
20535    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
20536        use crate::dialects::DialectType;
20537        self.generate_expression(&op.left)?;
20538        self.write_space();
20539        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
20540            self.write("<=>");
20541        } else {
20542            self.write_keyword("IS NOT DISTINCT FROM");
20543        }
20544        self.write_space();
20545        self.generate_expression(&op.right)?;
20546        Ok(())
20547    }
20548
20549    /// Generate IS DISTINCT FROM (null-safe inequality)
20550    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
20551        self.generate_expression(&op.left)?;
20552        self.write_space();
20553        self.write_keyword("IS DISTINCT FROM");
20554        self.write_space();
20555        self.generate_expression(&op.right)?;
20556        Ok(())
20557    }
20558
20559    /// Generate binary op without trailing comments (used when nested inside another binary op)
20560    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
20561        // Generate left expression, but skip trailing comments
20562        match &op.left {
20563            Expression::Column(col) => {
20564                if let Some(table) = &col.table {
20565                    self.generate_identifier(table)?;
20566                    self.write(".");
20567                }
20568                self.generate_identifier(&col.name)?;
20569                // Oracle-style join marker (+)
20570                if col.join_mark && self.config.supports_column_join_marks {
20571                    self.write(" (+)");
20572                }
20573            }
20574            Expression::Add(inner_op)
20575            | Expression::Sub(inner_op)
20576            | Expression::Mul(inner_op)
20577            | Expression::Div(inner_op)
20578            | Expression::Concat(inner_op) => {
20579                self.generate_binary_op_no_trailing(inner_op, match &op.left {
20580                    Expression::Add(_) => "+",
20581                    Expression::Sub(_) => "-",
20582                    Expression::Mul(_) => "*",
20583                    Expression::Div(_) => "/",
20584                    Expression::Concat(_) => "||",
20585                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
20586                })?;
20587            }
20588            _ => {
20589                self.generate_expression(&op.left)?;
20590            }
20591        }
20592        // Output left_comments
20593        for comment in &op.left_comments {
20594            self.write_space();
20595            self.write_formatted_comment(comment);
20596        }
20597        self.write_space();
20598        if operator.chars().all(|c| c.is_alphabetic()) {
20599            self.write_keyword(operator);
20600        } else {
20601            self.write(operator);
20602        }
20603        // Output operator_comments
20604        for comment in &op.operator_comments {
20605            self.write_space();
20606            self.write_formatted_comment(comment);
20607        }
20608        self.write_space();
20609        // Generate right expression, but skip trailing comments if it's a Column
20610        // (the parent's left_comments will output them)
20611        match &op.right {
20612            Expression::Column(col) => {
20613                if let Some(table) = &col.table {
20614                    self.generate_identifier(table)?;
20615                    self.write(".");
20616                }
20617                self.generate_identifier(&col.name)?;
20618                // Oracle-style join marker (+)
20619                if col.join_mark && self.config.supports_column_join_marks {
20620                    self.write(" (+)");
20621                }
20622            }
20623            _ => {
20624                self.generate_expression(&op.right)?;
20625            }
20626        }
20627        // Skip trailing_comments - parent will handle them via its left_comments
20628        Ok(())
20629    }
20630
20631    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
20632        if operator.chars().all(|c| c.is_alphabetic()) {
20633            self.write_keyword(operator);
20634            self.write_space();
20635        } else {
20636            self.write(operator);
20637            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
20638            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
20639                self.write_space();
20640            }
20641        }
20642        self.generate_expression(&op.this)
20643    }
20644
20645    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
20646        // Generic mode supports two styles for negated IN:
20647        // - Prefix: NOT a IN (...)
20648        // - Infix:  a NOT IN (...)
20649        let is_generic =
20650            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
20651        let use_prefix_not =
20652            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
20653        if use_prefix_not {
20654            self.write_keyword("NOT");
20655            self.write_space();
20656        }
20657        self.generate_expression(&in_expr.this)?;
20658        if in_expr.global {
20659            self.write_space();
20660            self.write_keyword("GLOBAL");
20661        }
20662        if in_expr.not && !use_prefix_not {
20663            self.write_space();
20664            self.write_keyword("NOT");
20665        }
20666        self.write_space();
20667        self.write_keyword("IN");
20668
20669        // BigQuery: IN UNNEST(expr)
20670        if let Some(unnest_expr) = &in_expr.unnest {
20671            self.write_space();
20672            self.write_keyword("UNNEST");
20673            self.write("(");
20674            self.generate_expression(unnest_expr)?;
20675            self.write(")");
20676            return Ok(());
20677        }
20678
20679        if let Some(query) = &in_expr.query {
20680            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
20681            // vs a subquery (col IN (SELECT ...))
20682            let is_bare = in_expr.expressions.is_empty()
20683                && !matches!(
20684                    query,
20685                    Expression::Select(_)
20686                        | Expression::Union(_)
20687                        | Expression::Intersect(_)
20688                        | Expression::Except(_)
20689                        | Expression::Subquery(_)
20690                );
20691            if is_bare {
20692                // Bare identifier: no parentheses
20693                self.write_space();
20694                self.generate_expression(query)?;
20695            } else {
20696                // Subquery: with parentheses
20697                self.write(" (");
20698                let is_statement = matches!(
20699                    query,
20700                    Expression::Select(_)
20701                        | Expression::Union(_)
20702                        | Expression::Intersect(_)
20703                        | Expression::Except(_)
20704                        | Expression::Subquery(_)
20705                );
20706                if self.config.pretty && is_statement {
20707                    self.write_newline();
20708                    self.indent_level += 1;
20709                    self.write_indent();
20710                }
20711                self.generate_expression(query)?;
20712                if self.config.pretty && is_statement {
20713                    self.write_newline();
20714                    self.indent_level -= 1;
20715                    self.write_indent();
20716                }
20717                self.write(")");
20718            }
20719        } else {
20720            // DuckDB: IN without parentheses for single expression that is NOT a literal
20721            // (array/list membership like 'red' IN tbl.flags)
20722            // ClickHouse: IN without parentheses for single non-array expressions
20723            let is_duckdb = matches!(
20724                self.config.dialect,
20725                Some(crate::dialects::DialectType::DuckDB)
20726            );
20727            let is_clickhouse = matches!(
20728                self.config.dialect,
20729                Some(crate::dialects::DialectType::ClickHouse)
20730            );
20731            let single_expr = in_expr.expressions.len() == 1;
20732            if is_clickhouse && single_expr {
20733                if let Expression::Array(arr) = &in_expr.expressions[0] {
20734                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
20735                    self.write(" (");
20736                    for (i, expr) in arr.expressions.iter().enumerate() {
20737                        if i > 0 {
20738                            self.write(", ");
20739                        }
20740                        self.generate_expression(expr)?;
20741                    }
20742                    self.write(")");
20743                } else {
20744                    self.write_space();
20745                    self.generate_expression(&in_expr.expressions[0])?;
20746                }
20747            } else {
20748                let is_bare_ref = single_expr
20749                    && matches!(
20750                        &in_expr.expressions[0],
20751                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
20752                    );
20753                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
20754                    // Bare field reference (no parens in source): IN identifier
20755                    // Also DuckDB: IN without parentheses for array/list membership
20756                    self.write_space();
20757                    self.generate_expression(&in_expr.expressions[0])?;
20758                } else {
20759                    // Standard IN (list)
20760                    self.write(" (");
20761                    for (i, expr) in in_expr.expressions.iter().enumerate() {
20762                        if i > 0 {
20763                            self.write(", ");
20764                        }
20765                        self.generate_expression(expr)?;
20766                    }
20767                    self.write(")");
20768                }
20769            }
20770        }
20771
20772        Ok(())
20773    }
20774
20775    fn generate_between(&mut self, between: &Between) -> Result<()> {
20776        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
20777        let use_prefix_not = between.not
20778            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
20779        if use_prefix_not {
20780            self.write_keyword("NOT");
20781            self.write_space();
20782        }
20783        self.generate_expression(&between.this)?;
20784        if between.not && !use_prefix_not {
20785            self.write_space();
20786            self.write_keyword("NOT");
20787        }
20788        self.write_space();
20789        self.write_keyword("BETWEEN");
20790        // Emit SYMMETRIC/ASYMMETRIC if present
20791        if let Some(sym) = between.symmetric {
20792            if sym {
20793                self.write(" SYMMETRIC");
20794            } else {
20795                self.write(" ASYMMETRIC");
20796            }
20797        }
20798        self.write_space();
20799        self.generate_expression(&between.low)?;
20800        self.write_space();
20801        self.write_keyword("AND");
20802        self.write_space();
20803        self.generate_expression(&between.high)
20804    }
20805
20806    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
20807        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
20808        let use_prefix_not = is_null.not
20809            && (self.config.dialect.is_none()
20810                || self.config.dialect == Some(DialectType::Generic)
20811                || is_null.postfix_form);
20812        if use_prefix_not {
20813            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
20814            self.write_keyword("NOT");
20815            self.write_space();
20816            self.generate_expression(&is_null.this)?;
20817            self.write_space();
20818            self.write_keyword("IS");
20819            self.write_space();
20820            self.write_keyword("NULL");
20821        } else {
20822            self.generate_expression(&is_null.this)?;
20823            self.write_space();
20824            self.write_keyword("IS");
20825            if is_null.not {
20826                self.write_space();
20827                self.write_keyword("NOT");
20828            }
20829            self.write_space();
20830            self.write_keyword("NULL");
20831        }
20832        Ok(())
20833    }
20834
20835    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
20836        self.generate_expression(&is_true.this)?;
20837        self.write_space();
20838        self.write_keyword("IS");
20839        if is_true.not {
20840            self.write_space();
20841            self.write_keyword("NOT");
20842        }
20843        self.write_space();
20844        self.write_keyword("TRUE");
20845        Ok(())
20846    }
20847
20848    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
20849        self.generate_expression(&is_false.this)?;
20850        self.write_space();
20851        self.write_keyword("IS");
20852        if is_false.not {
20853            self.write_space();
20854            self.write_keyword("NOT");
20855        }
20856        self.write_space();
20857        self.write_keyword("FALSE");
20858        Ok(())
20859    }
20860
20861    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
20862        self.generate_expression(&is_json.this)?;
20863        self.write_space();
20864        self.write_keyword("IS");
20865        if is_json.negated {
20866            self.write_space();
20867            self.write_keyword("NOT");
20868        }
20869        self.write_space();
20870        self.write_keyword("JSON");
20871
20872        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
20873        if let Some(ref json_type) = is_json.json_type {
20874            self.write_space();
20875            self.write_keyword(json_type);
20876        }
20877
20878        // Output key uniqueness constraint if specified
20879        match &is_json.unique_keys {
20880            Some(JsonUniqueKeys::With) => {
20881                self.write_space();
20882                self.write_keyword("WITH UNIQUE KEYS");
20883            }
20884            Some(JsonUniqueKeys::Without) => {
20885                self.write_space();
20886                self.write_keyword("WITHOUT UNIQUE KEYS");
20887            }
20888            Some(JsonUniqueKeys::Shorthand) => {
20889                self.write_space();
20890                self.write_keyword("UNIQUE KEYS");
20891            }
20892            None => {}
20893        }
20894
20895        Ok(())
20896    }
20897
20898    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
20899        self.generate_expression(&is_expr.left)?;
20900        self.write_space();
20901        self.write_keyword("IS");
20902        self.write_space();
20903        self.generate_expression(&is_expr.right)
20904    }
20905
20906    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
20907        if exists.not {
20908            self.write_keyword("NOT");
20909            self.write_space();
20910        }
20911        self.write_keyword("EXISTS");
20912        self.write("(");
20913        let is_statement = matches!(
20914            &exists.this,
20915            Expression::Select(_)
20916                | Expression::Union(_)
20917                | Expression::Intersect(_)
20918                | Expression::Except(_)
20919        );
20920        if self.config.pretty && is_statement {
20921            self.write_newline();
20922            self.indent_level += 1;
20923            self.write_indent();
20924            self.generate_expression(&exists.this)?;
20925            self.write_newline();
20926            self.indent_level -= 1;
20927            self.write_indent();
20928            self.write(")");
20929        } else {
20930            self.generate_expression(&exists.this)?;
20931            self.write(")");
20932        }
20933        Ok(())
20934    }
20935
20936    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
20937        self.generate_expression(&op.left)?;
20938        self.write_space();
20939        self.write_keyword("MEMBER OF");
20940        self.write("(");
20941        self.generate_expression(&op.right)?;
20942        self.write(")");
20943        Ok(())
20944    }
20945
20946    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
20947        if subquery.lateral {
20948            self.write_keyword("LATERAL");
20949            self.write_space();
20950        }
20951
20952        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
20953        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
20954        // to carry the LIMIT modifier without adding more parens
20955        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
20956            matches!(
20957                &p.this,
20958                Expression::Select(_)
20959                    | Expression::Union(_)
20960                    | Expression::Intersect(_)
20961                    | Expression::Except(_)
20962                    | Expression::Subquery(_)
20963            )
20964        } else {
20965            false
20966        };
20967
20968        // Check if inner expression is a statement for pretty formatting
20969        let is_statement = matches!(
20970            &subquery.this,
20971            Expression::Select(_)
20972                | Expression::Union(_)
20973                | Expression::Intersect(_)
20974                | Expression::Except(_)
20975                | Expression::Merge(_)
20976        );
20977
20978        if !skip_outer_parens {
20979            self.write("(");
20980            if self.config.pretty && is_statement {
20981                self.write_newline();
20982                self.indent_level += 1;
20983                self.write_indent();
20984            }
20985        }
20986        self.generate_expression(&subquery.this)?;
20987
20988        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
20989        if subquery.modifiers_inside {
20990            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
20991            if let Some(order_by) = &subquery.order_by {
20992                self.write_space();
20993                self.write_keyword("ORDER BY");
20994                self.write_space();
20995                for (i, ord) in order_by.expressions.iter().enumerate() {
20996                    if i > 0 {
20997                        self.write(", ");
20998                    }
20999                    self.generate_ordered(ord)?;
21000                }
21001            }
21002
21003            if let Some(limit) = &subquery.limit {
21004                self.write_space();
21005                self.write_keyword("LIMIT");
21006                self.write_space();
21007                self.generate_expression(&limit.this)?;
21008                if limit.percent {
21009                    self.write_space();
21010                    self.write_keyword("PERCENT");
21011                }
21012            }
21013
21014            if let Some(offset) = &subquery.offset {
21015                self.write_space();
21016                self.write_keyword("OFFSET");
21017                self.write_space();
21018                self.generate_expression(&offset.this)?;
21019            }
21020        }
21021
21022        if !skip_outer_parens {
21023            if self.config.pretty && is_statement {
21024                self.write_newline();
21025                self.indent_level -= 1;
21026                self.write_indent();
21027            }
21028            self.write(")");
21029        }
21030
21031        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
21032        if !subquery.modifiers_inside {
21033            if let Some(order_by) = &subquery.order_by {
21034                self.write_space();
21035                self.write_keyword("ORDER BY");
21036                self.write_space();
21037                for (i, ord) in order_by.expressions.iter().enumerate() {
21038                    if i > 0 {
21039                        self.write(", ");
21040                    }
21041                    self.generate_ordered(ord)?;
21042                }
21043            }
21044
21045            if let Some(limit) = &subquery.limit {
21046                self.write_space();
21047                self.write_keyword("LIMIT");
21048                self.write_space();
21049                self.generate_expression(&limit.this)?;
21050                if limit.percent {
21051                    self.write_space();
21052                    self.write_keyword("PERCENT");
21053                }
21054            }
21055
21056            if let Some(offset) = &subquery.offset {
21057                self.write_space();
21058                self.write_keyword("OFFSET");
21059                self.write_space();
21060                self.generate_expression(&offset.this)?;
21061            }
21062
21063            // Generate DISTRIBUTE BY (Hive/Spark)
21064            if let Some(distribute_by) = &subquery.distribute_by {
21065                self.write_space();
21066                self.write_keyword("DISTRIBUTE BY");
21067                self.write_space();
21068                for (i, expr) in distribute_by.expressions.iter().enumerate() {
21069                    if i > 0 {
21070                        self.write(", ");
21071                    }
21072                    self.generate_expression(expr)?;
21073                }
21074            }
21075
21076            // Generate SORT BY (Hive/Spark)
21077            if let Some(sort_by) = &subquery.sort_by {
21078                self.write_space();
21079                self.write_keyword("SORT BY");
21080                self.write_space();
21081                for (i, ord) in sort_by.expressions.iter().enumerate() {
21082                    if i > 0 {
21083                        self.write(", ");
21084                    }
21085                    self.generate_ordered(ord)?;
21086                }
21087            }
21088
21089            // Generate CLUSTER BY (Hive/Spark)
21090            if let Some(cluster_by) = &subquery.cluster_by {
21091                self.write_space();
21092                self.write_keyword("CLUSTER BY");
21093                self.write_space();
21094                for (i, ord) in cluster_by.expressions.iter().enumerate() {
21095                    if i > 0 {
21096                        self.write(", ");
21097                    }
21098                    self.generate_ordered(ord)?;
21099                }
21100            }
21101        }
21102
21103        if let Some(alias) = &subquery.alias {
21104            self.write_space();
21105            // Oracle doesn't use AS for subquery aliases
21106            let skip_as = matches!(
21107                self.config.dialect,
21108                Some(crate::dialects::DialectType::Oracle)
21109            );
21110            if !skip_as {
21111                self.write_keyword("AS");
21112                self.write_space();
21113            }
21114            self.generate_identifier(alias)?;
21115            if !subquery.column_aliases.is_empty() {
21116                self.write("(");
21117                for (i, col) in subquery.column_aliases.iter().enumerate() {
21118                    if i > 0 {
21119                        self.write(", ");
21120                    }
21121                    self.generate_identifier(col)?;
21122                }
21123                self.write(")");
21124            }
21125        }
21126        // Output trailing comments
21127        for comment in &subquery.trailing_comments {
21128            self.write(" ");
21129            self.write_formatted_comment(comment);
21130        }
21131        Ok(())
21132    }
21133
21134    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
21135        // Generate WITH clause if present
21136        if let Some(ref with) = pivot.with {
21137            self.generate_with(with)?;
21138            self.write_space();
21139        }
21140
21141        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
21142
21143        // Check for Redshift UNPIVOT in FROM clause:
21144        // UNPIVOT expr [AS val AT attr]
21145        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
21146        let is_redshift_unpivot = pivot.unpivot
21147            && pivot.expressions.is_empty()
21148            && pivot.fields.is_empty()
21149            && pivot.using.is_empty()
21150            && pivot.into.is_none()
21151            && !matches!(&pivot.this, Expression::Null(_));
21152
21153        if is_redshift_unpivot {
21154            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
21155            self.write_keyword("UNPIVOT");
21156            self.write_space();
21157            self.generate_expression(&pivot.this)?;
21158            // Alias - for Redshift it can be "val AT attr" format
21159            if let Some(alias) = &pivot.alias {
21160                self.write_space();
21161                self.write_keyword("AS");
21162                self.write_space();
21163                // The alias might contain " AT " for the attr part
21164                self.write(&alias.name);
21165            }
21166            return Ok(());
21167        }
21168
21169        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
21170        let is_simplified = !pivot.using.is_empty()
21171            || pivot.into.is_some()
21172            || (pivot.fields.is_empty()
21173                && !pivot.expressions.is_empty()
21174                && !matches!(&pivot.this, Expression::Null(_)));
21175
21176        if is_simplified {
21177            // DuckDB simplified syntax:
21178            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
21179            //   UNPIVOT table ON cols INTO NAME col VALUE col
21180            self.write_keyword(direction);
21181            self.write_space();
21182            self.generate_expression(&pivot.this)?;
21183
21184            if !pivot.expressions.is_empty() {
21185                self.write_space();
21186                self.write_keyword("ON");
21187                self.write_space();
21188                for (i, expr) in pivot.expressions.iter().enumerate() {
21189                    if i > 0 {
21190                        self.write(", ");
21191                    }
21192                    self.generate_expression(expr)?;
21193                }
21194            }
21195
21196            // INTO (for UNPIVOT)
21197            if let Some(into) = &pivot.into {
21198                self.write_space();
21199                self.write_keyword("INTO");
21200                self.write_space();
21201                self.generate_expression(into)?;
21202            }
21203
21204            // USING (for PIVOT)
21205            if !pivot.using.is_empty() {
21206                self.write_space();
21207                self.write_keyword("USING");
21208                self.write_space();
21209                for (i, expr) in pivot.using.iter().enumerate() {
21210                    if i > 0 {
21211                        self.write(", ");
21212                    }
21213                    self.generate_expression(expr)?;
21214                }
21215            }
21216
21217            // GROUP BY
21218            if let Some(group) = &pivot.group {
21219                self.write_space();
21220                self.generate_expression(group)?;
21221            }
21222        } else {
21223            // Standard syntax:
21224            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
21225            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
21226            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
21227            if !matches!(&pivot.this, Expression::Null(_)) {
21228                self.generate_expression(&pivot.this)?;
21229                self.write_space();
21230            }
21231            self.write_keyword(direction);
21232            self.write("(");
21233
21234            // Aggregation expressions
21235            for (i, expr) in pivot.expressions.iter().enumerate() {
21236                if i > 0 {
21237                    self.write(", ");
21238                }
21239                self.generate_expression(expr)?;
21240            }
21241
21242            // FOR...IN fields
21243            if !pivot.fields.is_empty() {
21244                if !pivot.expressions.is_empty() {
21245                    self.write_space();
21246                }
21247                self.write_keyword("FOR");
21248                self.write_space();
21249                for (i, field) in pivot.fields.iter().enumerate() {
21250                    if i > 0 {
21251                        self.write_space();
21252                    }
21253                    // field is an In expression: column IN (values)
21254                    self.generate_expression(field)?;
21255                }
21256            }
21257
21258            // DEFAULT ON NULL
21259            if let Some(default_val) = &pivot.default_on_null {
21260                self.write_space();
21261                self.write_keyword("DEFAULT ON NULL");
21262                self.write(" (");
21263                self.generate_expression(default_val)?;
21264                self.write(")");
21265            }
21266
21267            // GROUP BY inside PIVOT parens
21268            if let Some(group) = &pivot.group {
21269                self.write_space();
21270                self.generate_expression(group)?;
21271            }
21272
21273            self.write(")");
21274        }
21275
21276        // Alias
21277        if let Some(alias) = &pivot.alias {
21278            self.write_space();
21279            self.write_keyword("AS");
21280            self.write_space();
21281            self.generate_identifier(alias)?;
21282        }
21283
21284        Ok(())
21285    }
21286
21287    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
21288        self.generate_expression(&unpivot.this)?;
21289        self.write_space();
21290        self.write_keyword("UNPIVOT");
21291        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
21292        if let Some(include) = unpivot.include_nulls {
21293            self.write_space();
21294            if include {
21295                self.write_keyword("INCLUDE NULLS");
21296            } else {
21297                self.write_keyword("EXCLUDE NULLS");
21298            }
21299            self.write_space();
21300        }
21301        self.write("(");
21302        if unpivot.value_column_parenthesized {
21303            self.write("(");
21304        }
21305        self.generate_identifier(&unpivot.value_column)?;
21306        // Output additional value columns if present
21307        for extra_col in &unpivot.extra_value_columns {
21308            self.write(", ");
21309            self.generate_identifier(extra_col)?;
21310        }
21311        if unpivot.value_column_parenthesized {
21312            self.write(")");
21313        }
21314        self.write_space();
21315        self.write_keyword("FOR");
21316        self.write_space();
21317        self.generate_identifier(&unpivot.name_column)?;
21318        self.write_space();
21319        self.write_keyword("IN");
21320        self.write(" (");
21321        for (i, col) in unpivot.columns.iter().enumerate() {
21322            if i > 0 {
21323                self.write(", ");
21324            }
21325            self.generate_expression(col)?;
21326        }
21327        self.write("))");
21328        if let Some(alias) = &unpivot.alias {
21329            self.write_space();
21330            self.write_keyword("AS");
21331            self.write_space();
21332            self.generate_identifier(alias)?;
21333        }
21334        Ok(())
21335    }
21336
21337    fn generate_values(&mut self, values: &Values) -> Result<()> {
21338        self.write_keyword("VALUES");
21339        for (i, row) in values.expressions.iter().enumerate() {
21340            if i > 0 {
21341                self.write(",");
21342            }
21343            self.write(" (");
21344            for (j, expr) in row.expressions.iter().enumerate() {
21345                if j > 0 {
21346                    self.write(", ");
21347                }
21348                self.generate_expression(expr)?;
21349            }
21350            self.write(")");
21351        }
21352        if let Some(alias) = &values.alias {
21353            self.write_space();
21354            self.write_keyword("AS");
21355            self.write_space();
21356            self.generate_identifier(alias)?;
21357            if !values.column_aliases.is_empty() {
21358                self.write("(");
21359                for (i, col) in values.column_aliases.iter().enumerate() {
21360                    if i > 0 {
21361                        self.write(", ");
21362                    }
21363                    self.generate_identifier(col)?;
21364                }
21365                self.write(")");
21366            }
21367        }
21368        Ok(())
21369    }
21370
21371    fn generate_array(&mut self, arr: &Array) -> Result<()> {
21372        // Apply struct name inheritance for target dialects that need it
21373        let needs_inheritance = matches!(
21374            self.config.dialect,
21375            Some(DialectType::DuckDB)
21376                | Some(DialectType::Spark)
21377                | Some(DialectType::Databricks)
21378                | Some(DialectType::Hive)
21379                | Some(DialectType::Snowflake)
21380                | Some(DialectType::Presto)
21381                | Some(DialectType::Trino)
21382        );
21383        let propagated: Vec<Expression>;
21384        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
21385            propagated = Self::inherit_struct_field_names(&arr.expressions);
21386            &propagated
21387        } else {
21388            &arr.expressions
21389        };
21390
21391        // Generic mode: ARRAY(1, 2, 3) with parentheses
21392        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
21393        let use_parens =
21394            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
21395        if !self.config.array_bracket_only {
21396            self.write_keyword("ARRAY");
21397        }
21398        if use_parens {
21399            self.write("(");
21400        } else {
21401            self.write("[");
21402        }
21403        for (i, expr) in expressions.iter().enumerate() {
21404            if i > 0 {
21405                self.write(", ");
21406            }
21407            self.generate_expression(expr)?;
21408        }
21409        if use_parens {
21410            self.write(")");
21411        } else {
21412            self.write("]");
21413        }
21414        Ok(())
21415    }
21416
21417    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
21418        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
21419        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
21420        if tuple.expressions.len() == 2 {
21421            if let Expression::TableAlias(_) = &tuple.expressions[1] {
21422                // First element is the function/expression, second is the TableAlias
21423                self.generate_expression(&tuple.expressions[0])?;
21424                self.write_space();
21425                self.write_keyword("AS");
21426                self.write_space();
21427                self.generate_expression(&tuple.expressions[1])?;
21428                return Ok(());
21429            }
21430        }
21431
21432        // In pretty mode, format long tuples with each element on a new line
21433        // Only expand if total width exceeds threshold
21434        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
21435            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
21436            for expr in &tuple.expressions {
21437                expr_strings.push(self.generate_to_string(expr)?);
21438            }
21439            self.too_wide(&expr_strings)
21440        } else {
21441            false
21442        };
21443
21444        if expand_tuple {
21445            self.write("(");
21446            self.write_newline();
21447            self.indent_level += 1;
21448            for (i, expr) in tuple.expressions.iter().enumerate() {
21449                if i > 0 {
21450                    self.write(",");
21451                    self.write_newline();
21452                }
21453                self.write_indent();
21454                self.generate_expression(expr)?;
21455            }
21456            self.indent_level -= 1;
21457            self.write_newline();
21458            self.write_indent();
21459            self.write(")");
21460        } else {
21461            self.write("(");
21462            for (i, expr) in tuple.expressions.iter().enumerate() {
21463                if i > 0 {
21464                    self.write(", ");
21465                }
21466                self.generate_expression(expr)?;
21467            }
21468            self.write(")");
21469        }
21470        Ok(())
21471    }
21472
21473    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
21474        self.generate_expression(&pipe.this)?;
21475        self.write(" |> ");
21476        self.generate_expression(&pipe.expression)?;
21477        Ok(())
21478    }
21479
21480    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
21481        self.generate_expression(&ordered.this)?;
21482        if ordered.desc {
21483            self.write_space();
21484            self.write_keyword("DESC");
21485        } else if ordered.explicit_asc {
21486            self.write_space();
21487            self.write_keyword("ASC");
21488        }
21489        if let Some(nulls_first) = ordered.nulls_first {
21490            // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
21491            // for the dialect. Different dialects have different NULL ordering defaults:
21492            //
21493            // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
21494            //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
21495            //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
21496            //
21497            // nulls_are_small (Spark, Hive, BigQuery, most others):
21498            //   - ASC: NULLS FIRST is default
21499            //   - DESC: NULLS LAST is default
21500            //
21501            // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
21502            //   - NULLS LAST is always the default regardless of sort direction
21503            let is_asc = !ordered.desc;
21504            let is_nulls_are_large = matches!(
21505                self.config.dialect,
21506                Some(DialectType::Oracle)
21507                    | Some(DialectType::PostgreSQL)
21508                    | Some(DialectType::Redshift)
21509                    | Some(DialectType::Snowflake)
21510            );
21511            let is_nulls_are_last = matches!(
21512                self.config.dialect,
21513                Some(DialectType::Dremio)
21514                    | Some(DialectType::DuckDB)
21515                    | Some(DialectType::Presto)
21516                    | Some(DialectType::Trino)
21517                    | Some(DialectType::Athena)
21518                    | Some(DialectType::ClickHouse)
21519                    | Some(DialectType::Drill)
21520                    | Some(DialectType::Exasol)
21521            );
21522
21523            // Check if the NULLS ordering matches the default for this dialect
21524            let is_default_nulls = if is_nulls_are_large {
21525                // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
21526                (is_asc && !nulls_first) || (!is_asc && nulls_first)
21527            } else if is_nulls_are_last {
21528                // For nulls_are_last: NULLS LAST is always default
21529                !nulls_first
21530            } else {
21531                false
21532            };
21533
21534            if !is_default_nulls {
21535                self.write_space();
21536                self.write_keyword("NULLS");
21537                self.write_space();
21538                self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
21539            }
21540        }
21541        // WITH FILL clause (ClickHouse)
21542        if let Some(ref with_fill) = ordered.with_fill {
21543            self.write_space();
21544            self.generate_with_fill(with_fill)?;
21545        }
21546        Ok(())
21547    }
21548
21549    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
21550    fn write_clickhouse_type(&mut self, type_str: &str) {
21551        if self.clickhouse_nullable_depth < 0 {
21552            // Map key context: don't wrap in Nullable
21553            self.write(type_str);
21554        } else {
21555            self.write(&format!("Nullable({})", type_str));
21556        }
21557    }
21558
21559    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
21560        use crate::dialects::DialectType;
21561
21562        match dt {
21563            DataType::Boolean => {
21564                // Dialect-specific boolean type mappings
21565                match self.config.dialect {
21566                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
21567                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
21568                    Some(DialectType::Oracle) => {
21569                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
21570                        self.write_keyword("NUMBER(1)")
21571                    }
21572                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
21573                    _ => self.write_keyword("BOOLEAN"),
21574                }
21575            }
21576            DataType::TinyInt { length } => {
21577                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
21578                // Dremio maps TINYINT to INT
21579                // ClickHouse maps TINYINT to Int8
21580                match self.config.dialect {
21581                    Some(DialectType::PostgreSQL)
21582                    | Some(DialectType::Redshift)
21583                    | Some(DialectType::Oracle)
21584                    | Some(DialectType::Exasol) => {
21585                        self.write_keyword("SMALLINT");
21586                    }
21587                    Some(DialectType::Teradata) => {
21588                        // Teradata uses BYTEINT for smallest integer
21589                        self.write_keyword("BYTEINT");
21590                    }
21591                    Some(DialectType::Dremio) => {
21592                        // Dremio maps TINYINT to INT
21593                        self.write_keyword("INT");
21594                    }
21595                    Some(DialectType::ClickHouse) => {
21596                        self.write_clickhouse_type("Int8");
21597                    }
21598                    _ => {
21599                        self.write_keyword("TINYINT");
21600                    }
21601                }
21602                if let Some(n) = length {
21603                    if !matches!(
21604                        self.config.dialect,
21605                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
21606                    ) {
21607                        self.write(&format!("({})", n));
21608                    }
21609                }
21610            }
21611            DataType::SmallInt { length } => {
21612                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
21613                match self.config.dialect {
21614                    Some(DialectType::Dremio) => {
21615                        self.write_keyword("INT");
21616                    }
21617                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
21618                        self.write_keyword("INTEGER");
21619                    }
21620                    Some(DialectType::BigQuery) => {
21621                        self.write_keyword("INT64");
21622                    }
21623                    Some(DialectType::ClickHouse) => {
21624                        self.write_clickhouse_type("Int16");
21625                    }
21626                    _ => {
21627                        self.write_keyword("SMALLINT");
21628                        if let Some(n) = length {
21629                            self.write(&format!("({})", n));
21630                        }
21631                    }
21632                }
21633            }
21634            DataType::Int {
21635                length,
21636                integer_spelling,
21637            } => {
21638                // BigQuery uses INT64 for INT
21639                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
21640                    self.write_keyword("INT64");
21641                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
21642                    self.write_clickhouse_type("Int32");
21643                } else {
21644                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
21645                    let use_integer = match self.config.dialect {
21646                        Some(DialectType::TSQL)
21647                        | Some(DialectType::Fabric)
21648                        | Some(DialectType::Presto)
21649                        | Some(DialectType::Trino)
21650                        | Some(DialectType::SQLite)
21651                        | Some(DialectType::Redshift) => true,
21652                        // Databricks preserves the original spelling
21653                        Some(DialectType::Databricks) => *integer_spelling,
21654                        _ => false,
21655                    };
21656                    if use_integer {
21657                        self.write_keyword("INTEGER");
21658                    } else {
21659                        self.write_keyword("INT");
21660                    }
21661                    if let Some(n) = length {
21662                        self.write(&format!("({})", n));
21663                    }
21664                }
21665            }
21666            DataType::BigInt { length } => {
21667                // Dialect-specific bigint type mappings
21668                match self.config.dialect {
21669                    Some(DialectType::Oracle) => {
21670                        // Oracle doesn't have BIGINT, uses INT
21671                        self.write_keyword("INT");
21672                    }
21673                    Some(DialectType::ClickHouse) => {
21674                        self.write_clickhouse_type("Int64");
21675                    }
21676                    _ => {
21677                        self.write_keyword("BIGINT");
21678                        if let Some(n) = length {
21679                            self.write(&format!("({})", n));
21680                        }
21681                    }
21682                }
21683            }
21684            DataType::Float {
21685                precision,
21686                scale,
21687                real_spelling,
21688            } => {
21689                // Dialect-specific float type mappings
21690                // If real_spelling is true, preserve REAL; otherwise use dialect default
21691                // Spark/Hive don't support REAL, always use FLOAT
21692                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
21693                    self.write_clickhouse_type("Float32");
21694                } else if *real_spelling
21695                    && !matches!(
21696                        self.config.dialect,
21697                        Some(DialectType::Spark)
21698                            | Some(DialectType::Databricks)
21699                            | Some(DialectType::Hive)
21700                            | Some(DialectType::Snowflake)
21701                            | Some(DialectType::MySQL)
21702                            | Some(DialectType::BigQuery)
21703                    )
21704                {
21705                    self.write_keyword("REAL")
21706                } else {
21707                    match self.config.dialect {
21708                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
21709                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
21710                        _ => self.write_keyword("FLOAT"),
21711                    }
21712                }
21713                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
21714                // Spark/Hive don't support FLOAT(precision)
21715                if !matches!(
21716                    self.config.dialect,
21717                    Some(DialectType::Spark)
21718                        | Some(DialectType::Databricks)
21719                        | Some(DialectType::Hive)
21720                        | Some(DialectType::Presto)
21721                        | Some(DialectType::Trino)
21722                ) {
21723                    if let Some(p) = precision {
21724                        self.write(&format!("({}", p));
21725                        if let Some(s) = scale {
21726                            self.write(&format!(", {})", s));
21727                        } else {
21728                            self.write(")");
21729                        }
21730                    }
21731                }
21732            }
21733            DataType::Double { precision, scale } => {
21734                // Dialect-specific double type mappings
21735                match self.config.dialect {
21736                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21737                        self.write_keyword("FLOAT")
21738                    } // SQL Server/Fabric FLOAT is double
21739                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
21740                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
21741                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
21742                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
21743                    Some(DialectType::PostgreSQL)
21744                    | Some(DialectType::Redshift)
21745                    | Some(DialectType::Teradata)
21746                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
21747                    _ => self.write_keyword("DOUBLE"),
21748                }
21749                // MySQL supports DOUBLE(precision, scale)
21750                if let Some(p) = precision {
21751                    self.write(&format!("({}", p));
21752                    if let Some(s) = scale {
21753                        self.write(&format!(", {})", s));
21754                    } else {
21755                        self.write(")");
21756                    }
21757                }
21758            }
21759            DataType::Decimal { precision, scale } => {
21760                // Dialect-specific decimal type mappings
21761                match self.config.dialect {
21762                    Some(DialectType::ClickHouse) => {
21763                        self.write("Decimal");
21764                        if let Some(p) = precision {
21765                            self.write(&format!("({}", p));
21766                            if let Some(s) = scale {
21767                                self.write(&format!(", {}", s));
21768                            }
21769                            self.write(")");
21770                        }
21771                    }
21772                    Some(DialectType::Oracle) => {
21773                        // Oracle uses NUMBER instead of DECIMAL
21774                        self.write_keyword("NUMBER");
21775                        if let Some(p) = precision {
21776                            self.write(&format!("({}", p));
21777                            if let Some(s) = scale {
21778                                self.write(&format!(", {}", s));
21779                            }
21780                            self.write(")");
21781                        }
21782                    }
21783                    Some(DialectType::BigQuery) => {
21784                        // BigQuery uses NUMERIC instead of DECIMAL
21785                        self.write_keyword("NUMERIC");
21786                        if let Some(p) = precision {
21787                            self.write(&format!("({}", p));
21788                            if let Some(s) = scale {
21789                                self.write(&format!(", {}", s));
21790                            }
21791                            self.write(")");
21792                        }
21793                    }
21794                    _ => {
21795                        self.write_keyword("DECIMAL");
21796                        if let Some(p) = precision {
21797                            self.write(&format!("({}", p));
21798                            if let Some(s) = scale {
21799                                self.write(&format!(", {}", s));
21800                            }
21801                            self.write(")");
21802                        }
21803                    }
21804                }
21805            }
21806            DataType::Char { length } => {
21807                // Dialect-specific char type mappings
21808                match self.config.dialect {
21809                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
21810                        // DuckDB/SQLite maps CHAR to TEXT
21811                        self.write_keyword("TEXT");
21812                    }
21813                    Some(DialectType::Hive)
21814                    | Some(DialectType::Spark)
21815                    | Some(DialectType::Databricks) => {
21816                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
21817                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
21818                        if length.is_some()
21819                            && !matches!(self.config.dialect, Some(DialectType::Hive))
21820                        {
21821                            self.write_keyword("CHAR");
21822                            if let Some(n) = length {
21823                                self.write(&format!("({})", n));
21824                            }
21825                        } else {
21826                            self.write_keyword("STRING");
21827                        }
21828                    }
21829                    Some(DialectType::Dremio) => {
21830                        // Dremio maps CHAR to VARCHAR
21831                        self.write_keyword("VARCHAR");
21832                        if let Some(n) = length {
21833                            self.write(&format!("({})", n));
21834                        }
21835                    }
21836                    _ => {
21837                        self.write_keyword("CHAR");
21838                        if let Some(n) = length {
21839                            self.write(&format!("({})", n));
21840                        }
21841                    }
21842                }
21843            }
21844            DataType::VarChar {
21845                length,
21846                parenthesized_length,
21847            } => {
21848                // Dialect-specific varchar type mappings
21849                match self.config.dialect {
21850                    Some(DialectType::Oracle) => {
21851                        self.write_keyword("VARCHAR2");
21852                        if let Some(n) = length {
21853                            self.write(&format!("({})", n));
21854                        }
21855                    }
21856                    Some(DialectType::DuckDB) => {
21857                        // DuckDB maps VARCHAR to TEXT, preserving length
21858                        self.write_keyword("TEXT");
21859                        if let Some(n) = length {
21860                            self.write(&format!("({})", n));
21861                        }
21862                    }
21863                    Some(DialectType::SQLite) => {
21864                        // SQLite maps VARCHAR to TEXT, preserving length
21865                        self.write_keyword("TEXT");
21866                        if let Some(n) = length {
21867                            self.write(&format!("({})", n));
21868                        }
21869                    }
21870                    Some(DialectType::MySQL) if length.is_none() => {
21871                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
21872                        self.write_keyword("TEXT");
21873                    }
21874                    Some(DialectType::Hive)
21875                    | Some(DialectType::Spark)
21876                    | Some(DialectType::Databricks)
21877                        if length.is_none() =>
21878                    {
21879                        // Hive/Spark/Databricks: VARCHAR without length → STRING
21880                        self.write_keyword("STRING");
21881                    }
21882                    _ => {
21883                        self.write_keyword("VARCHAR");
21884                        if let Some(n) = length {
21885                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
21886                            if *parenthesized_length {
21887                                self.write(&format!("(({}))", n));
21888                            } else {
21889                                self.write(&format!("({})", n));
21890                            }
21891                        }
21892                    }
21893                }
21894            }
21895            DataType::Text => {
21896                // Dialect-specific text type mappings
21897                match self.config.dialect {
21898                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
21899                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21900                        self.write_keyword("VARCHAR(MAX)")
21901                    }
21902                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
21903                    Some(DialectType::Snowflake)
21904                    | Some(DialectType::Dremio)
21905                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
21906                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
21907                    Some(DialectType::Presto)
21908                    | Some(DialectType::Trino)
21909                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
21910                    Some(DialectType::Spark)
21911                    | Some(DialectType::Databricks)
21912                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
21913                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
21914                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
21915                        self.write_keyword("STRING")
21916                    }
21917                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
21918                    _ => self.write_keyword("TEXT"),
21919                }
21920            }
21921            DataType::TextWithLength { length } => {
21922                // TEXT(n) - dialect-specific type with length
21923                match self.config.dialect {
21924                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
21925                    Some(DialectType::Hive)
21926                    | Some(DialectType::Spark)
21927                    | Some(DialectType::Databricks) => {
21928                        self.write(&format!("VARCHAR({})", length));
21929                    }
21930                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
21931                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
21932                    Some(DialectType::Snowflake)
21933                    | Some(DialectType::Presto)
21934                    | Some(DialectType::Trino)
21935                    | Some(DialectType::Athena)
21936                    | Some(DialectType::Drill)
21937                    | Some(DialectType::Dremio) => {
21938                        self.write(&format!("VARCHAR({})", length));
21939                    }
21940                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21941                        self.write(&format!("VARCHAR({})", length))
21942                    }
21943                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
21944                        self.write(&format!("STRING({})", length))
21945                    }
21946                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
21947                    _ => self.write(&format!("TEXT({})", length)),
21948                }
21949            }
21950            DataType::String { length } => {
21951                // STRING type with optional length (BigQuery STRING(n))
21952                match self.config.dialect {
21953                    Some(DialectType::ClickHouse) => {
21954                        // ClickHouse uses String with specific casing
21955                        self.write("String");
21956                        if let Some(n) = length {
21957                            self.write(&format!("({})", n));
21958                        }
21959                    }
21960                    Some(DialectType::BigQuery)
21961                    | Some(DialectType::Hive)
21962                    | Some(DialectType::Spark)
21963                    | Some(DialectType::Databricks)
21964                    | Some(DialectType::StarRocks)
21965                    | Some(DialectType::Doris) => {
21966                        self.write_keyword("STRING");
21967                        if let Some(n) = length {
21968                            self.write(&format!("({})", n));
21969                        }
21970                    }
21971                    Some(DialectType::PostgreSQL) => {
21972                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
21973                        if let Some(n) = length {
21974                            self.write_keyword("VARCHAR");
21975                            self.write(&format!("({})", n));
21976                        } else {
21977                            self.write_keyword("TEXT");
21978                        }
21979                    }
21980                    Some(DialectType::Redshift) => {
21981                        // Redshift: STRING -> VARCHAR(MAX)
21982                        if let Some(n) = length {
21983                            self.write_keyword("VARCHAR");
21984                            self.write(&format!("({})", n));
21985                        } else {
21986                            self.write_keyword("VARCHAR(MAX)");
21987                        }
21988                    }
21989                    Some(DialectType::MySQL) => {
21990                        // MySQL doesn't have STRING - use VARCHAR or TEXT
21991                        if let Some(n) = length {
21992                            self.write_keyword("VARCHAR");
21993                            self.write(&format!("({})", n));
21994                        } else {
21995                            self.write_keyword("TEXT");
21996                        }
21997                    }
21998                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21999                        // TSQL: STRING -> VARCHAR(MAX)
22000                        if let Some(n) = length {
22001                            self.write_keyword("VARCHAR");
22002                            self.write(&format!("({})", n));
22003                        } else {
22004                            self.write_keyword("VARCHAR(MAX)");
22005                        }
22006                    }
22007                    Some(DialectType::Oracle) => {
22008                        // Oracle: STRING -> CLOB
22009                        self.write_keyword("CLOB");
22010                    }
22011                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
22012                        // DuckDB/Materialize uses TEXT for string types
22013                        self.write_keyword("TEXT");
22014                        if let Some(n) = length {
22015                            self.write(&format!("({})", n));
22016                        }
22017                    }
22018                    Some(DialectType::Presto)
22019                    | Some(DialectType::Trino)
22020                    | Some(DialectType::Drill)
22021                    | Some(DialectType::Dremio) => {
22022                        // Presto/Trino/Drill use VARCHAR for string types
22023                        self.write_keyword("VARCHAR");
22024                        if let Some(n) = length {
22025                            self.write(&format!("({})", n));
22026                        }
22027                    }
22028                    Some(DialectType::Snowflake) => {
22029                        // Snowflake: STRING stays as STRING (identity/DDL)
22030                        // CAST context STRING -> VARCHAR is handled in generate_cast
22031                        self.write_keyword("STRING");
22032                        if let Some(n) = length {
22033                            self.write(&format!("({})", n));
22034                        }
22035                    }
22036                    _ => {
22037                        // Default: output STRING with optional length
22038                        self.write_keyword("STRING");
22039                        if let Some(n) = length {
22040                            self.write(&format!("({})", n));
22041                        }
22042                    }
22043                }
22044            }
22045            DataType::Binary { length } => {
22046                // Dialect-specific binary type mappings
22047                match self.config.dialect {
22048                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
22049                        self.write_keyword("BYTEA");
22050                        if let Some(n) = length {
22051                            self.write(&format!("({})", n));
22052                        }
22053                    }
22054                    Some(DialectType::Redshift) => {
22055                        self.write_keyword("VARBYTE");
22056                        if let Some(n) = length {
22057                            self.write(&format!("({})", n));
22058                        }
22059                    }
22060                    Some(DialectType::DuckDB)
22061                    | Some(DialectType::SQLite)
22062                    | Some(DialectType::Oracle) => {
22063                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
22064                        self.write_keyword("BLOB");
22065                        if let Some(n) = length {
22066                            self.write(&format!("({})", n));
22067                        }
22068                    }
22069                    Some(DialectType::Presto)
22070                    | Some(DialectType::Trino)
22071                    | Some(DialectType::Athena)
22072                    | Some(DialectType::Drill)
22073                    | Some(DialectType::Dremio) => {
22074                        // These dialects map BINARY to VARBINARY
22075                        self.write_keyword("VARBINARY");
22076                        if let Some(n) = length {
22077                            self.write(&format!("({})", n));
22078                        }
22079                    }
22080                    Some(DialectType::ClickHouse) => {
22081                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
22082                        if self.clickhouse_nullable_depth < 0 {
22083                            self.write("BINARY");
22084                        } else {
22085                            self.write("Nullable(BINARY");
22086                        }
22087                        if let Some(n) = length {
22088                            self.write(&format!("({})", n));
22089                        }
22090                        if self.clickhouse_nullable_depth >= 0 {
22091                            self.write(")");
22092                        }
22093                    }
22094                    _ => {
22095                        self.write_keyword("BINARY");
22096                        if let Some(n) = length {
22097                            self.write(&format!("({})", n));
22098                        }
22099                    }
22100                }
22101            }
22102            DataType::VarBinary { length } => {
22103                // Dialect-specific varbinary type mappings
22104                match self.config.dialect {
22105                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
22106                        self.write_keyword("BYTEA");
22107                        if let Some(n) = length {
22108                            self.write(&format!("({})", n));
22109                        }
22110                    }
22111                    Some(DialectType::Redshift) => {
22112                        self.write_keyword("VARBYTE");
22113                        if let Some(n) = length {
22114                            self.write(&format!("({})", n));
22115                        }
22116                    }
22117                    Some(DialectType::DuckDB)
22118                    | Some(DialectType::SQLite)
22119                    | Some(DialectType::Oracle) => {
22120                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
22121                        self.write_keyword("BLOB");
22122                        if let Some(n) = length {
22123                            self.write(&format!("({})", n));
22124                        }
22125                    }
22126                    Some(DialectType::Exasol) => {
22127                        // Exasol maps VARBINARY to VARCHAR
22128                        self.write_keyword("VARCHAR");
22129                    }
22130                    Some(DialectType::Spark)
22131                    | Some(DialectType::Hive)
22132                    | Some(DialectType::Databricks) => {
22133                        // Spark/Hive use BINARY instead of VARBINARY
22134                        self.write_keyword("BINARY");
22135                        if let Some(n) = length {
22136                            self.write(&format!("({})", n));
22137                        }
22138                    }
22139                    Some(DialectType::ClickHouse) => {
22140                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
22141                        self.write_clickhouse_type("String");
22142                    }
22143                    _ => {
22144                        self.write_keyword("VARBINARY");
22145                        if let Some(n) = length {
22146                            self.write(&format!("({})", n));
22147                        }
22148                    }
22149                }
22150            }
22151            DataType::Blob => {
22152                // Dialect-specific blob type mappings
22153                match self.config.dialect {
22154                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
22155                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
22156                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22157                        self.write_keyword("VARBINARY")
22158                    }
22159                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
22160                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
22161                    Some(DialectType::Presto)
22162                    | Some(DialectType::Trino)
22163                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
22164                    Some(DialectType::DuckDB) => {
22165                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
22166                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
22167                        self.write_keyword("VARBINARY");
22168                    }
22169                    Some(DialectType::Spark)
22170                    | Some(DialectType::Databricks)
22171                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
22172                    Some(DialectType::ClickHouse) => {
22173                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
22174                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
22175                        // This matches Python sqlglot behavior.
22176                        self.write("Nullable(String)");
22177                    }
22178                    _ => self.write_keyword("BLOB"),
22179                }
22180            }
22181            DataType::Bit { length } => {
22182                // Dialect-specific bit type mappings
22183                match self.config.dialect {
22184                    Some(DialectType::Dremio)
22185                    | Some(DialectType::Spark)
22186                    | Some(DialectType::Databricks)
22187                    | Some(DialectType::Hive)
22188                    | Some(DialectType::Snowflake)
22189                    | Some(DialectType::BigQuery)
22190                    | Some(DialectType::Presto)
22191                    | Some(DialectType::Trino)
22192                    | Some(DialectType::ClickHouse)
22193                    | Some(DialectType::Redshift) => {
22194                        // These dialects don't support BIT type, use BOOLEAN
22195                        self.write_keyword("BOOLEAN");
22196                    }
22197                    _ => {
22198                        self.write_keyword("BIT");
22199                        if let Some(n) = length {
22200                            self.write(&format!("({})", n));
22201                        }
22202                    }
22203                }
22204            }
22205            DataType::VarBit { length } => {
22206                self.write_keyword("VARBIT");
22207                if let Some(n) = length {
22208                    self.write(&format!("({})", n));
22209                }
22210            }
22211            DataType::Date => self.write_keyword("DATE"),
22212            DataType::Time {
22213                precision,
22214                timezone,
22215            } => {
22216                if *timezone {
22217                    // Dialect-specific TIME WITH TIME ZONE output
22218                    match self.config.dialect {
22219                        Some(DialectType::DuckDB) => {
22220                            // DuckDB: TIMETZ (drops precision)
22221                            self.write_keyword("TIMETZ");
22222                        }
22223                        Some(DialectType::PostgreSQL) => {
22224                            // PostgreSQL: TIMETZ or TIMETZ(p)
22225                            self.write_keyword("TIMETZ");
22226                            if let Some(p) = precision {
22227                                self.write(&format!("({})", p));
22228                            }
22229                        }
22230                        _ => {
22231                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
22232                            self.write_keyword("TIME");
22233                            if let Some(p) = precision {
22234                                self.write(&format!("({})", p));
22235                            }
22236                            self.write_keyword(" WITH TIME ZONE");
22237                        }
22238                    }
22239                } else {
22240                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
22241                    if matches!(
22242                        self.config.dialect,
22243                        Some(DialectType::Spark)
22244                            | Some(DialectType::Databricks)
22245                            | Some(DialectType::Hive)
22246                    ) {
22247                        self.write_keyword("TIMESTAMP");
22248                    } else {
22249                        self.write_keyword("TIME");
22250                        if let Some(p) = precision {
22251                            self.write(&format!("({})", p));
22252                        }
22253                    }
22254                }
22255            }
22256            DataType::Timestamp {
22257                precision,
22258                timezone,
22259            } => {
22260                // Dialect-specific timestamp type mappings
22261                match self.config.dialect {
22262                    Some(DialectType::ClickHouse) => {
22263                        self.write("DateTime");
22264                        if let Some(p) = precision {
22265                            self.write(&format!("({})", p));
22266                        }
22267                    }
22268                    Some(DialectType::TSQL) => {
22269                        if *timezone {
22270                            self.write_keyword("DATETIMEOFFSET");
22271                        } else {
22272                            self.write_keyword("DATETIME2");
22273                        }
22274                        if let Some(p) = precision {
22275                            self.write(&format!("({})", p));
22276                        }
22277                    }
22278                    Some(DialectType::MySQL) => {
22279                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
22280                        self.write_keyword("TIMESTAMP");
22281                        if let Some(p) = precision {
22282                            self.write(&format!("({})", p));
22283                        }
22284                    }
22285                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
22286                        // Doris/StarRocks: TIMESTAMP -> DATETIME
22287                        self.write_keyword("DATETIME");
22288                        if let Some(p) = precision {
22289                            self.write(&format!("({})", p));
22290                        }
22291                    }
22292                    Some(DialectType::BigQuery) => {
22293                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
22294                        if *timezone {
22295                            self.write_keyword("TIMESTAMP");
22296                        } else {
22297                            self.write_keyword("DATETIME");
22298                        }
22299                    }
22300                    Some(DialectType::DuckDB) => {
22301                        // DuckDB: TIMESTAMPTZ shorthand
22302                        if *timezone {
22303                            self.write_keyword("TIMESTAMPTZ");
22304                        } else {
22305                            self.write_keyword("TIMESTAMP");
22306                            if let Some(p) = precision {
22307                                self.write(&format!("({})", p));
22308                            }
22309                        }
22310                    }
22311                    _ => {
22312                        if *timezone && !self.config.tz_to_with_time_zone {
22313                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
22314                            self.write_keyword("TIMESTAMPTZ");
22315                            if let Some(p) = precision {
22316                                self.write(&format!("({})", p));
22317                            }
22318                        } else {
22319                            self.write_keyword("TIMESTAMP");
22320                            if let Some(p) = precision {
22321                                self.write(&format!("({})", p));
22322                            }
22323                            if *timezone {
22324                                self.write_space();
22325                                self.write_keyword("WITH TIME ZONE");
22326                            }
22327                        }
22328                    }
22329                }
22330            }
22331            DataType::Interval { unit, to } => {
22332                self.write_keyword("INTERVAL");
22333                if let Some(u) = unit {
22334                    self.write_space();
22335                    self.write_keyword(u);
22336                }
22337                // Handle range intervals like DAY TO HOUR
22338                if let Some(t) = to {
22339                    self.write_space();
22340                    self.write_keyword("TO");
22341                    self.write_space();
22342                    self.write_keyword(t);
22343                }
22344            }
22345            DataType::Json => {
22346                // Dialect-specific JSON type mappings
22347                match self.config.dialect {
22348                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
22349                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
22350                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
22351                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
22352                    _ => self.write_keyword("JSON"),
22353                }
22354            }
22355            DataType::JsonB => {
22356                // JSONB is PostgreSQL specific, but Doris also supports it
22357                match self.config.dialect {
22358                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
22359                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
22360                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
22361                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
22362                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
22363                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
22364                }
22365            }
22366            DataType::Uuid => {
22367                // Dialect-specific UUID type mappings
22368                match self.config.dialect {
22369                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
22370                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
22371                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
22372                    Some(DialectType::BigQuery)
22373                    | Some(DialectType::Spark)
22374                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
22375                    _ => self.write_keyword("UUID"),
22376                }
22377            }
22378            DataType::Array {
22379                element_type,
22380                dimension,
22381            } => {
22382                // Dialect-specific array syntax
22383                match self.config.dialect {
22384                    Some(DialectType::PostgreSQL)
22385                    | Some(DialectType::Redshift)
22386                    | Some(DialectType::DuckDB) => {
22387                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
22388                        self.generate_data_type(element_type)?;
22389                        if let Some(dim) = dimension {
22390                            self.write(&format!("[{}]", dim));
22391                        } else {
22392                            self.write("[]");
22393                        }
22394                    }
22395                    Some(DialectType::BigQuery) => {
22396                        self.write_keyword("ARRAY<");
22397                        self.generate_data_type(element_type)?;
22398                        self.write(">");
22399                    }
22400                    Some(DialectType::Snowflake)
22401                    | Some(DialectType::Presto)
22402                    | Some(DialectType::Trino)
22403                    | Some(DialectType::ClickHouse) => {
22404                        // These dialects use Array(TYPE) parentheses syntax
22405                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22406                            self.write("Array(");
22407                        } else {
22408                            self.write_keyword("ARRAY(");
22409                        }
22410                        self.generate_data_type(element_type)?;
22411                        self.write(")");
22412                    }
22413                    Some(DialectType::TSQL)
22414                    | Some(DialectType::MySQL)
22415                    | Some(DialectType::Oracle) => {
22416                        // These dialects don't have native array types
22417                        // Fall back to JSON or use native workarounds
22418                        match self.config.dialect {
22419                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
22420                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
22421                            _ => self.write_keyword("JSON"),
22422                        }
22423                    }
22424                    _ => {
22425                        // Default: use angle bracket syntax (ARRAY<T>)
22426                        self.write_keyword("ARRAY<");
22427                        self.generate_data_type(element_type)?;
22428                        self.write(">");
22429                    }
22430                }
22431            }
22432            DataType::List { element_type } => {
22433                // Materialize: element_type LIST (postfix syntax)
22434                self.generate_data_type(element_type)?;
22435                self.write_keyword(" LIST");
22436            }
22437            DataType::Map {
22438                key_type,
22439                value_type,
22440            } => {
22441                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
22442                match self.config.dialect {
22443                    Some(DialectType::Materialize) => {
22444                        // Materialize: MAP[key_type => value_type]
22445                        self.write_keyword("MAP[");
22446                        self.generate_data_type(key_type)?;
22447                        self.write(" => ");
22448                        self.generate_data_type(value_type)?;
22449                        self.write("]");
22450                    }
22451                    Some(DialectType::Snowflake)
22452                    | Some(DialectType::RisingWave)
22453                    | Some(DialectType::DuckDB)
22454                    | Some(DialectType::Presto)
22455                    | Some(DialectType::Trino)
22456                    | Some(DialectType::Athena) => {
22457                        self.write_keyword("MAP(");
22458                        self.generate_data_type(key_type)?;
22459                        self.write(", ");
22460                        self.generate_data_type(value_type)?;
22461                        self.write(")");
22462                    }
22463                    Some(DialectType::ClickHouse) => {
22464                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
22465                        // Key types must NOT be wrapped in Nullable
22466                        self.write("Map(");
22467                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
22468                        self.generate_data_type(key_type)?;
22469                        self.clickhouse_nullable_depth = 0;
22470                        self.write(", ");
22471                        self.generate_data_type(value_type)?;
22472                        self.write(")");
22473                    }
22474                    _ => {
22475                        self.write_keyword("MAP<");
22476                        self.generate_data_type(key_type)?;
22477                        self.write(", ");
22478                        self.generate_data_type(value_type)?;
22479                        self.write(">");
22480                    }
22481                }
22482            }
22483            DataType::Vector {
22484                element_type,
22485                dimension,
22486            } => {
22487                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
22488                    // SingleStore format: VECTOR(dimension, type_alias)
22489                    self.write_keyword("VECTOR(");
22490                    if let Some(dim) = dimension {
22491                        self.write(&dim.to_string());
22492                    }
22493                    // Map type back to SingleStore alias
22494                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
22495                        DataType::TinyInt { .. } => Some("I8"),
22496                        DataType::SmallInt { .. } => Some("I16"),
22497                        DataType::Int { .. } => Some("I32"),
22498                        DataType::BigInt { .. } => Some("I64"),
22499                        DataType::Float { .. } => Some("F32"),
22500                        DataType::Double { .. } => Some("F64"),
22501                        _ => None,
22502                    });
22503                    if let Some(alias) = type_alias {
22504                        if dimension.is_some() {
22505                            self.write(", ");
22506                        }
22507                        self.write(alias);
22508                    }
22509                    self.write(")");
22510                } else {
22511                    // Snowflake format: VECTOR(type, dimension)
22512                    self.write_keyword("VECTOR(");
22513                    if let Some(ref et) = element_type {
22514                        self.generate_data_type(et)?;
22515                        if dimension.is_some() {
22516                            self.write(", ");
22517                        }
22518                    }
22519                    if let Some(dim) = dimension {
22520                        self.write(&dim.to_string());
22521                    }
22522                    self.write(")");
22523                }
22524            }
22525            DataType::Object { fields, modifier } => {
22526                self.write_keyword("OBJECT(");
22527                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
22528                    if i > 0 {
22529                        self.write(", ");
22530                    }
22531                    self.write(name);
22532                    self.write(" ");
22533                    self.generate_data_type(dt)?;
22534                    if *not_null {
22535                        self.write_keyword(" NOT NULL");
22536                    }
22537                }
22538                self.write(")");
22539                if let Some(mod_str) = modifier {
22540                    self.write(" ");
22541                    self.write_keyword(mod_str);
22542                }
22543            }
22544            DataType::Struct { fields, nested } => {
22545                // Dialect-specific struct type mappings
22546                match self.config.dialect {
22547                    Some(DialectType::Snowflake) => {
22548                        // Snowflake maps STRUCT to OBJECT
22549                        self.write_keyword("OBJECT(");
22550                        for (i, field) in fields.iter().enumerate() {
22551                            if i > 0 {
22552                                self.write(", ");
22553                            }
22554                            if !field.name.is_empty() {
22555                                self.write(&field.name);
22556                                self.write(" ");
22557                            }
22558                            self.generate_data_type(&field.data_type)?;
22559                        }
22560                        self.write(")");
22561                    }
22562                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
22563                        // Presto/Trino use ROW(name TYPE, ...) syntax
22564                        self.write_keyword("ROW(");
22565                        for (i, field) in fields.iter().enumerate() {
22566                            if i > 0 {
22567                                self.write(", ");
22568                            }
22569                            if !field.name.is_empty() {
22570                                self.write(&field.name);
22571                                self.write(" ");
22572                            }
22573                            self.generate_data_type(&field.data_type)?;
22574                        }
22575                        self.write(")");
22576                    }
22577                    Some(DialectType::DuckDB) => {
22578                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
22579                        self.write_keyword("STRUCT(");
22580                        for (i, field) in fields.iter().enumerate() {
22581                            if i > 0 {
22582                                self.write(", ");
22583                            }
22584                            if !field.name.is_empty() {
22585                                self.write(&field.name);
22586                                self.write(" ");
22587                            }
22588                            self.generate_data_type(&field.data_type)?;
22589                        }
22590                        self.write(")");
22591                    }
22592                    Some(DialectType::ClickHouse) => {
22593                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
22594                        self.write("Tuple(");
22595                        for (i, field) in fields.iter().enumerate() {
22596                            if i > 0 {
22597                                self.write(", ");
22598                            }
22599                            if !field.name.is_empty() {
22600                                self.write(&field.name);
22601                                self.write(" ");
22602                            }
22603                            self.generate_data_type(&field.data_type)?;
22604                        }
22605                        self.write(")");
22606                    }
22607                    Some(DialectType::SingleStore) => {
22608                        // SingleStore uses RECORD(name TYPE, ...) for struct types
22609                        self.write_keyword("RECORD(");
22610                        for (i, field) in fields.iter().enumerate() {
22611                            if i > 0 {
22612                                self.write(", ");
22613                            }
22614                            if !field.name.is_empty() {
22615                                self.write(&field.name);
22616                                self.write(" ");
22617                            }
22618                            self.generate_data_type(&field.data_type)?;
22619                        }
22620                        self.write(")");
22621                    }
22622                    _ => {
22623                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
22624                        let force_angle_brackets = matches!(
22625                            self.config.dialect,
22626                            Some(DialectType::Hive)
22627                                | Some(DialectType::Spark)
22628                                | Some(DialectType::Databricks)
22629                        );
22630                        if *nested && !force_angle_brackets {
22631                            self.write_keyword("STRUCT(");
22632                            for (i, field) in fields.iter().enumerate() {
22633                                if i > 0 {
22634                                    self.write(", ");
22635                                }
22636                                if !field.name.is_empty() {
22637                                    self.write(&field.name);
22638                                    self.write(" ");
22639                                }
22640                                self.generate_data_type(&field.data_type)?;
22641                            }
22642                            self.write(")");
22643                        } else {
22644                            self.write_keyword("STRUCT<");
22645                            for (i, field) in fields.iter().enumerate() {
22646                                if i > 0 {
22647                                    self.write(", ");
22648                                }
22649                                if !field.name.is_empty() {
22650                                    // Named field: name TYPE (with configurable separator for Hive)
22651                                    self.write(&field.name);
22652                                    self.write(self.config.struct_field_sep);
22653                                }
22654                                // For anonymous fields, just output the type
22655                                self.generate_data_type(&field.data_type)?;
22656                                // Spark/Databricks: Output COMMENT clause if present
22657                                if let Some(comment) = &field.comment {
22658                                    self.write(" COMMENT '");
22659                                    self.write(comment);
22660                                    self.write("'");
22661                                }
22662                                // BigQuery: Output OPTIONS clause if present
22663                                if !field.options.is_empty() {
22664                                    self.write(" ");
22665                                    self.generate_options_clause(&field.options)?;
22666                                }
22667                            }
22668                            self.write(">");
22669                        }
22670                    }
22671                }
22672            }
22673            DataType::Enum {
22674                values,
22675                assignments,
22676            } => {
22677                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
22678                // ClickHouse: Enum('hello' = 1, 'world' = 2)
22679                if self.config.dialect == Some(DialectType::ClickHouse) {
22680                    self.write("Enum(");
22681                } else {
22682                    self.write_keyword("ENUM(");
22683                }
22684                for (i, val) in values.iter().enumerate() {
22685                    if i > 0 {
22686                        self.write(", ");
22687                    }
22688                    self.write("'");
22689                    self.write(val);
22690                    self.write("'");
22691                    if let Some(Some(assignment)) = assignments.get(i) {
22692                        self.write(" = ");
22693                        self.write(assignment);
22694                    }
22695                }
22696                self.write(")");
22697            }
22698            DataType::Set { values } => {
22699                // MySQL SET type: SET('a', 'b', 'c')
22700                self.write_keyword("SET(");
22701                for (i, val) in values.iter().enumerate() {
22702                    if i > 0 {
22703                        self.write(", ");
22704                    }
22705                    self.write("'");
22706                    self.write(val);
22707                    self.write("'");
22708                }
22709                self.write(")");
22710            }
22711            DataType::Union { fields } => {
22712                // DuckDB UNION type: UNION(num INT, str TEXT)
22713                self.write_keyword("UNION(");
22714                for (i, (name, dt)) in fields.iter().enumerate() {
22715                    if i > 0 {
22716                        self.write(", ");
22717                    }
22718                    if !name.is_empty() {
22719                        self.write(name);
22720                        self.write(" ");
22721                    }
22722                    self.generate_data_type(dt)?;
22723                }
22724                self.write(")");
22725            }
22726            DataType::Nullable { inner } => {
22727                // ClickHouse: Nullable(T), other dialects: just the inner type
22728                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22729                    self.write("Nullable(");
22730                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
22731                    let saved_depth = self.clickhouse_nullable_depth;
22732                    self.clickhouse_nullable_depth = -1;
22733                    self.generate_data_type(inner)?;
22734                    self.clickhouse_nullable_depth = saved_depth;
22735                    self.write(")");
22736                } else {
22737                    // Map ClickHouse-specific custom type names to standard types
22738                    match inner.as_ref() {
22739                        DataType::Custom { name } if name.to_uppercase() == "DATETIME" => {
22740                            self.generate_data_type(&DataType::Timestamp {
22741                                precision: None,
22742                                timezone: false,
22743                            })?;
22744                        }
22745                        _ => {
22746                            self.generate_data_type(inner)?;
22747                        }
22748                    }
22749                }
22750            }
22751            DataType::Custom { name } => {
22752                // Handle dialect-specific type transformations
22753                let name_upper = name.to_uppercase();
22754                match self.config.dialect {
22755                    Some(DialectType::ClickHouse) => {
22756                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
22757                            (name_upper[..idx].to_string(), &name[idx..])
22758                        } else {
22759                            (name_upper.clone(), "")
22760                        };
22761                        let mapped = match base_upper.as_str() {
22762                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
22763                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
22764                            "DATETIME64" => "DateTime64",
22765                            "DATE32" => "Date32",
22766                            "INT" => "Int32",
22767                            "MEDIUMINT" => "Int32",
22768                            "INT8" => "Int8",
22769                            "INT16" => "Int16",
22770                            "INT32" => "Int32",
22771                            "INT64" => "Int64",
22772                            "INT128" => "Int128",
22773                            "INT256" => "Int256",
22774                            "UINT8" => "UInt8",
22775                            "UINT16" => "UInt16",
22776                            "UINT32" => "UInt32",
22777                            "UINT64" => "UInt64",
22778                            "UINT128" => "UInt128",
22779                            "UINT256" => "UInt256",
22780                            "FLOAT32" => "Float32",
22781                            "FLOAT64" => "Float64",
22782                            "DECIMAL32" => "Decimal32",
22783                            "DECIMAL64" => "Decimal64",
22784                            "DECIMAL128" => "Decimal128",
22785                            "DECIMAL256" => "Decimal256",
22786                            "ENUM" => "Enum",
22787                            "ENUM8" => "Enum8",
22788                            "ENUM16" => "Enum16",
22789                            "FIXEDSTRING" => "FixedString",
22790                            "NESTED" => "Nested",
22791                            "LOWCARDINALITY" => "LowCardinality",
22792                            "NULLABLE" => "Nullable",
22793                            "IPV4" => "IPv4",
22794                            "IPV6" => "IPv6",
22795                            "POINT" => "Point",
22796                            "RING" => "Ring",
22797                            "LINESTRING" => "LineString",
22798                            "MULTILINESTRING" => "MultiLineString",
22799                            "POLYGON" => "Polygon",
22800                            "MULTIPOLYGON" => "MultiPolygon",
22801                            "AGGREGATEFUNCTION" => "AggregateFunction",
22802                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
22803                            "DYNAMIC" => "Dynamic",
22804                            _ => "",
22805                        };
22806                        if mapped.is_empty() {
22807                            self.write(name);
22808                        } else {
22809                            self.write(mapped);
22810                            self.write(suffix);
22811                        }
22812                    }
22813                    Some(DialectType::MySQL)
22814                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
22815                    {
22816                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
22817                        self.write_keyword("TIMESTAMP");
22818                    }
22819                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
22820                        self.write_keyword("SQL_VARIANT");
22821                    }
22822                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
22823                        self.write_keyword("DECIMAL(38, 5)");
22824                    }
22825                    Some(DialectType::Exasol) => {
22826                        // Exasol type mappings for custom types
22827                        match name_upper.as_str() {
22828                            // Binary types → VARCHAR
22829                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
22830                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
22831                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
22832                            // Integer types
22833                            "MEDIUMINT" => self.write_keyword("INT"),
22834                            // Decimal types → DECIMAL
22835                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
22836                                self.write_keyword("DECIMAL")
22837                            }
22838                            // Timestamp types
22839                            "DATETIME" => self.write_keyword("TIMESTAMP"),
22840                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
22841                            _ => self.write(name),
22842                        }
22843                    }
22844                    Some(DialectType::Dremio) => {
22845                        // Dremio type mappings for custom types
22846                        match name_upper.as_str() {
22847                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
22848                            "ARRAY" => self.write_keyword("LIST"),
22849                            "NCHAR" => self.write_keyword("VARCHAR"),
22850                            _ => self.write(name),
22851                        }
22852                    }
22853                    // Map dialect-specific custom types to standard SQL types for other dialects
22854                    _ => {
22855                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
22856                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
22857                            (name_upper[..idx].to_string(), Some(&name[idx..]))
22858                        } else {
22859                            (name_upper.clone(), None)
22860                        };
22861
22862                        match base_upper.as_str() {
22863                            "INT64"
22864                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22865                            {
22866                                self.write_keyword("BIGINT");
22867                            }
22868                            "FLOAT64"
22869                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22870                            {
22871                                self.write_keyword("DOUBLE");
22872                            }
22873                            "BOOL"
22874                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22875                            {
22876                                self.write_keyword("BOOLEAN");
22877                            }
22878                            "BYTES"
22879                                if matches!(
22880                                    self.config.dialect,
22881                                    Some(DialectType::Spark)
22882                                        | Some(DialectType::Hive)
22883                                        | Some(DialectType::Databricks)
22884                                ) =>
22885                            {
22886                                self.write_keyword("BINARY");
22887                            }
22888                            "BYTES"
22889                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22890                            {
22891                                self.write_keyword("VARBINARY");
22892                            }
22893                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
22894                            "DATETIME2" | "SMALLDATETIME"
22895                                if !matches!(
22896                                    self.config.dialect,
22897                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22898                                ) =>
22899                            {
22900                                // PostgreSQL preserves precision, others don't
22901                                if matches!(
22902                                    self.config.dialect,
22903                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22904                                ) {
22905                                    self.write_keyword("TIMESTAMP");
22906                                    if let Some(args) = _args_str {
22907                                        self.write(args);
22908                                    }
22909                                } else {
22910                                    self.write_keyword("TIMESTAMP");
22911                                }
22912                            }
22913                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
22914                            "DATETIMEOFFSET"
22915                                if !matches!(
22916                                    self.config.dialect,
22917                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22918                                ) =>
22919                            {
22920                                if matches!(
22921                                    self.config.dialect,
22922                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22923                                ) {
22924                                    self.write_keyword("TIMESTAMPTZ");
22925                                    if let Some(args) = _args_str {
22926                                        self.write(args);
22927                                    }
22928                                } else {
22929                                    self.write_keyword("TIMESTAMPTZ");
22930                                }
22931                            }
22932                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
22933                            "UNIQUEIDENTIFIER"
22934                                if !matches!(
22935                                    self.config.dialect,
22936                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22937                                ) =>
22938                            {
22939                                match self.config.dialect {
22940                                    Some(DialectType::Spark)
22941                                    | Some(DialectType::Databricks)
22942                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
22943                                    _ => self.write_keyword("UUID"),
22944                                }
22945                            }
22946                            // TSQL BIT -> BOOLEAN for most dialects
22947                            "BIT"
22948                                if !matches!(
22949                                    self.config.dialect,
22950                                    Some(DialectType::TSQL)
22951                                        | Some(DialectType::Fabric)
22952                                        | Some(DialectType::PostgreSQL)
22953                                        | Some(DialectType::MySQL)
22954                                        | Some(DialectType::DuckDB)
22955                                ) =>
22956                            {
22957                                self.write_keyword("BOOLEAN");
22958                            }
22959                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
22960                            "NVARCHAR"
22961                                if !matches!(
22962                                    self.config.dialect,
22963                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22964                                ) =>
22965                            {
22966                                match self.config.dialect {
22967                                    Some(DialectType::Oracle) => {
22968                                        // Oracle: NVARCHAR -> NVARCHAR2
22969                                        self.write_keyword("NVARCHAR2");
22970                                        if let Some(args) = _args_str {
22971                                            self.write(args);
22972                                        }
22973                                    }
22974                                    Some(DialectType::BigQuery) => {
22975                                        // BigQuery: NVARCHAR -> STRING
22976                                        self.write_keyword("STRING");
22977                                    }
22978                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
22979                                        self.write_keyword("TEXT");
22980                                        if let Some(args) = _args_str {
22981                                            self.write(args);
22982                                        }
22983                                    }
22984                                    Some(DialectType::Hive) => {
22985                                        // Hive: NVARCHAR -> STRING
22986                                        self.write_keyword("STRING");
22987                                    }
22988                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
22989                                        if _args_str.is_some() {
22990                                            self.write_keyword("VARCHAR");
22991                                            self.write(_args_str.unwrap());
22992                                        } else {
22993                                            self.write_keyword("STRING");
22994                                        }
22995                                    }
22996                                    _ => {
22997                                        self.write_keyword("VARCHAR");
22998                                        if let Some(args) = _args_str {
22999                                            self.write(args);
23000                                        }
23001                                    }
23002                                }
23003                            }
23004                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
23005                            "NCHAR"
23006                                if !matches!(
23007                                    self.config.dialect,
23008                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23009                                ) =>
23010                            {
23011                                match self.config.dialect {
23012                                    Some(DialectType::Oracle) => {
23013                                        // Oracle natively supports NCHAR
23014                                        self.write_keyword("NCHAR");
23015                                        if let Some(args) = _args_str {
23016                                            self.write(args);
23017                                        }
23018                                    }
23019                                    Some(DialectType::BigQuery) => {
23020                                        // BigQuery: NCHAR -> STRING
23021                                        self.write_keyword("STRING");
23022                                    }
23023                                    Some(DialectType::Hive) => {
23024                                        // Hive: NCHAR -> STRING
23025                                        self.write_keyword("STRING");
23026                                    }
23027                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
23028                                        self.write_keyword("TEXT");
23029                                        if let Some(args) = _args_str {
23030                                            self.write(args);
23031                                        }
23032                                    }
23033                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
23034                                        if _args_str.is_some() {
23035                                            self.write_keyword("CHAR");
23036                                            self.write(_args_str.unwrap());
23037                                        } else {
23038                                            self.write_keyword("STRING");
23039                                        }
23040                                    }
23041                                    _ => {
23042                                        self.write_keyword("CHAR");
23043                                        if let Some(args) = _args_str {
23044                                            self.write(args);
23045                                        }
23046                                    }
23047                                }
23048                            }
23049                            // MySQL text variant types -> map to appropriate target type
23050                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
23051                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
23052                                Some(DialectType::MySQL)
23053                                | Some(DialectType::SingleStore)
23054                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
23055                                Some(DialectType::Spark)
23056                                | Some(DialectType::Databricks)
23057                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
23058                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
23059                                Some(DialectType::Presto)
23060                                | Some(DialectType::Trino)
23061                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
23062                                Some(DialectType::Snowflake)
23063                                | Some(DialectType::Redshift)
23064                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
23065                                _ => self.write_keyword("TEXT"),
23066                            },
23067                            // MySQL blob variant types -> map to appropriate target type
23068                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
23069                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
23070                                Some(DialectType::MySQL)
23071                                | Some(DialectType::SingleStore)
23072                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
23073                                Some(DialectType::Spark)
23074                                | Some(DialectType::Databricks)
23075                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
23076                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
23077                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
23078                                Some(DialectType::Presto)
23079                                | Some(DialectType::Trino)
23080                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
23081                                Some(DialectType::Snowflake)
23082                                | Some(DialectType::Redshift)
23083                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
23084                                _ => self.write_keyword("BLOB"),
23085                            },
23086                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
23087                            "LONGVARCHAR" => match self.config.dialect {
23088                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
23089                                _ => self.write_keyword("VARCHAR"),
23090                            },
23091                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
23092                            "DATETIME" => {
23093                                match self.config.dialect {
23094                                    Some(DialectType::MySQL)
23095                                    | Some(DialectType::Doris)
23096                                    | Some(DialectType::StarRocks)
23097                                    | Some(DialectType::TSQL)
23098                                    | Some(DialectType::Fabric)
23099                                    | Some(DialectType::BigQuery)
23100                                    | Some(DialectType::SQLite)
23101                                    | Some(DialectType::Snowflake) => {
23102                                        self.write_keyword("DATETIME");
23103                                        if let Some(args) = _args_str {
23104                                            self.write(args);
23105                                        }
23106                                    }
23107                                    Some(_) => {
23108                                        // Only map to TIMESTAMP when we have a specific target dialect
23109                                        self.write_keyword("TIMESTAMP");
23110                                        if let Some(args) = _args_str {
23111                                            self.write(args);
23112                                        }
23113                                    }
23114                                    None => {
23115                                        // No dialect - preserve original
23116                                        self.write(name);
23117                                    }
23118                                }
23119                            }
23120                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
23121                            "VARCHAR2"
23122                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
23123                            {
23124                                match self.config.dialect {
23125                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23126                                        self.write_keyword("TEXT");
23127                                    }
23128                                    Some(DialectType::Hive)
23129                                    | Some(DialectType::Spark)
23130                                    | Some(DialectType::Databricks)
23131                                    | Some(DialectType::BigQuery)
23132                                    | Some(DialectType::ClickHouse)
23133                                    | Some(DialectType::StarRocks)
23134                                    | Some(DialectType::Doris) => {
23135                                        self.write_keyword("STRING");
23136                                    }
23137                                    _ => {
23138                                        self.write_keyword("VARCHAR");
23139                                        if let Some(args) = _args_str {
23140                                            self.write(args);
23141                                        }
23142                                    }
23143                                }
23144                            }
23145                            "NVARCHAR2"
23146                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
23147                            {
23148                                match self.config.dialect {
23149                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23150                                        self.write_keyword("TEXT");
23151                                    }
23152                                    Some(DialectType::Hive)
23153                                    | Some(DialectType::Spark)
23154                                    | Some(DialectType::Databricks)
23155                                    | Some(DialectType::BigQuery)
23156                                    | Some(DialectType::ClickHouse)
23157                                    | Some(DialectType::StarRocks)
23158                                    | Some(DialectType::Doris) => {
23159                                        self.write_keyword("STRING");
23160                                    }
23161                                    _ => {
23162                                        self.write_keyword("VARCHAR");
23163                                        if let Some(args) = _args_str {
23164                                            self.write(args);
23165                                        }
23166                                    }
23167                                }
23168                            }
23169                            _ => self.write(name),
23170                        }
23171                    }
23172                }
23173            }
23174            DataType::Geometry { subtype, srid } => {
23175                // Dialect-specific geometry type mappings
23176                match self.config.dialect {
23177                    Some(DialectType::MySQL) => {
23178                        // MySQL uses POINT SRID 4326 syntax for specific types
23179                        if let Some(sub) = subtype {
23180                            self.write_keyword(sub);
23181                            if let Some(s) = srid {
23182                                self.write(" SRID ");
23183                                self.write(&s.to_string());
23184                            }
23185                        } else {
23186                            self.write_keyword("GEOMETRY");
23187                        }
23188                    }
23189                    Some(DialectType::BigQuery) => {
23190                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
23191                        self.write_keyword("GEOGRAPHY");
23192                    }
23193                    Some(DialectType::Teradata) => {
23194                        // Teradata uses ST_GEOMETRY
23195                        self.write_keyword("ST_GEOMETRY");
23196                        if subtype.is_some() || srid.is_some() {
23197                            self.write("(");
23198                            if let Some(sub) = subtype {
23199                                self.write_keyword(sub);
23200                            }
23201                            if let Some(s) = srid {
23202                                if subtype.is_some() {
23203                                    self.write(", ");
23204                                }
23205                                self.write(&s.to_string());
23206                            }
23207                            self.write(")");
23208                        }
23209                    }
23210                    _ => {
23211                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
23212                        self.write_keyword("GEOMETRY");
23213                        if subtype.is_some() || srid.is_some() {
23214                            self.write("(");
23215                            if let Some(sub) = subtype {
23216                                self.write_keyword(sub);
23217                            }
23218                            if let Some(s) = srid {
23219                                if subtype.is_some() {
23220                                    self.write(", ");
23221                                }
23222                                self.write(&s.to_string());
23223                            }
23224                            self.write(")");
23225                        }
23226                    }
23227                }
23228            }
23229            DataType::Geography { subtype, srid } => {
23230                // Dialect-specific geography type mappings
23231                match self.config.dialect {
23232                    Some(DialectType::MySQL) => {
23233                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
23234                        if let Some(sub) = subtype {
23235                            self.write_keyword(sub);
23236                        } else {
23237                            self.write_keyword("GEOMETRY");
23238                        }
23239                        // Geography implies SRID 4326 (WGS84)
23240                        let effective_srid = srid.unwrap_or(4326);
23241                        self.write(" SRID ");
23242                        self.write(&effective_srid.to_string());
23243                    }
23244                    Some(DialectType::BigQuery) => {
23245                        // BigQuery uses simple GEOGRAPHY without parameters
23246                        self.write_keyword("GEOGRAPHY");
23247                    }
23248                    Some(DialectType::Snowflake) => {
23249                        // Snowflake uses GEOGRAPHY without parameters
23250                        self.write_keyword("GEOGRAPHY");
23251                    }
23252                    _ => {
23253                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
23254                        self.write_keyword("GEOGRAPHY");
23255                        if subtype.is_some() || srid.is_some() {
23256                            self.write("(");
23257                            if let Some(sub) = subtype {
23258                                self.write_keyword(sub);
23259                            }
23260                            if let Some(s) = srid {
23261                                if subtype.is_some() {
23262                                    self.write(", ");
23263                                }
23264                                self.write(&s.to_string());
23265                            }
23266                            self.write(")");
23267                        }
23268                    }
23269                }
23270            }
23271            DataType::CharacterSet { name } => {
23272                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
23273                self.write_keyword("CHAR CHARACTER SET ");
23274                self.write(name);
23275            }
23276            _ => self.write("UNKNOWN"),
23277        }
23278        Ok(())
23279    }
23280
23281    // === Helper methods ===
23282
23283    fn write(&mut self, s: &str) {
23284        self.output.push_str(s);
23285    }
23286
23287    fn write_space(&mut self) {
23288        self.output.push(' ');
23289    }
23290
23291    fn write_keyword(&mut self, keyword: &str) {
23292        if self.config.uppercase_keywords {
23293            self.output.push_str(keyword);
23294        } else {
23295            self.output.push_str(&keyword.to_lowercase());
23296        }
23297    }
23298
23299    /// Write a function name respecting the normalize_functions config setting
23300    fn write_func_name(&mut self, name: &str) {
23301        let normalized = self.normalize_func_name(name);
23302        self.output.push_str(&normalized);
23303    }
23304
23305    /// Convert strptime format string to Exasol format string
23306    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
23307    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
23308    fn convert_strptime_to_exasol_format(format: &str) -> String {
23309        let mut result = String::new();
23310        let chars: Vec<char> = format.chars().collect();
23311        let mut i = 0;
23312        while i < chars.len() {
23313            if chars[i] == '%' && i + 1 < chars.len() {
23314                let spec = chars[i + 1];
23315                let exasol_spec = match spec {
23316                    'Y' => "YYYY",
23317                    'y' => "YY",
23318                    'm' => "MM",
23319                    'd' => "DD",
23320                    'H' => "HH",
23321                    'M' => "MI",
23322                    'S' => "SS",
23323                    'a' => "DY",    // abbreviated weekday name
23324                    'A' => "DAY",   // full weekday name
23325                    'b' => "MON",   // abbreviated month name
23326                    'B' => "MONTH", // full month name
23327                    'I' => "H12",   // 12-hour format
23328                    'u' => "ID",    // ISO weekday (1-7)
23329                    'V' => "IW",    // ISO week number
23330                    'G' => "IYYY",  // ISO year
23331                    'W' => "UW",    // Week number (Monday as first day)
23332                    'U' => "UW",    // Week number (Sunday as first day)
23333                    'z' => "Z",     // timezone offset
23334                    _ => {
23335                        // Unknown specifier, keep as-is
23336                        result.push('%');
23337                        result.push(spec);
23338                        i += 2;
23339                        continue;
23340                    }
23341                };
23342                result.push_str(exasol_spec);
23343                i += 2;
23344            } else {
23345                result.push(chars[i]);
23346                i += 1;
23347            }
23348        }
23349        result
23350    }
23351
23352    /// Convert strptime format string to PostgreSQL/Redshift format string
23353    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
23354    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
23355    fn convert_strptime_to_postgres_format(format: &str) -> String {
23356        let mut result = String::new();
23357        let chars: Vec<char> = format.chars().collect();
23358        let mut i = 0;
23359        while i < chars.len() {
23360            if chars[i] == '%' && i + 1 < chars.len() {
23361                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
23362                if chars[i + 1] == '-' && i + 2 < chars.len() {
23363                    let spec = chars[i + 2];
23364                    let pg_spec = match spec {
23365                        'd' => "FMDD",
23366                        'm' => "FMMM",
23367                        'H' => "FMHH24",
23368                        'M' => "FMMI",
23369                        'S' => "FMSS",
23370                        _ => {
23371                            result.push('%');
23372                            result.push('-');
23373                            result.push(spec);
23374                            i += 3;
23375                            continue;
23376                        }
23377                    };
23378                    result.push_str(pg_spec);
23379                    i += 3;
23380                    continue;
23381                }
23382                let spec = chars[i + 1];
23383                let pg_spec = match spec {
23384                    'Y' => "YYYY",
23385                    'y' => "YY",
23386                    'm' => "MM",
23387                    'd' => "DD",
23388                    'H' => "HH24",
23389                    'I' => "HH12",
23390                    'M' => "MI",
23391                    'S' => "SS",
23392                    'f' => "US",      // microseconds
23393                    'u' => "D",       // day of week (1=Monday)
23394                    'j' => "DDD",     // day of year
23395                    'z' => "OF",      // UTC offset
23396                    'Z' => "TZ",      // timezone name
23397                    'A' => "TMDay",   // full weekday name
23398                    'a' => "TMDy",    // abbreviated weekday name
23399                    'b' => "TMMon",   // abbreviated month name
23400                    'B' => "TMMonth", // full month name
23401                    'U' => "WW",      // week number
23402                    _ => {
23403                        // Unknown specifier, keep as-is
23404                        result.push('%');
23405                        result.push(spec);
23406                        i += 2;
23407                        continue;
23408                    }
23409                };
23410                result.push_str(pg_spec);
23411                i += 2;
23412            } else {
23413                result.push(chars[i]);
23414                i += 1;
23415            }
23416        }
23417        result
23418    }
23419
23420    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
23421    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
23422        if self.config.limit_only_literals {
23423            if let Some(value) = Self::try_evaluate_constant(expr) {
23424                self.write(&value.to_string());
23425                return Ok(());
23426            }
23427        }
23428        self.generate_expression(expr)
23429    }
23430
23431    /// Format a comment with proper spacing.
23432    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
23433    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
23434    fn write_formatted_comment(&mut self, comment: &str) {
23435        // Normalize all comments to block comment format /* ... */
23436        // This matches Python sqlglot behavior which always outputs block comments
23437        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
23438            // Already block comment - extract inner content
23439            // Preserve internal whitespace, but ensure at least one space padding
23440            &comment[2..comment.len() - 2]
23441        } else if comment.starts_with("--") {
23442            // Line comment - extract content after --
23443            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
23444            &comment[2..]
23445        } else {
23446            // Raw content (no delimiters)
23447            comment
23448        };
23449        // Skip empty comments (e.g., bare "--" with no content)
23450        if content.trim().is_empty() {
23451            return;
23452        }
23453        // Ensure at least one space after /* and before */
23454        self.output.push_str("/*");
23455        if !content.starts_with(' ') {
23456            self.output.push(' ');
23457        }
23458        self.output.push_str(content);
23459        if !content.ends_with(' ') {
23460            self.output.push(' ');
23461        }
23462        self.output.push_str("*/");
23463    }
23464
23465    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
23466    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
23467    fn escape_block_for_single_quote(&self, block: &str) -> String {
23468        let escape_backslash = matches!(
23469            self.config.dialect,
23470            Some(crate::dialects::DialectType::Snowflake)
23471        );
23472        let mut escaped = String::with_capacity(block.len() + 4);
23473        for ch in block.chars() {
23474            if ch == '\'' {
23475                escaped.push('\\');
23476                escaped.push('\'');
23477            } else if escape_backslash && ch == '\\' {
23478                escaped.push('\\');
23479                escaped.push('\\');
23480            } else {
23481                escaped.push(ch);
23482            }
23483        }
23484        escaped
23485    }
23486
23487    fn write_newline(&mut self) {
23488        self.output.push('\n');
23489    }
23490
23491    fn write_indent(&mut self) {
23492        for _ in 0..self.indent_level {
23493            self.output.push_str(&self.config.indent);
23494        }
23495    }
23496
23497    // === SQLGlot-style pretty printing helpers ===
23498
23499    /// Returns the separator string for pretty printing.
23500    /// Check if the total length of arguments exceeds max_text_width.
23501    /// Used for dynamic line breaking in expressions() formatting.
23502    fn too_wide(&self, args: &[String]) -> bool {
23503        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
23504    }
23505
23506    /// Generate an expression to a string using a temporary non-pretty generator.
23507    /// Useful for width calculations before deciding on formatting.
23508    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
23509        let config = GeneratorConfig {
23510            pretty: false,
23511            dialect: self.config.dialect,
23512            ..Default::default()
23513        };
23514        let mut gen = Generator::with_config(config);
23515        gen.generate_expression(expr)?;
23516        Ok(gen.output)
23517    }
23518
23519    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
23520    /// In pretty mode: newline + indented keyword + newline + indented condition
23521    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
23522        if self.config.pretty {
23523            self.write_newline();
23524            self.write_indent();
23525            self.write_keyword(keyword);
23526            self.write_newline();
23527            self.indent_level += 1;
23528            self.write_indent();
23529            self.generate_expression(condition)?;
23530            self.indent_level -= 1;
23531        } else {
23532            self.write_space();
23533            self.write_keyword(keyword);
23534            self.write_space();
23535            self.generate_expression(condition)?;
23536        }
23537        Ok(())
23538    }
23539
23540    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
23541    /// In pretty mode: each expression on new line with indentation
23542    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
23543        if exprs.is_empty() {
23544            return Ok(());
23545        }
23546
23547        if self.config.pretty {
23548            self.write_newline();
23549            self.write_indent();
23550            self.write_keyword(keyword);
23551            self.write_newline();
23552            self.indent_level += 1;
23553            for (i, expr) in exprs.iter().enumerate() {
23554                if i > 0 {
23555                    self.write(",");
23556                    self.write_newline();
23557                }
23558                self.write_indent();
23559                self.generate_expression(expr)?;
23560            }
23561            self.indent_level -= 1;
23562        } else {
23563            self.write_space();
23564            self.write_keyword(keyword);
23565            self.write_space();
23566            for (i, expr) in exprs.iter().enumerate() {
23567                if i > 0 {
23568                    self.write(", ");
23569                }
23570                self.generate_expression(expr)?;
23571            }
23572        }
23573        Ok(())
23574    }
23575
23576    /// Writes ORDER BY / SORT BY clause with Ordered expressions
23577    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
23578        if orderings.is_empty() {
23579            return Ok(());
23580        }
23581
23582        if self.config.pretty {
23583            self.write_newline();
23584            self.write_indent();
23585            self.write_keyword(keyword);
23586            self.write_newline();
23587            self.indent_level += 1;
23588            for (i, ordered) in orderings.iter().enumerate() {
23589                if i > 0 {
23590                    self.write(",");
23591                    self.write_newline();
23592                }
23593                self.write_indent();
23594                self.generate_ordered(ordered)?;
23595            }
23596            self.indent_level -= 1;
23597        } else {
23598            self.write_space();
23599            self.write_keyword(keyword);
23600            self.write_space();
23601            for (i, ordered) in orderings.iter().enumerate() {
23602                if i > 0 {
23603                    self.write(", ");
23604                }
23605                self.generate_ordered(ordered)?;
23606            }
23607        }
23608        Ok(())
23609    }
23610
23611    /// Writes WINDOW clause with named window definitions
23612    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
23613        if windows.is_empty() {
23614            return Ok(());
23615        }
23616
23617        if self.config.pretty {
23618            self.write_newline();
23619            self.write_indent();
23620            self.write_keyword("WINDOW");
23621            self.write_newline();
23622            self.indent_level += 1;
23623            for (i, named_window) in windows.iter().enumerate() {
23624                if i > 0 {
23625                    self.write(",");
23626                    self.write_newline();
23627                }
23628                self.write_indent();
23629                self.generate_identifier(&named_window.name)?;
23630                self.write_space();
23631                self.write_keyword("AS");
23632                self.write(" (");
23633                self.generate_over(&named_window.spec)?;
23634                self.write(")");
23635            }
23636            self.indent_level -= 1;
23637        } else {
23638            self.write_space();
23639            self.write_keyword("WINDOW");
23640            self.write_space();
23641            for (i, named_window) in windows.iter().enumerate() {
23642                if i > 0 {
23643                    self.write(", ");
23644                }
23645                self.generate_identifier(&named_window.name)?;
23646                self.write_space();
23647                self.write_keyword("AS");
23648                self.write(" (");
23649                self.generate_over(&named_window.spec)?;
23650                self.write(")");
23651            }
23652        }
23653        Ok(())
23654    }
23655
23656    // === BATCH-GENERATED STUB METHODS (481 variants) ===
23657    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
23658        // AI_AGG(this, expression)
23659        self.write_keyword("AI_AGG");
23660        self.write("(");
23661        self.generate_expression(&e.this)?;
23662        self.write(", ");
23663        self.generate_expression(&e.expression)?;
23664        self.write(")");
23665        Ok(())
23666    }
23667
23668    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
23669        // AI_CLASSIFY(input, [categories], [config])
23670        self.write_keyword("AI_CLASSIFY");
23671        self.write("(");
23672        self.generate_expression(&e.this)?;
23673        if let Some(categories) = &e.categories {
23674            self.write(", ");
23675            self.generate_expression(categories)?;
23676        }
23677        if let Some(config) = &e.config {
23678            self.write(", ");
23679            self.generate_expression(config)?;
23680        }
23681        self.write(")");
23682        Ok(())
23683    }
23684
23685    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
23686        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
23687        self.write_keyword("ADD");
23688        self.write_space();
23689        if e.exists {
23690            self.write_keyword("IF NOT EXISTS");
23691            self.write_space();
23692        }
23693        self.generate_expression(&e.this)?;
23694        if let Some(location) = &e.location {
23695            self.write_space();
23696            self.generate_expression(location)?;
23697        }
23698        Ok(())
23699    }
23700
23701    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
23702        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
23703        self.write_keyword("ALGORITHM");
23704        self.write("=");
23705        self.generate_expression(&e.this)?;
23706        Ok(())
23707    }
23708
23709    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
23710        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
23711        self.generate_expression(&e.this)?;
23712        self.write_space();
23713        self.write_keyword("AS");
23714        self.write(" (");
23715        for (i, expr) in e.expressions.iter().enumerate() {
23716            if i > 0 {
23717                self.write(", ");
23718            }
23719            self.generate_expression(expr)?;
23720        }
23721        self.write(")");
23722        Ok(())
23723    }
23724
23725    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
23726        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
23727        self.write_keyword("ALLOWED_VALUES");
23728        self.write_space();
23729        for (i, expr) in e.expressions.iter().enumerate() {
23730            if i > 0 {
23731                self.write(", ");
23732            }
23733            self.generate_expression(expr)?;
23734        }
23735        Ok(())
23736    }
23737
23738    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
23739        // Python: complex logic based on dtype, default, comment, visible, etc.
23740        self.write_keyword("ALTER COLUMN");
23741        self.write_space();
23742        self.generate_expression(&e.this)?;
23743
23744        if let Some(dtype) = &e.dtype {
23745            self.write_space();
23746            self.write_keyword("SET DATA TYPE");
23747            self.write_space();
23748            self.generate_expression(dtype)?;
23749            if let Some(collate) = &e.collate {
23750                self.write_space();
23751                self.write_keyword("COLLATE");
23752                self.write_space();
23753                self.generate_expression(collate)?;
23754            }
23755            if let Some(using) = &e.using {
23756                self.write_space();
23757                self.write_keyword("USING");
23758                self.write_space();
23759                self.generate_expression(using)?;
23760            }
23761        } else if let Some(default) = &e.default {
23762            self.write_space();
23763            self.write_keyword("SET DEFAULT");
23764            self.write_space();
23765            self.generate_expression(default)?;
23766        } else if let Some(comment) = &e.comment {
23767            self.write_space();
23768            self.write_keyword("COMMENT");
23769            self.write_space();
23770            self.generate_expression(comment)?;
23771        } else if let Some(drop) = &e.drop {
23772            self.write_space();
23773            self.write_keyword("DROP");
23774            self.write_space();
23775            self.generate_expression(drop)?;
23776        } else if let Some(visible) = &e.visible {
23777            self.write_space();
23778            self.generate_expression(visible)?;
23779        } else if let Some(rename_to) = &e.rename_to {
23780            self.write_space();
23781            self.write_keyword("RENAME TO");
23782            self.write_space();
23783            self.generate_expression(rename_to)?;
23784        } else if let Some(allow_null) = &e.allow_null {
23785            self.write_space();
23786            self.generate_expression(allow_null)?;
23787        }
23788        Ok(())
23789    }
23790
23791    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
23792        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
23793        self.write_keyword("ALTER SESSION");
23794        self.write_space();
23795        if e.unset.is_some() {
23796            self.write_keyword("UNSET");
23797        } else {
23798            self.write_keyword("SET");
23799        }
23800        self.write_space();
23801        for (i, expr) in e.expressions.iter().enumerate() {
23802            if i > 0 {
23803                self.write(", ");
23804            }
23805            self.generate_expression(expr)?;
23806        }
23807        Ok(())
23808    }
23809
23810    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
23811        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
23812        self.write_keyword("SET");
23813
23814        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
23815        if let Some(opt) = &e.option {
23816            self.write_space();
23817            self.generate_expression(opt)?;
23818        }
23819
23820        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
23821        // Check if expressions look like property assignments
23822        if !e.expressions.is_empty() {
23823            // Check if this looks like property assignments (for SET PROPERTIES)
23824            let is_properties = e
23825                .expressions
23826                .iter()
23827                .any(|expr| matches!(expr, Expression::Eq(_)));
23828            if is_properties && e.option.is_none() {
23829                self.write_space();
23830                self.write_keyword("PROPERTIES");
23831            }
23832            self.write_space();
23833            for (i, expr) in e.expressions.iter().enumerate() {
23834                if i > 0 {
23835                    self.write(", ");
23836                }
23837                self.generate_expression(expr)?;
23838            }
23839        }
23840
23841        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
23842        if let Some(file_format) = &e.file_format {
23843            self.write(" ");
23844            self.write_keyword("STAGE_FILE_FORMAT");
23845            self.write(" = (");
23846            self.generate_space_separated_properties(file_format)?;
23847            self.write(")");
23848        }
23849
23850        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
23851        if let Some(copy_options) = &e.copy_options {
23852            self.write(" ");
23853            self.write_keyword("STAGE_COPY_OPTIONS");
23854            self.write(" = (");
23855            self.generate_space_separated_properties(copy_options)?;
23856            self.write(")");
23857        }
23858
23859        // Generate TAG ...
23860        if let Some(tag) = &e.tag {
23861            self.write(" ");
23862            self.write_keyword("TAG");
23863            self.write(" ");
23864            self.generate_expression(tag)?;
23865        }
23866
23867        Ok(())
23868    }
23869
23870    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
23871    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
23872        match expr {
23873            Expression::Tuple(t) => {
23874                for (i, prop) in t.expressions.iter().enumerate() {
23875                    if i > 0 {
23876                        self.write(" ");
23877                    }
23878                    self.generate_expression(prop)?;
23879                }
23880            }
23881            _ => {
23882                self.generate_expression(expr)?;
23883            }
23884        }
23885        Ok(())
23886    }
23887
23888    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
23889        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
23890        self.write_keyword("ALTER");
23891        if e.compound.is_some() {
23892            self.write_space();
23893            self.write_keyword("COMPOUND");
23894        }
23895        self.write_space();
23896        self.write_keyword("SORTKEY");
23897        self.write_space();
23898        if let Some(this) = &e.this {
23899            self.generate_expression(this)?;
23900        } else if !e.expressions.is_empty() {
23901            self.write("(");
23902            for (i, expr) in e.expressions.iter().enumerate() {
23903                if i > 0 {
23904                    self.write(", ");
23905                }
23906                self.generate_expression(expr)?;
23907            }
23908            self.write(")");
23909        }
23910        Ok(())
23911    }
23912
23913    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
23914        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
23915        self.write_keyword("ANALYZE");
23916        if !e.options.is_empty() {
23917            self.write_space();
23918            for (i, opt) in e.options.iter().enumerate() {
23919                if i > 0 {
23920                    self.write_space();
23921                }
23922                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
23923                if let Expression::Identifier(id) = opt {
23924                    self.write_keyword(&id.name);
23925                } else {
23926                    self.generate_expression(opt)?;
23927                }
23928            }
23929        }
23930        if let Some(kind) = &e.kind {
23931            self.write_space();
23932            self.write_keyword(kind);
23933        }
23934        if let Some(this) = &e.this {
23935            self.write_space();
23936            self.generate_expression(this)?;
23937        }
23938        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
23939        if !e.columns.is_empty() {
23940            self.write("(");
23941            for (i, col) in e.columns.iter().enumerate() {
23942                if i > 0 {
23943                    self.write(", ");
23944                }
23945                self.write(col);
23946            }
23947            self.write(")");
23948        }
23949        if let Some(partition) = &e.partition {
23950            self.write_space();
23951            self.generate_expression(partition)?;
23952        }
23953        if let Some(mode) = &e.mode {
23954            self.write_space();
23955            self.generate_expression(mode)?;
23956        }
23957        if let Some(expression) = &e.expression {
23958            self.write_space();
23959            self.generate_expression(expression)?;
23960        }
23961        if !e.properties.is_empty() {
23962            self.write_space();
23963            self.write_keyword(self.config.with_properties_prefix);
23964            self.write(" (");
23965            for (i, prop) in e.properties.iter().enumerate() {
23966                if i > 0 {
23967                    self.write(", ");
23968                }
23969                self.generate_expression(prop)?;
23970            }
23971            self.write(")");
23972        }
23973        Ok(())
23974    }
23975
23976    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
23977        // Python: return f"DELETE{kind} STATISTICS"
23978        self.write_keyword("DELETE");
23979        if let Some(kind) = &e.kind {
23980            self.write_space();
23981            self.write_keyword(kind);
23982        }
23983        self.write_space();
23984        self.write_keyword("STATISTICS");
23985        Ok(())
23986    }
23987
23988    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
23989        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
23990        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
23991        if let Expression::Identifier(id) = e.this.as_ref() {
23992            self.write_keyword(&id.name);
23993        } else {
23994            self.generate_expression(&e.this)?;
23995        }
23996        self.write_space();
23997        self.write_keyword("HISTOGRAM ON");
23998        self.write_space();
23999        for (i, expr) in e.expressions.iter().enumerate() {
24000            if i > 0 {
24001                self.write(", ");
24002            }
24003            self.generate_expression(expr)?;
24004        }
24005        if let Some(expression) = &e.expression {
24006            self.write_space();
24007            self.generate_expression(expression)?;
24008        }
24009        if let Some(update_options) = &e.update_options {
24010            self.write_space();
24011            self.generate_expression(update_options)?;
24012            self.write_space();
24013            self.write_keyword("UPDATE");
24014        }
24015        Ok(())
24016    }
24017
24018    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
24019        // Python: return f"LIST CHAINED ROWS{inner_expression}"
24020        self.write_keyword("LIST CHAINED ROWS");
24021        if let Some(expression) = &e.expression {
24022            self.write_space();
24023            self.write_keyword("INTO");
24024            self.write_space();
24025            self.generate_expression(expression)?;
24026        }
24027        Ok(())
24028    }
24029
24030    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
24031        // Python: return f"SAMPLE {sample} {kind}"
24032        self.write_keyword("SAMPLE");
24033        self.write_space();
24034        if let Some(sample) = &e.sample {
24035            self.generate_expression(sample)?;
24036            self.write_space();
24037        }
24038        self.write_keyword(&e.kind);
24039        Ok(())
24040    }
24041
24042    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
24043        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
24044        self.write_keyword(&e.kind);
24045        if let Some(option) = &e.option {
24046            self.write_space();
24047            self.generate_expression(option)?;
24048        }
24049        self.write_space();
24050        self.write_keyword("STATISTICS");
24051        if let Some(this) = &e.this {
24052            self.write_space();
24053            self.generate_expression(this)?;
24054        }
24055        if !e.expressions.is_empty() {
24056            self.write_space();
24057            for (i, expr) in e.expressions.iter().enumerate() {
24058                if i > 0 {
24059                    self.write(", ");
24060                }
24061                self.generate_expression(expr)?;
24062            }
24063        }
24064        Ok(())
24065    }
24066
24067    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
24068        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
24069        self.write_keyword("VALIDATE");
24070        self.write_space();
24071        self.write_keyword(&e.kind);
24072        if let Some(this) = &e.this {
24073            self.write_space();
24074            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
24075            if let Expression::Identifier(id) = this.as_ref() {
24076                self.write_keyword(&id.name);
24077            } else {
24078                self.generate_expression(this)?;
24079            }
24080        }
24081        if let Some(expression) = &e.expression {
24082            self.write_space();
24083            self.write_keyword("INTO");
24084            self.write_space();
24085            self.generate_expression(expression)?;
24086        }
24087        Ok(())
24088    }
24089
24090    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
24091        // Python: return f"WITH {expressions}"
24092        self.write_keyword("WITH");
24093        self.write_space();
24094        for (i, expr) in e.expressions.iter().enumerate() {
24095            if i > 0 {
24096                self.write(", ");
24097            }
24098            self.generate_expression(expr)?;
24099        }
24100        Ok(())
24101    }
24102
24103    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
24104        // Anonymous represents a generic function call: FUNC_NAME(args...)
24105        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
24106        self.generate_expression(&e.this)?;
24107        self.write("(");
24108        for (i, arg) in e.expressions.iter().enumerate() {
24109            if i > 0 {
24110                self.write(", ");
24111            }
24112            self.generate_expression(arg)?;
24113        }
24114        self.write(")");
24115        Ok(())
24116    }
24117
24118    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
24119        // Same as Anonymous but for aggregate functions
24120        self.generate_expression(&e.this)?;
24121        self.write("(");
24122        for (i, arg) in e.expressions.iter().enumerate() {
24123            if i > 0 {
24124                self.write(", ");
24125            }
24126            self.generate_expression(arg)?;
24127        }
24128        self.write(")");
24129        Ok(())
24130    }
24131
24132    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
24133        // Python: return f"{this} APPLY({expr})"
24134        self.generate_expression(&e.this)?;
24135        self.write_space();
24136        self.write_keyword("APPLY");
24137        self.write("(");
24138        self.generate_expression(&e.expression)?;
24139        self.write(")");
24140        Ok(())
24141    }
24142
24143    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
24144        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
24145        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
24146        self.write("(");
24147        self.generate_expression(&e.this)?;
24148        if let Some(percentile) = &e.percentile {
24149            self.write(", ");
24150            self.generate_expression(percentile)?;
24151        }
24152        self.write(")");
24153        Ok(())
24154    }
24155
24156    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
24157        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
24158        self.write_keyword("APPROX_QUANTILE");
24159        self.write("(");
24160        self.generate_expression(&e.this)?;
24161        if let Some(quantile) = &e.quantile {
24162            self.write(", ");
24163            self.generate_expression(quantile)?;
24164        }
24165        if let Some(accuracy) = &e.accuracy {
24166            self.write(", ");
24167            self.generate_expression(accuracy)?;
24168        }
24169        if let Some(weight) = &e.weight {
24170            self.write(", ");
24171            self.generate_expression(weight)?;
24172        }
24173        self.write(")");
24174        Ok(())
24175    }
24176
24177    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
24178        // APPROX_QUANTILES(this, expression)
24179        self.write_keyword("APPROX_QUANTILES");
24180        self.write("(");
24181        self.generate_expression(&e.this)?;
24182        if let Some(expression) = &e.expression {
24183            self.write(", ");
24184            self.generate_expression(expression)?;
24185        }
24186        self.write(")");
24187        Ok(())
24188    }
24189
24190    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
24191        // APPROX_TOP_K(this[, expression][, counters])
24192        self.write_keyword("APPROX_TOP_K");
24193        self.write("(");
24194        self.generate_expression(&e.this)?;
24195        if let Some(expression) = &e.expression {
24196            self.write(", ");
24197            self.generate_expression(expression)?;
24198        }
24199        if let Some(counters) = &e.counters {
24200            self.write(", ");
24201            self.generate_expression(counters)?;
24202        }
24203        self.write(")");
24204        Ok(())
24205    }
24206
24207    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
24208        // APPROX_TOP_K_ACCUMULATE(this[, expression])
24209        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
24210        self.write("(");
24211        self.generate_expression(&e.this)?;
24212        if let Some(expression) = &e.expression {
24213            self.write(", ");
24214            self.generate_expression(expression)?;
24215        }
24216        self.write(")");
24217        Ok(())
24218    }
24219
24220    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
24221        // APPROX_TOP_K_COMBINE(this[, expression])
24222        self.write_keyword("APPROX_TOP_K_COMBINE");
24223        self.write("(");
24224        self.generate_expression(&e.this)?;
24225        if let Some(expression) = &e.expression {
24226            self.write(", ");
24227            self.generate_expression(expression)?;
24228        }
24229        self.write(")");
24230        Ok(())
24231    }
24232
24233    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
24234        // APPROX_TOP_K_ESTIMATE(this[, expression])
24235        self.write_keyword("APPROX_TOP_K_ESTIMATE");
24236        self.write("(");
24237        self.generate_expression(&e.this)?;
24238        if let Some(expression) = &e.expression {
24239            self.write(", ");
24240            self.generate_expression(expression)?;
24241        }
24242        self.write(")");
24243        Ok(())
24244    }
24245
24246    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
24247        // APPROX_TOP_SUM(this, expression[, count])
24248        self.write_keyword("APPROX_TOP_SUM");
24249        self.write("(");
24250        self.generate_expression(&e.this)?;
24251        self.write(", ");
24252        self.generate_expression(&e.expression)?;
24253        if let Some(count) = &e.count {
24254            self.write(", ");
24255            self.generate_expression(count)?;
24256        }
24257        self.write(")");
24258        Ok(())
24259    }
24260
24261    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
24262        // ARG_MAX(this, expression[, count])
24263        self.write_keyword("ARG_MAX");
24264        self.write("(");
24265        self.generate_expression(&e.this)?;
24266        self.write(", ");
24267        self.generate_expression(&e.expression)?;
24268        if let Some(count) = &e.count {
24269            self.write(", ");
24270            self.generate_expression(count)?;
24271        }
24272        self.write(")");
24273        Ok(())
24274    }
24275
24276    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
24277        // ARG_MIN(this, expression[, count])
24278        self.write_keyword("ARG_MIN");
24279        self.write("(");
24280        self.generate_expression(&e.this)?;
24281        self.write(", ");
24282        self.generate_expression(&e.expression)?;
24283        if let Some(count) = &e.count {
24284            self.write(", ");
24285            self.generate_expression(count)?;
24286        }
24287        self.write(")");
24288        Ok(())
24289    }
24290
24291    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
24292        // ARRAY_ALL(this, expression)
24293        self.write_keyword("ARRAY_ALL");
24294        self.write("(");
24295        self.generate_expression(&e.this)?;
24296        self.write(", ");
24297        self.generate_expression(&e.expression)?;
24298        self.write(")");
24299        Ok(())
24300    }
24301
24302    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
24303        // ARRAY_ANY(this, expression) - fallback implementation
24304        self.write_keyword("ARRAY_ANY");
24305        self.write("(");
24306        self.generate_expression(&e.this)?;
24307        self.write(", ");
24308        self.generate_expression(&e.expression)?;
24309        self.write(")");
24310        Ok(())
24311    }
24312
24313    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
24314        // ARRAY_CONSTRUCT_COMPACT(expressions...)
24315        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
24316        self.write("(");
24317        for (i, expr) in e.expressions.iter().enumerate() {
24318            if i > 0 {
24319                self.write(", ");
24320            }
24321            self.generate_expression(expr)?;
24322        }
24323        self.write(")");
24324        Ok(())
24325    }
24326
24327    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
24328        // ARRAY_SUM(this[, expression])
24329        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24330            self.write("arraySum");
24331        } else {
24332            self.write_keyword("ARRAY_SUM");
24333        }
24334        self.write("(");
24335        self.generate_expression(&e.this)?;
24336        if let Some(expression) = &e.expression {
24337            self.write(", ");
24338            self.generate_expression(expression)?;
24339        }
24340        self.write(")");
24341        Ok(())
24342    }
24343
24344    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
24345        // Python: return f"{this} AT {index}"
24346        self.generate_expression(&e.this)?;
24347        self.write_space();
24348        self.write_keyword("AT");
24349        self.write_space();
24350        self.generate_expression(&e.expression)?;
24351        Ok(())
24352    }
24353
24354    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
24355        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
24356        self.write_keyword("ATTACH");
24357        if e.exists {
24358            self.write_space();
24359            self.write_keyword("IF NOT EXISTS");
24360        }
24361        self.write_space();
24362        self.generate_expression(&e.this)?;
24363        if !e.expressions.is_empty() {
24364            self.write(" (");
24365            for (i, expr) in e.expressions.iter().enumerate() {
24366                if i > 0 {
24367                    self.write(", ");
24368                }
24369                self.generate_expression(expr)?;
24370            }
24371            self.write(")");
24372        }
24373        Ok(())
24374    }
24375
24376    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
24377        // AttachOption: this [expression]
24378        // Python sqlglot: no equals sign, just space-separated
24379        self.generate_expression(&e.this)?;
24380        if let Some(expression) = &e.expression {
24381            self.write_space();
24382            self.generate_expression(expression)?;
24383        }
24384        Ok(())
24385    }
24386
24387    /// Generate the auto_increment keyword and options for a column definition.
24388    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
24389    /// GENERATED AS IDENTITY, etc.
24390    fn generate_auto_increment_keyword(
24391        &mut self,
24392        col: &crate::expressions::ColumnDef,
24393    ) -> Result<()> {
24394        use crate::dialects::DialectType;
24395        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
24396            self.write_keyword("IDENTITY");
24397            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24398                self.write("(");
24399                if let Some(ref start) = col.auto_increment_start {
24400                    self.generate_expression(start)?;
24401                } else {
24402                    self.write("0");
24403                }
24404                self.write(", ");
24405                if let Some(ref inc) = col.auto_increment_increment {
24406                    self.generate_expression(inc)?;
24407                } else {
24408                    self.write("1");
24409                }
24410                self.write(")");
24411            }
24412        } else if matches!(
24413            self.config.dialect,
24414            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
24415        ) {
24416            self.write_keyword("AUTOINCREMENT");
24417            if let Some(ref start) = col.auto_increment_start {
24418                self.write_space();
24419                self.write_keyword("START");
24420                self.write_space();
24421                self.generate_expression(start)?;
24422            }
24423            if let Some(ref inc) = col.auto_increment_increment {
24424                self.write_space();
24425                self.write_keyword("INCREMENT");
24426                self.write_space();
24427                self.generate_expression(inc)?;
24428            }
24429            if let Some(order) = col.auto_increment_order {
24430                self.write_space();
24431                if order {
24432                    self.write_keyword("ORDER");
24433                } else {
24434                    self.write_keyword("NOORDER");
24435                }
24436            }
24437        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
24438            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
24439            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24440                self.write(" (");
24441                let mut first = true;
24442                if let Some(ref start) = col.auto_increment_start {
24443                    self.write_keyword("START WITH");
24444                    self.write_space();
24445                    self.generate_expression(start)?;
24446                    first = false;
24447                }
24448                if let Some(ref inc) = col.auto_increment_increment {
24449                    if !first {
24450                        self.write_space();
24451                    }
24452                    self.write_keyword("INCREMENT BY");
24453                    self.write_space();
24454                    self.generate_expression(inc)?;
24455                }
24456                self.write(")");
24457            }
24458        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
24459            self.write_keyword("GENERATED ALWAYS AS IDENTITY");
24460            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24461                self.write(" (");
24462                let mut first = true;
24463                if let Some(ref start) = col.auto_increment_start {
24464                    self.write_keyword("START WITH");
24465                    self.write_space();
24466                    self.generate_expression(start)?;
24467                    first = false;
24468                }
24469                if let Some(ref inc) = col.auto_increment_increment {
24470                    if !first {
24471                        self.write_space();
24472                    }
24473                    self.write_keyword("INCREMENT BY");
24474                    self.write_space();
24475                    self.generate_expression(inc)?;
24476                }
24477                self.write(")");
24478            }
24479        } else if matches!(
24480            self.config.dialect,
24481            Some(DialectType::TSQL) | Some(DialectType::Fabric)
24482        ) {
24483            self.write_keyword("IDENTITY");
24484            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24485                self.write("(");
24486                if let Some(ref start) = col.auto_increment_start {
24487                    self.generate_expression(start)?;
24488                } else {
24489                    self.write("0");
24490                }
24491                self.write(", ");
24492                if let Some(ref inc) = col.auto_increment_increment {
24493                    self.generate_expression(inc)?;
24494                } else {
24495                    self.write("1");
24496                }
24497                self.write(")");
24498            }
24499        } else {
24500            self.write_keyword("AUTO_INCREMENT");
24501            if let Some(ref start) = col.auto_increment_start {
24502                self.write_space();
24503                self.write_keyword("START");
24504                self.write_space();
24505                self.generate_expression(start)?;
24506            }
24507            if let Some(ref inc) = col.auto_increment_increment {
24508                self.write_space();
24509                self.write_keyword("INCREMENT");
24510                self.write_space();
24511                self.generate_expression(inc)?;
24512            }
24513            if let Some(order) = col.auto_increment_order {
24514                self.write_space();
24515                if order {
24516                    self.write_keyword("ORDER");
24517                } else {
24518                    self.write_keyword("NOORDER");
24519                }
24520            }
24521        }
24522        Ok(())
24523    }
24524
24525    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
24526        // AUTO_INCREMENT=value
24527        self.write_keyword("AUTO_INCREMENT");
24528        self.write("=");
24529        self.generate_expression(&e.this)?;
24530        Ok(())
24531    }
24532
24533    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
24534        // AUTO_REFRESH=value
24535        self.write_keyword("AUTO_REFRESH");
24536        self.write("=");
24537        self.generate_expression(&e.this)?;
24538        Ok(())
24539    }
24540
24541    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
24542        // BACKUP YES|NO (Redshift syntax uses space, not equals)
24543        self.write_keyword("BACKUP");
24544        self.write_space();
24545        self.generate_expression(&e.this)?;
24546        Ok(())
24547    }
24548
24549    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
24550        // BASE64_DECODE_BINARY(this[, alphabet])
24551        self.write_keyword("BASE64_DECODE_BINARY");
24552        self.write("(");
24553        self.generate_expression(&e.this)?;
24554        if let Some(alphabet) = &e.alphabet {
24555            self.write(", ");
24556            self.generate_expression(alphabet)?;
24557        }
24558        self.write(")");
24559        Ok(())
24560    }
24561
24562    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
24563        // BASE64_DECODE_STRING(this[, alphabet])
24564        self.write_keyword("BASE64_DECODE_STRING");
24565        self.write("(");
24566        self.generate_expression(&e.this)?;
24567        if let Some(alphabet) = &e.alphabet {
24568            self.write(", ");
24569            self.generate_expression(alphabet)?;
24570        }
24571        self.write(")");
24572        Ok(())
24573    }
24574
24575    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
24576        // BASE64_ENCODE(this[, max_line_length][, alphabet])
24577        self.write_keyword("BASE64_ENCODE");
24578        self.write("(");
24579        self.generate_expression(&e.this)?;
24580        if let Some(max_line_length) = &e.max_line_length {
24581            self.write(", ");
24582            self.generate_expression(max_line_length)?;
24583        }
24584        if let Some(alphabet) = &e.alphabet {
24585            self.write(", ");
24586            self.generate_expression(alphabet)?;
24587        }
24588        self.write(")");
24589        Ok(())
24590    }
24591
24592    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
24593        // BLOCKCOMPRESSION=... (complex Teradata property)
24594        self.write_keyword("BLOCKCOMPRESSION");
24595        self.write("=");
24596        if let Some(autotemp) = &e.autotemp {
24597            self.write_keyword("AUTOTEMP");
24598            self.write("(");
24599            self.generate_expression(autotemp)?;
24600            self.write(")");
24601        }
24602        if let Some(always) = &e.always {
24603            self.generate_expression(always)?;
24604        }
24605        if let Some(default) = &e.default {
24606            self.generate_expression(default)?;
24607        }
24608        if let Some(manual) = &e.manual {
24609            self.generate_expression(manual)?;
24610        }
24611        if let Some(never) = &e.never {
24612            self.generate_expression(never)?;
24613        }
24614        Ok(())
24615    }
24616
24617    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
24618        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
24619        self.write("((");
24620        self.generate_expression(&e.this)?;
24621        self.write(") ");
24622        self.write_keyword("AND");
24623        self.write(" (");
24624        self.generate_expression(&e.expression)?;
24625        self.write("))");
24626        Ok(())
24627    }
24628
24629    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
24630        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
24631        self.write("((");
24632        self.generate_expression(&e.this)?;
24633        self.write(") ");
24634        self.write_keyword("OR");
24635        self.write(" (");
24636        self.generate_expression(&e.expression)?;
24637        self.write("))");
24638        Ok(())
24639    }
24640
24641    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
24642        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
24643        self.write_keyword("BUILD");
24644        self.write_space();
24645        self.generate_expression(&e.this)?;
24646        Ok(())
24647    }
24648
24649    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
24650        // Byte string literal like B'...' or X'...'
24651        self.generate_expression(&e.this)?;
24652        Ok(())
24653    }
24654
24655    fn generate_case_specific_column_constraint(
24656        &mut self,
24657        e: &CaseSpecificColumnConstraint,
24658    ) -> Result<()> {
24659        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
24660        if e.not_.is_some() {
24661            self.write_keyword("NOT");
24662            self.write_space();
24663        }
24664        self.write_keyword("CASESPECIFIC");
24665        Ok(())
24666    }
24667
24668    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
24669        // Cast to string type (dialect-specific)
24670        self.write_keyword("CAST");
24671        self.write("(");
24672        self.generate_expression(&e.this)?;
24673        if self.config.dialect == Some(DialectType::ClickHouse) {
24674            // ClickHouse: CAST(expr, 'type_string')
24675            self.write(", ");
24676        } else {
24677            self.write_space();
24678            self.write_keyword("AS");
24679            self.write_space();
24680        }
24681        if let Some(to) = &e.to {
24682            self.generate_expression(to)?;
24683        }
24684        self.write(")");
24685        Ok(())
24686    }
24687
24688    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
24689        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
24690        // Python: f"CHANGES ({information}){at_before}{end}"
24691        self.write_keyword("CHANGES");
24692        self.write(" (");
24693        if let Some(information) = &e.information {
24694            self.write_keyword("INFORMATION");
24695            self.write(" => ");
24696            self.generate_expression(information)?;
24697        }
24698        self.write(")");
24699        // at_before and end are HistoricalData expressions that generate their own keywords
24700        if let Some(at_before) = &e.at_before {
24701            self.write(" ");
24702            self.generate_expression(at_before)?;
24703        }
24704        if let Some(end) = &e.end {
24705            self.write(" ");
24706            self.generate_expression(end)?;
24707        }
24708        Ok(())
24709    }
24710
24711    fn generate_character_set_column_constraint(
24712        &mut self,
24713        e: &CharacterSetColumnConstraint,
24714    ) -> Result<()> {
24715        // CHARACTER SET charset_name
24716        self.write_keyword("CHARACTER SET");
24717        self.write_space();
24718        self.generate_expression(&e.this)?;
24719        Ok(())
24720    }
24721
24722    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
24723        // [DEFAULT] CHARACTER SET=value
24724        if e.default.is_some() {
24725            self.write_keyword("DEFAULT");
24726            self.write_space();
24727        }
24728        self.write_keyword("CHARACTER SET");
24729        self.write("=");
24730        self.generate_expression(&e.this)?;
24731        Ok(())
24732    }
24733
24734    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
24735        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
24736        self.write_keyword("CHECK");
24737        self.write(" (");
24738        self.generate_expression(&e.this)?;
24739        self.write(")");
24740        if e.enforced.is_some() {
24741            self.write_space();
24742            self.write_keyword("ENFORCED");
24743        }
24744        Ok(())
24745    }
24746
24747    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
24748        // CHECK_JSON(this)
24749        self.write_keyword("CHECK_JSON");
24750        self.write("(");
24751        self.generate_expression(&e.this)?;
24752        self.write(")");
24753        Ok(())
24754    }
24755
24756    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
24757        // CHECK_XML(this)
24758        self.write_keyword("CHECK_XML");
24759        self.write("(");
24760        self.generate_expression(&e.this)?;
24761        self.write(")");
24762        Ok(())
24763    }
24764
24765    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
24766        // CHECKSUM=[ON|OFF|DEFAULT]
24767        self.write_keyword("CHECKSUM");
24768        self.write("=");
24769        if e.on.is_some() {
24770            self.write_keyword("ON");
24771        } else if e.default.is_some() {
24772            self.write_keyword("DEFAULT");
24773        } else {
24774            self.write_keyword("OFF");
24775        }
24776        Ok(())
24777    }
24778
24779    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
24780        // Python: return f"{shallow}{keyword} {this}"
24781        if e.shallow.is_some() {
24782            self.write_keyword("SHALLOW");
24783            self.write_space();
24784        }
24785        if e.copy.is_some() {
24786            self.write_keyword("COPY");
24787        } else {
24788            self.write_keyword("CLONE");
24789        }
24790        self.write_space();
24791        self.generate_expression(&e.this)?;
24792        Ok(())
24793    }
24794
24795    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
24796        // CLUSTER BY (expressions)
24797        self.write_keyword("CLUSTER BY");
24798        self.write(" (");
24799        for (i, ord) in e.expressions.iter().enumerate() {
24800            if i > 0 {
24801                self.write(", ");
24802            }
24803            self.generate_ordered(ord)?;
24804        }
24805        self.write(")");
24806        Ok(())
24807    }
24808
24809    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
24810        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
24811        self.write_keyword("CLUSTERED BY");
24812        self.write(" (");
24813        for (i, expr) in e.expressions.iter().enumerate() {
24814            if i > 0 {
24815                self.write(", ");
24816            }
24817            self.generate_expression(expr)?;
24818        }
24819        self.write(")");
24820        if let Some(sorted_by) = &e.sorted_by {
24821            self.write_space();
24822            self.write_keyword("SORTED BY");
24823            self.write(" (");
24824            // Unwrap Tuple to avoid double parentheses
24825            if let Expression::Tuple(t) = sorted_by.as_ref() {
24826                for (i, expr) in t.expressions.iter().enumerate() {
24827                    if i > 0 {
24828                        self.write(", ");
24829                    }
24830                    self.generate_expression(expr)?;
24831                }
24832            } else {
24833                self.generate_expression(sorted_by)?;
24834            }
24835            self.write(")");
24836        }
24837        if let Some(buckets) = &e.buckets {
24838            self.write_space();
24839            self.write_keyword("INTO");
24840            self.write_space();
24841            self.generate_expression(buckets)?;
24842            self.write_space();
24843            self.write_keyword("BUCKETS");
24844        }
24845        Ok(())
24846    }
24847
24848    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
24849        // [DEFAULT] COLLATE [=] value
24850        // BigQuery uses space: DEFAULT COLLATE 'en'
24851        // Others use equals: COLLATE='en'
24852        if e.default.is_some() {
24853            self.write_keyword("DEFAULT");
24854            self.write_space();
24855        }
24856        self.write_keyword("COLLATE");
24857        // BigQuery uses space between COLLATE and value
24858        match self.config.dialect {
24859            Some(DialectType::BigQuery) => self.write_space(),
24860            _ => self.write("="),
24861        }
24862        self.generate_expression(&e.this)?;
24863        Ok(())
24864    }
24865
24866    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
24867        // ColumnConstraint is an enum
24868        match e {
24869            ColumnConstraint::NotNull => {
24870                self.write_keyword("NOT NULL");
24871            }
24872            ColumnConstraint::Null => {
24873                self.write_keyword("NULL");
24874            }
24875            ColumnConstraint::Unique => {
24876                self.write_keyword("UNIQUE");
24877            }
24878            ColumnConstraint::PrimaryKey => {
24879                self.write_keyword("PRIMARY KEY");
24880            }
24881            ColumnConstraint::Default(expr) => {
24882                self.write_keyword("DEFAULT");
24883                self.write_space();
24884                self.generate_expression(expr)?;
24885            }
24886            ColumnConstraint::Check(expr) => {
24887                self.write_keyword("CHECK");
24888                self.write(" (");
24889                self.generate_expression(expr)?;
24890                self.write(")");
24891            }
24892            ColumnConstraint::References(fk_ref) => {
24893                if fk_ref.has_foreign_key_keywords {
24894                    self.write_keyword("FOREIGN KEY");
24895                    self.write_space();
24896                }
24897                self.write_keyword("REFERENCES");
24898                self.write_space();
24899                self.generate_table(&fk_ref.table)?;
24900                if !fk_ref.columns.is_empty() {
24901                    self.write(" (");
24902                    for (i, col) in fk_ref.columns.iter().enumerate() {
24903                        if i > 0 {
24904                            self.write(", ");
24905                        }
24906                        self.generate_identifier(col)?;
24907                    }
24908                    self.write(")");
24909                }
24910            }
24911            ColumnConstraint::GeneratedAsIdentity(gen) => {
24912                self.write_keyword("GENERATED");
24913                self.write_space();
24914                if gen.always {
24915                    self.write_keyword("ALWAYS");
24916                } else {
24917                    self.write_keyword("BY DEFAULT");
24918                    if gen.on_null {
24919                        self.write_space();
24920                        self.write_keyword("ON NULL");
24921                    }
24922                }
24923                self.write_space();
24924                self.write_keyword("AS IDENTITY");
24925            }
24926            ColumnConstraint::Collate(collation) => {
24927                self.write_keyword("COLLATE");
24928                self.write_space();
24929                self.generate_identifier(collation)?;
24930            }
24931            ColumnConstraint::Comment(comment) => {
24932                self.write_keyword("COMMENT");
24933                self.write(" '");
24934                self.write(comment);
24935                self.write("'");
24936            }
24937            ColumnConstraint::ComputedColumn(cc) => {
24938                self.generate_computed_column_inline(cc)?;
24939            }
24940            ColumnConstraint::GeneratedAsRow(gar) => {
24941                self.generate_generated_as_row_inline(gar)?;
24942            }
24943            ColumnConstraint::Tags(tags) => {
24944                self.write_keyword("TAG");
24945                self.write(" (");
24946                for (i, expr) in tags.expressions.iter().enumerate() {
24947                    if i > 0 {
24948                        self.write(", ");
24949                    }
24950                    self.generate_expression(expr)?;
24951                }
24952                self.write(")");
24953            }
24954            ColumnConstraint::Path(path_expr) => {
24955                self.write_keyword("PATH");
24956                self.write_space();
24957                self.generate_expression(path_expr)?;
24958            }
24959        }
24960        Ok(())
24961    }
24962
24963    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
24964        // ColumnPosition is an enum
24965        match e {
24966            ColumnPosition::First => {
24967                self.write_keyword("FIRST");
24968            }
24969            ColumnPosition::After(ident) => {
24970                self.write_keyword("AFTER");
24971                self.write_space();
24972                self.generate_identifier(ident)?;
24973            }
24974        }
24975        Ok(())
24976    }
24977
24978    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
24979        // column(prefix)
24980        self.generate_expression(&e.this)?;
24981        self.write("(");
24982        self.generate_expression(&e.expression)?;
24983        self.write(")");
24984        Ok(())
24985    }
24986
24987    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
24988        // If unpack is true, this came from * COLUMNS(pattern)
24989        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
24990        if let Some(ref unpack) = e.unpack {
24991            if let Expression::Boolean(b) = unpack.as_ref() {
24992                if b.value {
24993                    self.write("*");
24994                }
24995            }
24996        }
24997        self.write_keyword("COLUMNS");
24998        self.write("(");
24999        self.generate_expression(&e.this)?;
25000        self.write(")");
25001        Ok(())
25002    }
25003
25004    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
25005        // Combined aggregate: FUNC(args) combined
25006        self.generate_expression(&e.this)?;
25007        self.write("(");
25008        for (i, expr) in e.expressions.iter().enumerate() {
25009            if i > 0 {
25010                self.write(", ");
25011            }
25012            self.generate_expression(expr)?;
25013        }
25014        self.write(")");
25015        Ok(())
25016    }
25017
25018    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
25019        // Combined parameterized aggregate: FUNC(params)(expressions)
25020        self.generate_expression(&e.this)?;
25021        self.write("(");
25022        for (i, param) in e.params.iter().enumerate() {
25023            if i > 0 {
25024                self.write(", ");
25025            }
25026            self.generate_expression(param)?;
25027        }
25028        self.write(")(");
25029        for (i, expr) in e.expressions.iter().enumerate() {
25030            if i > 0 {
25031                self.write(", ");
25032            }
25033            self.generate_expression(expr)?;
25034        }
25035        self.write(")");
25036        Ok(())
25037    }
25038
25039    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
25040        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
25041        self.write_keyword("COMMIT");
25042
25043        // TSQL always uses COMMIT TRANSACTION
25044        if e.this.is_none()
25045            && matches!(
25046                self.config.dialect,
25047                Some(DialectType::TSQL) | Some(DialectType::Fabric)
25048            )
25049        {
25050            self.write_space();
25051            self.write_keyword("TRANSACTION");
25052        }
25053
25054        // Check if this has TRANSACTION keyword or transaction name
25055        if let Some(this) = &e.this {
25056            // Check if it's just the "TRANSACTION" marker or an actual transaction name
25057            let is_transaction_marker = matches!(
25058                this.as_ref(),
25059                Expression::Identifier(id) if id.name == "TRANSACTION"
25060            );
25061
25062            self.write_space();
25063            self.write_keyword("TRANSACTION");
25064
25065            // If it's a real transaction name, output it
25066            if !is_transaction_marker {
25067                self.write_space();
25068                self.generate_expression(this)?;
25069            }
25070        }
25071
25072        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
25073        if let Some(durability) = &e.durability {
25074            self.write_space();
25075            self.write_keyword("WITH");
25076            self.write(" (");
25077            self.write_keyword("DELAYED_DURABILITY");
25078            self.write(" = ");
25079            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
25080                self.write_keyword("ON");
25081            } else {
25082                self.write_keyword("OFF");
25083            }
25084            self.write(")");
25085        }
25086
25087        // Output AND [NO] CHAIN
25088        if let Some(chain) = &e.chain {
25089            self.write_space();
25090            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
25091                self.write_keyword("AND NO CHAIN");
25092            } else {
25093                self.write_keyword("AND CHAIN");
25094            }
25095        }
25096        Ok(())
25097    }
25098
25099    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
25100        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
25101        self.write("[");
25102        self.generate_expression(&e.this)?;
25103        self.write_space();
25104        self.write_keyword("FOR");
25105        self.write_space();
25106        self.generate_expression(&e.expression)?;
25107        // Handle optional position variable (for enumerate-like syntax)
25108        if let Some(pos) = &e.position {
25109            self.write(", ");
25110            self.generate_expression(pos)?;
25111        }
25112        if let Some(iterator) = &e.iterator {
25113            self.write_space();
25114            self.write_keyword("IN");
25115            self.write_space();
25116            self.generate_expression(iterator)?;
25117        }
25118        if let Some(condition) = &e.condition {
25119            self.write_space();
25120            self.write_keyword("IF");
25121            self.write_space();
25122            self.generate_expression(condition)?;
25123        }
25124        self.write("]");
25125        Ok(())
25126    }
25127
25128    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
25129        // COMPRESS(this[, method])
25130        self.write_keyword("COMPRESS");
25131        self.write("(");
25132        self.generate_expression(&e.this)?;
25133        if let Some(method) = &e.method {
25134            self.write(", '");
25135            self.write(method);
25136            self.write("'");
25137        }
25138        self.write(")");
25139        Ok(())
25140    }
25141
25142    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
25143        // Python: return f"COMPRESS {this}"
25144        self.write_keyword("COMPRESS");
25145        if let Some(this) = &e.this {
25146            self.write_space();
25147            self.generate_expression(this)?;
25148        }
25149        Ok(())
25150    }
25151
25152    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
25153        // Python: return f"AS {this}{persisted}"
25154        self.write_keyword("AS");
25155        self.write_space();
25156        self.generate_expression(&e.this)?;
25157        if e.not_null.is_some() {
25158            self.write_space();
25159            self.write_keyword("PERSISTED NOT NULL");
25160        } else if e.persisted.is_some() {
25161            self.write_space();
25162            self.write_keyword("PERSISTED");
25163        }
25164        Ok(())
25165    }
25166
25167    /// Generate a ComputedColumn constraint inline within a column definition.
25168    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
25169    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
25170    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
25171        let computed_expr = if matches!(
25172            self.config.dialect,
25173            Some(DialectType::TSQL) | Some(DialectType::Fabric)
25174        ) {
25175            match &*cc.expression {
25176                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
25177                {
25178                    let wrapped = Expression::Cast(Box::new(Cast {
25179                        this: y.this.clone(),
25180                        to: DataType::Date,
25181                        trailing_comments: Vec::new(),
25182                        double_colon_syntax: false,
25183                        format: None,
25184                        default: None,
25185                    }));
25186                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
25187                }
25188                Expression::Function(f)
25189                    if f.name.eq_ignore_ascii_case("YEAR")
25190                        && f.args.len() == 1
25191                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
25192                {
25193                    let wrapped = Expression::Cast(Box::new(Cast {
25194                        this: f.args[0].clone(),
25195                        to: DataType::Date,
25196                        trailing_comments: Vec::new(),
25197                        double_colon_syntax: false,
25198                        format: None,
25199                        default: None,
25200                    }));
25201                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
25202                }
25203                _ => *cc.expression.clone(),
25204            }
25205        } else {
25206            *cc.expression.clone()
25207        };
25208
25209        match cc.persistence_kind.as_deref() {
25210            Some("STORED") | Some("VIRTUAL") => {
25211                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
25212                self.write_keyword("GENERATED ALWAYS AS");
25213                self.write(" (");
25214                self.generate_expression(&computed_expr)?;
25215                self.write(")");
25216                self.write_space();
25217                if cc.persisted {
25218                    self.write_keyword("STORED");
25219                } else {
25220                    self.write_keyword("VIRTUAL");
25221                }
25222            }
25223            Some("PERSISTED") => {
25224                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
25225                self.write_keyword("AS");
25226                self.write(" (");
25227                self.generate_expression(&computed_expr)?;
25228                self.write(")");
25229                self.write_space();
25230                self.write_keyword("PERSISTED");
25231                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
25232                if let Some(ref dt) = cc.data_type {
25233                    self.write_space();
25234                    self.generate_data_type(dt)?;
25235                }
25236                if cc.not_null {
25237                    self.write_space();
25238                    self.write_keyword("NOT NULL");
25239                }
25240            }
25241            _ => {
25242                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
25243                // TSQL computed column without PERSISTED: AS (expr)
25244                if matches!(
25245                    self.config.dialect,
25246                    Some(DialectType::Spark)
25247                        | Some(DialectType::Databricks)
25248                        | Some(DialectType::Hive)
25249                ) {
25250                    self.write_keyword("GENERATED ALWAYS AS");
25251                    self.write(" (");
25252                    self.generate_expression(&computed_expr)?;
25253                    self.write(")");
25254                } else if matches!(
25255                    self.config.dialect,
25256                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25257                ) {
25258                    self.write_keyword("AS");
25259                    let omit_parens = matches!(computed_expr, Expression::Year(_))
25260                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
25261                    if omit_parens {
25262                        self.write_space();
25263                        self.generate_expression(&computed_expr)?;
25264                    } else {
25265                        self.write(" (");
25266                        self.generate_expression(&computed_expr)?;
25267                        self.write(")");
25268                    }
25269                } else {
25270                    self.write_keyword("AS");
25271                    self.write(" (");
25272                    self.generate_expression(&computed_expr)?;
25273                    self.write(")");
25274                }
25275            }
25276        }
25277        Ok(())
25278    }
25279
25280    /// Generate a GeneratedAsRow constraint inline within a column definition.
25281    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
25282    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
25283        self.write_keyword("GENERATED ALWAYS AS ROW ");
25284        if gar.start {
25285            self.write_keyword("START");
25286        } else {
25287            self.write_keyword("END");
25288        }
25289        if gar.hidden {
25290            self.write_space();
25291            self.write_keyword("HIDDEN");
25292        }
25293        Ok(())
25294    }
25295
25296    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
25297    fn generate_system_versioning_content(
25298        &mut self,
25299        e: &WithSystemVersioningProperty,
25300    ) -> Result<()> {
25301        let mut parts = Vec::new();
25302
25303        if let Some(this) = &e.this {
25304            let mut s = String::from("HISTORY_TABLE=");
25305            let mut gen = Generator::new();
25306            gen.config = self.config.clone();
25307            gen.generate_expression(this)?;
25308            s.push_str(&gen.output);
25309            parts.push(s);
25310        }
25311
25312        if let Some(data_consistency) = &e.data_consistency {
25313            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
25314            let mut gen = Generator::new();
25315            gen.config = self.config.clone();
25316            gen.generate_expression(data_consistency)?;
25317            s.push_str(&gen.output);
25318            parts.push(s);
25319        }
25320
25321        if let Some(retention_period) = &e.retention_period {
25322            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
25323            let mut gen = Generator::new();
25324            gen.config = self.config.clone();
25325            gen.generate_expression(retention_period)?;
25326            s.push_str(&gen.output);
25327            parts.push(s);
25328        }
25329
25330        self.write_keyword("SYSTEM_VERSIONING");
25331        self.write("=");
25332
25333        if !parts.is_empty() {
25334            self.write_keyword("ON");
25335            self.write("(");
25336            self.write(&parts.join(", "));
25337            self.write(")");
25338        } else if e.on.is_some() {
25339            self.write_keyword("ON");
25340        } else {
25341            self.write_keyword("OFF");
25342        }
25343
25344        Ok(())
25345    }
25346
25347    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
25348        // Conditional INSERT for multi-table inserts
25349        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
25350        if e.else_.is_some() {
25351            self.write_keyword("ELSE");
25352            self.write_space();
25353        } else if let Some(expression) = &e.expression {
25354            self.write_keyword("WHEN");
25355            self.write_space();
25356            self.generate_expression(expression)?;
25357            self.write_space();
25358            self.write_keyword("THEN");
25359            self.write_space();
25360        }
25361
25362        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
25363        // without the "INSERT " prefix
25364        if let Expression::Insert(insert) = e.this.as_ref() {
25365            self.write_keyword("INTO");
25366            self.write_space();
25367            self.generate_table(&insert.table)?;
25368
25369            // Optional column list
25370            if !insert.columns.is_empty() {
25371                self.write(" (");
25372                for (i, col) in insert.columns.iter().enumerate() {
25373                    if i > 0 {
25374                        self.write(", ");
25375                    }
25376                    self.generate_identifier(col)?;
25377                }
25378                self.write(")");
25379            }
25380
25381            // Optional VALUES clause
25382            if !insert.values.is_empty() {
25383                self.write_space();
25384                self.write_keyword("VALUES");
25385                for (row_idx, row) in insert.values.iter().enumerate() {
25386                    if row_idx > 0 {
25387                        self.write(", ");
25388                    }
25389                    self.write(" (");
25390                    for (i, val) in row.iter().enumerate() {
25391                        if i > 0 {
25392                            self.write(", ");
25393                        }
25394                        self.generate_expression(val)?;
25395                    }
25396                    self.write(")");
25397                }
25398            }
25399        } else {
25400            // Fallback for non-Insert expressions
25401            self.generate_expression(&e.this)?;
25402        }
25403        Ok(())
25404    }
25405
25406    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
25407        // Python: return f"CONSTRAINT {this} {expressions}"
25408        self.write_keyword("CONSTRAINT");
25409        self.write_space();
25410        self.generate_expression(&e.this)?;
25411        if !e.expressions.is_empty() {
25412            self.write_space();
25413            for (i, expr) in e.expressions.iter().enumerate() {
25414                if i > 0 {
25415                    self.write_space();
25416                }
25417                self.generate_expression(expr)?;
25418            }
25419        }
25420        Ok(())
25421    }
25422
25423    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
25424        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
25425        self.write_keyword("CONVERT_TIMEZONE");
25426        self.write("(");
25427        let mut first = true;
25428        if let Some(source_tz) = &e.source_tz {
25429            self.generate_expression(source_tz)?;
25430            first = false;
25431        }
25432        if let Some(target_tz) = &e.target_tz {
25433            if !first {
25434                self.write(", ");
25435            }
25436            self.generate_expression(target_tz)?;
25437            first = false;
25438        }
25439        if let Some(timestamp) = &e.timestamp {
25440            if !first {
25441                self.write(", ");
25442            }
25443            self.generate_expression(timestamp)?;
25444        }
25445        self.write(")");
25446        Ok(())
25447    }
25448
25449    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
25450        // CONVERT(this USING dest)
25451        self.write_keyword("CONVERT");
25452        self.write("(");
25453        self.generate_expression(&e.this)?;
25454        if let Some(dest) = &e.dest {
25455            self.write_space();
25456            self.write_keyword("USING");
25457            self.write_space();
25458            self.generate_expression(dest)?;
25459        }
25460        self.write(")");
25461        Ok(())
25462    }
25463
25464    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
25465        self.write_keyword("COPY");
25466        if e.is_into {
25467            self.write_space();
25468            self.write_keyword("INTO");
25469        }
25470        self.write_space();
25471
25472        // Generate target table or query (or stage for COPY INTO @stage)
25473        if let Expression::Literal(Literal::String(s)) = &e.this {
25474            if s.starts_with('@') {
25475                self.write(s);
25476            } else {
25477                self.generate_expression(&e.this)?;
25478            }
25479        } else {
25480            self.generate_expression(&e.this)?;
25481        }
25482
25483        // FROM or TO based on kind
25484        if e.kind {
25485            // kind=true means FROM (loading into table)
25486            if self.config.pretty {
25487                self.write_newline();
25488            } else {
25489                self.write_space();
25490            }
25491            self.write_keyword("FROM");
25492            self.write_space();
25493        } else if !e.files.is_empty() {
25494            // kind=false means TO (exporting)
25495            if self.config.pretty {
25496                self.write_newline();
25497            } else {
25498                self.write_space();
25499            }
25500            self.write_keyword("TO");
25501            self.write_space();
25502        }
25503
25504        // Generate source/destination files
25505        for (i, file) in e.files.iter().enumerate() {
25506            if i > 0 {
25507                self.write_space();
25508            }
25509            // For stage references (strings starting with @), output without quotes
25510            if let Expression::Literal(Literal::String(s)) = file {
25511                if s.starts_with('@') {
25512                    self.write(s);
25513                } else {
25514                    self.generate_expression(file)?;
25515                }
25516            } else if let Expression::Identifier(id) = file {
25517                // Backtick-quoted file path (Databricks style: `s3://link`)
25518                if id.quoted {
25519                    self.write("`");
25520                    self.write(&id.name);
25521                    self.write("`");
25522                } else {
25523                    self.generate_expression(file)?;
25524                }
25525            } else {
25526                self.generate_expression(file)?;
25527            }
25528        }
25529
25530        // Generate credentials if present (Snowflake style - not wrapped in WITH)
25531        if !e.with_wrapped {
25532            if let Some(ref creds) = e.credentials {
25533                if let Some(ref storage) = creds.storage {
25534                    if self.config.pretty {
25535                        self.write_newline();
25536                    } else {
25537                        self.write_space();
25538                    }
25539                    self.write_keyword("STORAGE_INTEGRATION");
25540                    self.write(" = ");
25541                    self.write(storage);
25542                }
25543                if creds.credentials.is_empty() {
25544                    // Empty credentials: CREDENTIALS = ()
25545                    if self.config.pretty {
25546                        self.write_newline();
25547                    } else {
25548                        self.write_space();
25549                    }
25550                    self.write_keyword("CREDENTIALS");
25551                    self.write(" = ()");
25552                } else {
25553                    if self.config.pretty {
25554                        self.write_newline();
25555                    } else {
25556                        self.write_space();
25557                    }
25558                    self.write_keyword("CREDENTIALS");
25559                    // Check if this is Redshift-style (single value with empty key)
25560                    // vs Snowflake-style (multiple key=value pairs)
25561                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
25562                        // Redshift style: CREDENTIALS 'value'
25563                        self.write(" '");
25564                        self.write(&creds.credentials[0].1);
25565                        self.write("'");
25566                    } else {
25567                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
25568                        self.write(" = (");
25569                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
25570                            if i > 0 {
25571                                self.write_space();
25572                            }
25573                            self.write(k);
25574                            self.write("='");
25575                            self.write(v);
25576                            self.write("'");
25577                        }
25578                        self.write(")");
25579                    }
25580                }
25581                if let Some(ref encryption) = creds.encryption {
25582                    self.write_space();
25583                    self.write_keyword("ENCRYPTION");
25584                    self.write(" = ");
25585                    self.write(encryption);
25586                }
25587            }
25588        }
25589
25590        // Generate parameters
25591        if !e.params.is_empty() {
25592            if e.with_wrapped {
25593                // DuckDB/PostgreSQL/TSQL WITH (...) format
25594                self.write_space();
25595                self.write_keyword("WITH");
25596                self.write(" (");
25597                for (i, param) in e.params.iter().enumerate() {
25598                    if i > 0 {
25599                        self.write(", ");
25600                    }
25601                    self.generate_copy_param_with_format(param)?;
25602                }
25603                self.write(")");
25604            } else {
25605                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
25606                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
25607                // For Snowflake: KEY = VALUE
25608                for param in &e.params {
25609                    if self.config.pretty {
25610                        self.write_newline();
25611                    } else {
25612                        self.write_space();
25613                    }
25614                    // Preserve original case of parameter name (important for Redshift COPY options)
25615                    self.write(&param.name);
25616                    if let Some(ref value) = param.value {
25617                        // Use = only if it was present in the original (param.eq)
25618                        if param.eq {
25619                            self.write(" = ");
25620                        } else {
25621                            self.write(" ");
25622                        }
25623                        if !param.values.is_empty() {
25624                            self.write("(");
25625                            for (i, v) in param.values.iter().enumerate() {
25626                                if i > 0 {
25627                                    self.write_space();
25628                                }
25629                                self.generate_copy_nested_param(v)?;
25630                            }
25631                            self.write(")");
25632                        } else {
25633                            // For COPY parameter values, output identifiers without quoting
25634                            self.generate_copy_param_value(value)?;
25635                        }
25636                    } else if !param.values.is_empty() {
25637                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
25638                        if param.eq {
25639                            self.write(" = (");
25640                        } else {
25641                            self.write(" (");
25642                        }
25643                        // Determine separator for values inside parentheses:
25644                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
25645                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
25646                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
25647                        let is_key_value_pairs = param
25648                            .values
25649                            .first()
25650                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
25651                        let sep = if is_key_value_pairs && param.eq {
25652                            " "
25653                        } else {
25654                            ", "
25655                        };
25656                        for (i, v) in param.values.iter().enumerate() {
25657                            if i > 0 {
25658                                self.write(sep);
25659                            }
25660                            self.generate_copy_nested_param(v)?;
25661                        }
25662                        self.write(")");
25663                    }
25664                }
25665            }
25666        }
25667
25668        Ok(())
25669    }
25670
25671    /// Generate a COPY parameter in WITH (...) format
25672    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
25673    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
25674        self.write_keyword(&param.name);
25675        if !param.values.is_empty() {
25676            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
25677            self.write(" = (");
25678            for (i, v) in param.values.iter().enumerate() {
25679                if i > 0 {
25680                    self.write(", ");
25681                }
25682                self.generate_copy_nested_param(v)?;
25683            }
25684            self.write(")");
25685        } else if let Some(ref value) = param.value {
25686            if param.eq {
25687                self.write(" = ");
25688            } else {
25689                self.write(" ");
25690            }
25691            self.generate_expression(value)?;
25692        }
25693        Ok(())
25694    }
25695
25696    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
25697    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
25698        match expr {
25699            Expression::Eq(eq) => {
25700                // Generate key
25701                match &eq.left {
25702                    Expression::Column(c) => self.write(&c.name.name),
25703                    _ => self.generate_expression(&eq.left)?,
25704                }
25705                self.write("=");
25706                // Generate value
25707                match &eq.right {
25708                    Expression::Literal(Literal::String(s)) => {
25709                        self.write("'");
25710                        self.write(s);
25711                        self.write("'");
25712                    }
25713                    Expression::Tuple(t) => {
25714                        // For lists like NULL_IF=('', 'str1')
25715                        self.write("(");
25716                        if self.config.pretty {
25717                            self.write_newline();
25718                            self.indent_level += 1;
25719                            for (i, item) in t.expressions.iter().enumerate() {
25720                                if i > 0 {
25721                                    self.write(", ");
25722                                }
25723                                self.write_indent();
25724                                self.generate_expression(item)?;
25725                            }
25726                            self.write_newline();
25727                            self.indent_level -= 1;
25728                        } else {
25729                            for (i, item) in t.expressions.iter().enumerate() {
25730                                if i > 0 {
25731                                    self.write(", ");
25732                                }
25733                                self.generate_expression(item)?;
25734                            }
25735                        }
25736                        self.write(")");
25737                    }
25738                    _ => self.generate_expression(&eq.right)?,
25739                }
25740                Ok(())
25741            }
25742            Expression::Column(c) => {
25743                // Standalone keyword like COMPRESSION
25744                self.write(&c.name.name);
25745                Ok(())
25746            }
25747            _ => self.generate_expression(expr),
25748        }
25749    }
25750
25751    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
25752    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
25753    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
25754        match expr {
25755            Expression::Column(c) => {
25756                // Output identifier, preserving quotes if originally quoted
25757                if c.name.quoted {
25758                    self.write("\"");
25759                    self.write(&c.name.name);
25760                    self.write("\"");
25761                } else {
25762                    self.write(&c.name.name);
25763                }
25764                Ok(())
25765            }
25766            Expression::Identifier(id) => {
25767                // Output identifier, preserving quotes if originally quoted
25768                if id.quoted {
25769                    self.write("\"");
25770                    self.write(&id.name);
25771                    self.write("\"");
25772                } else {
25773                    self.write(&id.name);
25774                }
25775                Ok(())
25776            }
25777            Expression::Literal(Literal::String(s)) => {
25778                // Output string with quotes
25779                self.write("'");
25780                self.write(s);
25781                self.write("'");
25782                Ok(())
25783            }
25784            _ => self.generate_expression(expr),
25785        }
25786    }
25787
25788    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
25789        self.write_keyword(&e.name);
25790        if let Some(ref value) = e.value {
25791            if e.eq {
25792                self.write(" = ");
25793            } else {
25794                self.write(" ");
25795            }
25796            self.generate_expression(value)?;
25797        }
25798        if !e.values.is_empty() {
25799            if e.eq {
25800                self.write(" = ");
25801            } else {
25802                self.write(" ");
25803            }
25804            self.write("(");
25805            for (i, v) in e.values.iter().enumerate() {
25806                if i > 0 {
25807                    self.write(", ");
25808                }
25809                self.generate_expression(v)?;
25810            }
25811            self.write(")");
25812        }
25813        Ok(())
25814    }
25815
25816    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
25817        // CORR(this, expression)
25818        self.write_keyword("CORR");
25819        self.write("(");
25820        self.generate_expression(&e.this)?;
25821        self.write(", ");
25822        self.generate_expression(&e.expression)?;
25823        self.write(")");
25824        Ok(())
25825    }
25826
25827    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
25828        // COSINE_DISTANCE(this, expression)
25829        self.write_keyword("COSINE_DISTANCE");
25830        self.write("(");
25831        self.generate_expression(&e.this)?;
25832        self.write(", ");
25833        self.generate_expression(&e.expression)?;
25834        self.write(")");
25835        Ok(())
25836    }
25837
25838    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
25839        // COVAR_POP(this, expression)
25840        self.write_keyword("COVAR_POP");
25841        self.write("(");
25842        self.generate_expression(&e.this)?;
25843        self.write(", ");
25844        self.generate_expression(&e.expression)?;
25845        self.write(")");
25846        Ok(())
25847    }
25848
25849    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
25850        // COVAR_SAMP(this, expression)
25851        self.write_keyword("COVAR_SAMP");
25852        self.write("(");
25853        self.generate_expression(&e.this)?;
25854        self.write(", ");
25855        self.generate_expression(&e.expression)?;
25856        self.write(")");
25857        Ok(())
25858    }
25859
25860    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
25861        // CREDENTIALS (key1='value1', key2='value2')
25862        self.write_keyword("CREDENTIALS");
25863        self.write(" (");
25864        for (i, (key, value)) in e.credentials.iter().enumerate() {
25865            if i > 0 {
25866                self.write(", ");
25867            }
25868            self.write(key);
25869            self.write("='");
25870            self.write(value);
25871            self.write("'");
25872        }
25873        self.write(")");
25874        Ok(())
25875    }
25876
25877    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
25878        // CREDENTIALS=(expressions)
25879        self.write_keyword("CREDENTIALS");
25880        self.write("=(");
25881        for (i, expr) in e.expressions.iter().enumerate() {
25882            if i > 0 {
25883                self.write(", ");
25884            }
25885            self.generate_expression(expr)?;
25886        }
25887        self.write(")");
25888        Ok(())
25889    }
25890
25891    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
25892        use crate::dialects::DialectType;
25893
25894        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
25895        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
25896        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
25897            self.generate_expression(&e.this)?;
25898            self.write_space();
25899            self.write_keyword("AS");
25900            self.write_space();
25901            self.generate_identifier(&e.alias)?;
25902            return Ok(());
25903        }
25904        self.write(&e.alias.name);
25905
25906        // BigQuery doesn't support column aliases in CTE definitions
25907        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
25908
25909        if !e.columns.is_empty() && !skip_cte_columns {
25910            self.write("(");
25911            for (i, col) in e.columns.iter().enumerate() {
25912                if i > 0 {
25913                    self.write(", ");
25914                }
25915                self.write(&col.name);
25916            }
25917            self.write(")");
25918        }
25919        // USING KEY (columns) for DuckDB recursive CTEs
25920        if !e.key_expressions.is_empty() {
25921            self.write_space();
25922            self.write_keyword("USING KEY");
25923            self.write(" (");
25924            for (i, key) in e.key_expressions.iter().enumerate() {
25925                if i > 0 {
25926                    self.write(", ");
25927                }
25928                self.write(&key.name);
25929            }
25930            self.write(")");
25931        }
25932        self.write_space();
25933        self.write_keyword("AS");
25934        self.write_space();
25935        if let Some(materialized) = e.materialized {
25936            if materialized {
25937                self.write_keyword("MATERIALIZED");
25938            } else {
25939                self.write_keyword("NOT MATERIALIZED");
25940            }
25941            self.write_space();
25942        }
25943        self.write("(");
25944        self.generate_expression(&e.this)?;
25945        self.write(")");
25946        Ok(())
25947    }
25948
25949    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
25950        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
25951        if e.expressions.is_empty() {
25952            self.write_keyword("WITH CUBE");
25953        } else {
25954            self.write_keyword("CUBE");
25955            self.write("(");
25956            for (i, expr) in e.expressions.iter().enumerate() {
25957                if i > 0 {
25958                    self.write(", ");
25959                }
25960                self.generate_expression(expr)?;
25961            }
25962            self.write(")");
25963        }
25964        Ok(())
25965    }
25966
25967    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
25968        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
25969        self.write_keyword("CURRENT_DATETIME");
25970        if let Some(this) = &e.this {
25971            self.write("(");
25972            self.generate_expression(this)?;
25973            self.write(")");
25974        }
25975        Ok(())
25976    }
25977
25978    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
25979        // CURRENT_SCHEMA - no arguments
25980        self.write_keyword("CURRENT_SCHEMA");
25981        Ok(())
25982    }
25983
25984    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
25985        // CURRENT_SCHEMAS(include_implicit)
25986        self.write_keyword("CURRENT_SCHEMAS");
25987        self.write("(");
25988        if let Some(this) = &e.this {
25989            self.generate_expression(this)?;
25990        }
25991        self.write(")");
25992        Ok(())
25993    }
25994
25995    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
25996        // CURRENT_USER or CURRENT_USER()
25997        self.write_keyword("CURRENT_USER");
25998        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
25999        let needs_parens = e.this.is_some()
26000            || matches!(
26001                self.config.dialect,
26002                Some(DialectType::Snowflake)
26003                    | Some(DialectType::Spark)
26004                    | Some(DialectType::Hive)
26005                    | Some(DialectType::DuckDB)
26006                    | Some(DialectType::BigQuery)
26007                    | Some(DialectType::MySQL)
26008                    | Some(DialectType::Databricks)
26009            );
26010        if needs_parens {
26011            self.write("()");
26012        }
26013        Ok(())
26014    }
26015
26016    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
26017        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
26018        if self.config.dialect == Some(DialectType::Solr) {
26019            self.generate_expression(&e.this)?;
26020            self.write(" ");
26021            self.write_keyword("OR");
26022            self.write(" ");
26023            self.generate_expression(&e.expression)?;
26024        } else {
26025            // String concatenation: this || expression
26026            self.generate_expression(&e.this)?;
26027            self.write(" || ");
26028            self.generate_expression(&e.expression)?;
26029        }
26030        Ok(())
26031    }
26032
26033    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
26034        // DATABLOCKSIZE=... (Teradata)
26035        self.write_keyword("DATABLOCKSIZE");
26036        self.write("=");
26037        if let Some(size) = e.size {
26038            self.write(&size.to_string());
26039            if let Some(units) = &e.units {
26040                self.write_space();
26041                self.generate_expression(units)?;
26042            }
26043        } else if e.minimum.is_some() {
26044            self.write_keyword("MINIMUM");
26045        } else if e.maximum.is_some() {
26046            self.write_keyword("MAXIMUM");
26047        } else if e.default.is_some() {
26048            self.write_keyword("DEFAULT");
26049        }
26050        Ok(())
26051    }
26052
26053    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
26054        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
26055        self.write_keyword("DATA_DELETION");
26056        self.write("=");
26057
26058        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
26059        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
26060
26061        if is_on {
26062            self.write_keyword("ON");
26063            if has_options {
26064                self.write("(");
26065                let mut first = true;
26066                if let Some(filter_column) = &e.filter_column {
26067                    self.write_keyword("FILTER_COLUMN");
26068                    self.write("=");
26069                    self.generate_expression(filter_column)?;
26070                    first = false;
26071                }
26072                if let Some(retention_period) = &e.retention_period {
26073                    if !first {
26074                        self.write(", ");
26075                    }
26076                    self.write_keyword("RETENTION_PERIOD");
26077                    self.write("=");
26078                    self.generate_expression(retention_period)?;
26079                }
26080                self.write(")");
26081            }
26082        } else {
26083            self.write_keyword("OFF");
26084        }
26085        Ok(())
26086    }
26087
26088    /// Generate a Date function expression
26089    /// For Exasol: {d'value'} -> TO_DATE('value')
26090    /// For other dialects: DATE('value')
26091    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
26092        use crate::dialects::DialectType;
26093        use crate::expressions::Literal;
26094
26095        match self.config.dialect {
26096            // Exasol uses TO_DATE for Date expressions
26097            Some(DialectType::Exasol) => {
26098                self.write_keyword("TO_DATE");
26099                self.write("(");
26100                // Extract the string value from the expression if it's a string literal
26101                match &e.this {
26102                    Expression::Literal(Literal::String(s)) => {
26103                        self.write("'");
26104                        self.write(s);
26105                        self.write("'");
26106                    }
26107                    _ => {
26108                        self.generate_expression(&e.this)?;
26109                    }
26110                }
26111                self.write(")");
26112            }
26113            // Standard: DATE(value)
26114            _ => {
26115                self.write_keyword("DATE");
26116                self.write("(");
26117                self.generate_expression(&e.this)?;
26118                self.write(")");
26119            }
26120        }
26121        Ok(())
26122    }
26123
26124    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
26125        // DATE_BIN(interval, timestamp[, origin])
26126        self.write_keyword("DATE_BIN");
26127        self.write("(");
26128        self.generate_expression(&e.this)?;
26129        self.write(", ");
26130        self.generate_expression(&e.expression)?;
26131        if let Some(origin) = &e.origin {
26132            self.write(", ");
26133            self.generate_expression(origin)?;
26134        }
26135        self.write(")");
26136        Ok(())
26137    }
26138
26139    fn generate_date_format_column_constraint(
26140        &mut self,
26141        e: &DateFormatColumnConstraint,
26142    ) -> Result<()> {
26143        // FORMAT 'format_string' (Teradata)
26144        self.write_keyword("FORMAT");
26145        self.write_space();
26146        self.generate_expression(&e.this)?;
26147        Ok(())
26148    }
26149
26150    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
26151        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
26152        self.write_keyword("DATE_FROM_PARTS");
26153        self.write("(");
26154        let mut first = true;
26155        if let Some(year) = &e.year {
26156            self.generate_expression(year)?;
26157            first = false;
26158        }
26159        if let Some(month) = &e.month {
26160            if !first {
26161                self.write(", ");
26162            }
26163            self.generate_expression(month)?;
26164            first = false;
26165        }
26166        if let Some(day) = &e.day {
26167            if !first {
26168                self.write(", ");
26169            }
26170            self.generate_expression(day)?;
26171        }
26172        self.write(")");
26173        Ok(())
26174    }
26175
26176    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
26177        // DATETIME(this) or DATETIME(this, expression)
26178        self.write_keyword("DATETIME");
26179        self.write("(");
26180        self.generate_expression(&e.this)?;
26181        if let Some(expr) = &e.expression {
26182            self.write(", ");
26183            self.generate_expression(expr)?;
26184        }
26185        self.write(")");
26186        Ok(())
26187    }
26188
26189    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
26190        // DATETIME_ADD(this, expression, unit)
26191        self.write_keyword("DATETIME_ADD");
26192        self.write("(");
26193        self.generate_expression(&e.this)?;
26194        self.write(", ");
26195        self.generate_expression(&e.expression)?;
26196        if let Some(unit) = &e.unit {
26197            self.write(", ");
26198            self.write_keyword(unit);
26199        }
26200        self.write(")");
26201        Ok(())
26202    }
26203
26204    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
26205        // DATETIME_DIFF(this, expression, unit)
26206        self.write_keyword("DATETIME_DIFF");
26207        self.write("(");
26208        self.generate_expression(&e.this)?;
26209        self.write(", ");
26210        self.generate_expression(&e.expression)?;
26211        if let Some(unit) = &e.unit {
26212            self.write(", ");
26213            self.write_keyword(unit);
26214        }
26215        self.write(")");
26216        Ok(())
26217    }
26218
26219    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
26220        // DATETIME_SUB(this, expression, unit)
26221        self.write_keyword("DATETIME_SUB");
26222        self.write("(");
26223        self.generate_expression(&e.this)?;
26224        self.write(", ");
26225        self.generate_expression(&e.expression)?;
26226        if let Some(unit) = &e.unit {
26227            self.write(", ");
26228            self.write_keyword(unit);
26229        }
26230        self.write(")");
26231        Ok(())
26232    }
26233
26234    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
26235        // DATETIME_TRUNC(this, unit, zone)
26236        self.write_keyword("DATETIME_TRUNC");
26237        self.write("(");
26238        self.generate_expression(&e.this)?;
26239        self.write(", ");
26240        self.write_keyword(&e.unit);
26241        if let Some(zone) = &e.zone {
26242            self.write(", ");
26243            self.generate_expression(zone)?;
26244        }
26245        self.write(")");
26246        Ok(())
26247    }
26248
26249    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
26250        // DAYNAME(this)
26251        self.write_keyword("DAYNAME");
26252        self.write("(");
26253        self.generate_expression(&e.this)?;
26254        self.write(")");
26255        Ok(())
26256    }
26257
26258    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
26259        // DECLARE var1 AS type1, var2 AS type2, ...
26260        self.write_keyword("DECLARE");
26261        self.write_space();
26262        for (i, expr) in e.expressions.iter().enumerate() {
26263            if i > 0 {
26264                self.write(", ");
26265            }
26266            self.generate_expression(expr)?;
26267        }
26268        Ok(())
26269    }
26270
26271    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
26272        use crate::dialects::DialectType;
26273
26274        // variable TYPE [DEFAULT default]
26275        self.generate_expression(&e.this)?;
26276        // BigQuery multi-variable: DECLARE X, Y, Z INT64
26277        for name in &e.additional_names {
26278            self.write(", ");
26279            self.generate_expression(name)?;
26280        }
26281        if let Some(kind) = &e.kind {
26282            self.write_space();
26283            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
26284            // TSQL: Always includes AS (normalization)
26285            // Others: Include AS if present in original
26286            match self.config.dialect {
26287                Some(DialectType::BigQuery) => {
26288                    self.write(kind);
26289                }
26290                Some(DialectType::TSQL) => {
26291                    // TSQL: Check for complex TABLE constraints that should be passed through unchanged
26292                    // Python sqlglot falls back to Command for TABLE declarations with CLUSTERED,
26293                    // NONCLUSTERED, or INDEX constraints
26294                    let is_complex_table = kind.starts_with("TABLE")
26295                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
26296
26297                    if is_complex_table {
26298                        // Complex TABLE declarations: preserve as-is (no AS, no INT normalization)
26299                        self.write(kind);
26300                    } else {
26301                        // Simple declarations: add AS (except for CURSOR) and normalize INT
26302                        if !kind.starts_with("CURSOR") {
26303                            self.write_keyword("AS");
26304                            self.write_space();
26305                        }
26306                        // Normalize INT to INTEGER for TSQL DECLARE statements
26307                        if kind == "INT" {
26308                            self.write("INTEGER");
26309                        } else if kind.starts_with("TABLE") {
26310                            // Normalize INT to INTEGER inside TABLE column definitions
26311                            let normalized = kind
26312                                .replace(" INT ", " INTEGER ")
26313                                .replace(" INT,", " INTEGER,")
26314                                .replace(" INT)", " INTEGER)")
26315                                .replace("(INT ", "(INTEGER ");
26316                            self.write(&normalized);
26317                        } else {
26318                            self.write(kind);
26319                        }
26320                    }
26321                }
26322                _ => {
26323                    if e.has_as {
26324                        self.write_keyword("AS");
26325                        self.write_space();
26326                    }
26327                    self.write(kind);
26328                }
26329            }
26330        }
26331        if let Some(default) = &e.default {
26332            // BigQuery uses DEFAULT, others use =
26333            match self.config.dialect {
26334                Some(DialectType::BigQuery) => {
26335                    self.write_space();
26336                    self.write_keyword("DEFAULT");
26337                    self.write_space();
26338                }
26339                _ => {
26340                    self.write(" = ");
26341                }
26342            }
26343            self.generate_expression(default)?;
26344        }
26345        Ok(())
26346    }
26347
26348    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
26349        // DECODE(expr, search1, result1, search2, result2, ..., default)
26350        self.write_keyword("DECODE");
26351        self.write("(");
26352        for (i, expr) in e.expressions.iter().enumerate() {
26353            if i > 0 {
26354                self.write(", ");
26355            }
26356            self.generate_expression(expr)?;
26357        }
26358        self.write(")");
26359        Ok(())
26360    }
26361
26362    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
26363        // DECOMPRESS(expr, 'method')
26364        self.write_keyword("DECOMPRESS");
26365        self.write("(");
26366        self.generate_expression(&e.this)?;
26367        self.write(", '");
26368        self.write(&e.method);
26369        self.write("')");
26370        Ok(())
26371    }
26372
26373    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
26374        // DECOMPRESS(expr, 'method')
26375        self.write_keyword("DECOMPRESS");
26376        self.write("(");
26377        self.generate_expression(&e.this)?;
26378        self.write(", '");
26379        self.write(&e.method);
26380        self.write("')");
26381        Ok(())
26382    }
26383
26384    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
26385        // DECRYPT(value, passphrase [, aad [, algorithm]])
26386        self.write_keyword("DECRYPT");
26387        self.write("(");
26388        self.generate_expression(&e.this)?;
26389        if let Some(passphrase) = &e.passphrase {
26390            self.write(", ");
26391            self.generate_expression(passphrase)?;
26392        }
26393        if let Some(aad) = &e.aad {
26394            self.write(", ");
26395            self.generate_expression(aad)?;
26396        }
26397        if let Some(method) = &e.encryption_method {
26398            self.write(", ");
26399            self.generate_expression(method)?;
26400        }
26401        self.write(")");
26402        Ok(())
26403    }
26404
26405    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
26406        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
26407        self.write_keyword("DECRYPT_RAW");
26408        self.write("(");
26409        self.generate_expression(&e.this)?;
26410        if let Some(key) = &e.key {
26411            self.write(", ");
26412            self.generate_expression(key)?;
26413        }
26414        if let Some(iv) = &e.iv {
26415            self.write(", ");
26416            self.generate_expression(iv)?;
26417        }
26418        if let Some(aad) = &e.aad {
26419            self.write(", ");
26420            self.generate_expression(aad)?;
26421        }
26422        if let Some(method) = &e.encryption_method {
26423            self.write(", ");
26424            self.generate_expression(method)?;
26425        }
26426        self.write(")");
26427        Ok(())
26428    }
26429
26430    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
26431        // DEFINER = user
26432        self.write_keyword("DEFINER");
26433        self.write(" = ");
26434        self.generate_expression(&e.this)?;
26435        Ok(())
26436    }
26437
26438    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
26439        // Python: DETACH[DATABASE IF EXISTS] this
26440        self.write_keyword("DETACH");
26441        if e.exists {
26442            self.write_keyword(" DATABASE IF EXISTS");
26443        }
26444        self.write_space();
26445        self.generate_expression(&e.this)?;
26446        Ok(())
26447    }
26448
26449    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
26450        let property_name = match e.this.as_ref() {
26451            Expression::Identifier(id) => id.name.as_str(),
26452            Expression::Var(v) => v.this.as_str(),
26453            _ => "DICTIONARY",
26454        };
26455        self.write_keyword(property_name);
26456        self.write("(");
26457        self.write(&e.kind);
26458        if let Some(settings) = &e.settings {
26459            self.write("(");
26460            if let Expression::Tuple(t) = settings.as_ref() {
26461                if self.config.pretty && !t.expressions.is_empty() {
26462                    self.write_newline();
26463                    self.indent_level += 1;
26464                    for (i, pair) in t.expressions.iter().enumerate() {
26465                        if i > 0 {
26466                            self.write(",");
26467                            self.write_newline();
26468                        }
26469                        self.write_indent();
26470                        if let Expression::Tuple(pair_tuple) = pair {
26471                            if let Some(k) = pair_tuple.expressions.first() {
26472                                self.generate_expression(k)?;
26473                            }
26474                            if let Some(v) = pair_tuple.expressions.get(1) {
26475                                self.write(" ");
26476                                self.generate_expression(v)?;
26477                            }
26478                        } else {
26479                            self.generate_expression(pair)?;
26480                        }
26481                    }
26482                    self.indent_level -= 1;
26483                    self.write_newline();
26484                    self.write_indent();
26485                } else {
26486                    for (i, pair) in t.expressions.iter().enumerate() {
26487                        if i > 0 {
26488                            self.write(", ");
26489                        }
26490                        if let Expression::Tuple(pair_tuple) = pair {
26491                            if let Some(k) = pair_tuple.expressions.first() {
26492                                self.generate_expression(k)?;
26493                            }
26494                            if let Some(v) = pair_tuple.expressions.get(1) {
26495                                self.write(" ");
26496                                self.generate_expression(v)?;
26497                            }
26498                        } else {
26499                            self.generate_expression(pair)?;
26500                        }
26501                    }
26502                }
26503            } else {
26504                self.generate_expression(settings)?;
26505            }
26506            self.write(")");
26507        } else if property_name.eq_ignore_ascii_case("LAYOUT") {
26508            self.write("()");
26509        }
26510        self.write(")");
26511        Ok(())
26512    }
26513
26514    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
26515        let property_name = match e.this.as_ref() {
26516            Expression::Identifier(id) => id.name.as_str(),
26517            Expression::Var(v) => v.this.as_str(),
26518            _ => "RANGE",
26519        };
26520        self.write_keyword(property_name);
26521        self.write("(");
26522        if let Some(min) = &e.min {
26523            self.write_keyword("MIN");
26524            self.write_space();
26525            self.generate_expression(min)?;
26526        }
26527        if let Some(max) = &e.max {
26528            self.write_space();
26529            self.write_keyword("MAX");
26530            self.write_space();
26531            self.generate_expression(max)?;
26532        }
26533        self.write(")");
26534        Ok(())
26535    }
26536
26537    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
26538        // Python: {local}DIRECTORY {this}{row_format}
26539        if e.local.is_some() {
26540            self.write_keyword("LOCAL ");
26541        }
26542        self.write_keyword("DIRECTORY");
26543        self.write_space();
26544        self.generate_expression(&e.this)?;
26545        if let Some(row_format) = &e.row_format {
26546            self.write_space();
26547            self.generate_expression(row_format)?;
26548        }
26549        Ok(())
26550    }
26551
26552    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
26553        // Redshift: DISTKEY(column)
26554        self.write_keyword("DISTKEY");
26555        self.write("(");
26556        self.generate_expression(&e.this)?;
26557        self.write(")");
26558        Ok(())
26559    }
26560
26561    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
26562        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
26563        self.write_keyword("DISTSTYLE");
26564        self.write_space();
26565        self.generate_expression(&e.this)?;
26566        Ok(())
26567    }
26568
26569    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
26570        // Python: "DISTRIBUTE BY" expressions
26571        self.write_keyword("DISTRIBUTE BY");
26572        self.write_space();
26573        for (i, expr) in e.expressions.iter().enumerate() {
26574            if i > 0 {
26575                self.write(", ");
26576            }
26577            self.generate_expression(expr)?;
26578        }
26579        Ok(())
26580    }
26581
26582    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
26583        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
26584        self.write_keyword("DISTRIBUTED BY");
26585        self.write_space();
26586        self.write(&e.kind);
26587        if !e.expressions.is_empty() {
26588            self.write(" (");
26589            for (i, expr) in e.expressions.iter().enumerate() {
26590                if i > 0 {
26591                    self.write(", ");
26592                }
26593                self.generate_expression(expr)?;
26594            }
26595            self.write(")");
26596        }
26597        if let Some(buckets) = &e.buckets {
26598            self.write_space();
26599            self.write_keyword("BUCKETS");
26600            self.write_space();
26601            self.generate_expression(buckets)?;
26602        }
26603        if let Some(order) = &e.order {
26604            self.write_space();
26605            self.generate_expression(order)?;
26606        }
26607        Ok(())
26608    }
26609
26610    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
26611        // DOT_PRODUCT(vector1, vector2)
26612        self.write_keyword("DOT_PRODUCT");
26613        self.write("(");
26614        self.generate_expression(&e.this)?;
26615        self.write(", ");
26616        self.generate_expression(&e.expression)?;
26617        self.write(")");
26618        Ok(())
26619    }
26620
26621    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
26622        // Python: DROP{IF EXISTS }expressions
26623        self.write_keyword("DROP");
26624        if e.exists {
26625            self.write_keyword(" IF EXISTS ");
26626        } else {
26627            self.write_space();
26628        }
26629        for (i, expr) in e.expressions.iter().enumerate() {
26630            if i > 0 {
26631                self.write(", ");
26632            }
26633            self.generate_expression(expr)?;
26634        }
26635        Ok(())
26636    }
26637
26638    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
26639        // Python: DUPLICATE KEY (expressions)
26640        self.write_keyword("DUPLICATE KEY");
26641        self.write(" (");
26642        for (i, expr) in e.expressions.iter().enumerate() {
26643            if i > 0 {
26644                self.write(", ");
26645            }
26646            self.generate_expression(expr)?;
26647        }
26648        self.write(")");
26649        Ok(())
26650    }
26651
26652    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
26653        // ELT(index, str1, str2, ...)
26654        self.write_keyword("ELT");
26655        self.write("(");
26656        self.generate_expression(&e.this)?;
26657        for expr in &e.expressions {
26658            self.write(", ");
26659            self.generate_expression(expr)?;
26660        }
26661        self.write(")");
26662        Ok(())
26663    }
26664
26665    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
26666        // ENCODE(string, charset)
26667        self.write_keyword("ENCODE");
26668        self.write("(");
26669        self.generate_expression(&e.this)?;
26670        if let Some(charset) = &e.charset {
26671            self.write(", ");
26672            self.generate_expression(charset)?;
26673        }
26674        self.write(")");
26675        Ok(())
26676    }
26677
26678    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
26679        // Python: [KEY ]ENCODE this [properties]
26680        if e.key.is_some() {
26681            self.write_keyword("KEY ");
26682        }
26683        self.write_keyword("ENCODE");
26684        self.write_space();
26685        self.generate_expression(&e.this)?;
26686        if !e.properties.is_empty() {
26687            self.write(" (");
26688            for (i, prop) in e.properties.iter().enumerate() {
26689                if i > 0 {
26690                    self.write(", ");
26691                }
26692                self.generate_expression(prop)?;
26693            }
26694            self.write(")");
26695        }
26696        Ok(())
26697    }
26698
26699    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
26700        // ENCRYPT(value, passphrase [, aad [, algorithm]])
26701        self.write_keyword("ENCRYPT");
26702        self.write("(");
26703        self.generate_expression(&e.this)?;
26704        if let Some(passphrase) = &e.passphrase {
26705            self.write(", ");
26706            self.generate_expression(passphrase)?;
26707        }
26708        if let Some(aad) = &e.aad {
26709            self.write(", ");
26710            self.generate_expression(aad)?;
26711        }
26712        if let Some(method) = &e.encryption_method {
26713            self.write(", ");
26714            self.generate_expression(method)?;
26715        }
26716        self.write(")");
26717        Ok(())
26718    }
26719
26720    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
26721        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
26722        self.write_keyword("ENCRYPT_RAW");
26723        self.write("(");
26724        self.generate_expression(&e.this)?;
26725        if let Some(key) = &e.key {
26726            self.write(", ");
26727            self.generate_expression(key)?;
26728        }
26729        if let Some(iv) = &e.iv {
26730            self.write(", ");
26731            self.generate_expression(iv)?;
26732        }
26733        if let Some(aad) = &e.aad {
26734            self.write(", ");
26735            self.generate_expression(aad)?;
26736        }
26737        if let Some(method) = &e.encryption_method {
26738            self.write(", ");
26739            self.generate_expression(method)?;
26740        }
26741        self.write(")");
26742        Ok(())
26743    }
26744
26745    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
26746        // MySQL: ENGINE = InnoDB
26747        self.write_keyword("ENGINE");
26748        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
26749            self.write("=");
26750        } else {
26751            self.write(" = ");
26752        }
26753        self.generate_expression(&e.this)?;
26754        Ok(())
26755    }
26756
26757    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
26758        // ENVIRONMENT (expressions)
26759        self.write_keyword("ENVIRONMENT");
26760        self.write(" (");
26761        for (i, expr) in e.expressions.iter().enumerate() {
26762            if i > 0 {
26763                self.write(", ");
26764            }
26765            self.generate_expression(expr)?;
26766        }
26767        self.write(")");
26768        Ok(())
26769    }
26770
26771    fn generate_ephemeral_column_constraint(
26772        &mut self,
26773        e: &EphemeralColumnConstraint,
26774    ) -> Result<()> {
26775        // MySQL: EPHEMERAL [expr]
26776        self.write_keyword("EPHEMERAL");
26777        if let Some(this) = &e.this {
26778            self.write_space();
26779            self.generate_expression(this)?;
26780        }
26781        Ok(())
26782    }
26783
26784    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
26785        // Snowflake: EQUAL_NULL(a, b)
26786        self.write_keyword("EQUAL_NULL");
26787        self.write("(");
26788        self.generate_expression(&e.this)?;
26789        self.write(", ");
26790        self.generate_expression(&e.expression)?;
26791        self.write(")");
26792        Ok(())
26793    }
26794
26795    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
26796        use crate::dialects::DialectType;
26797
26798        // PostgreSQL uses <-> operator syntax
26799        match self.config.dialect {
26800            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
26801                self.generate_expression(&e.this)?;
26802                self.write(" <-> ");
26803                self.generate_expression(&e.expression)?;
26804            }
26805            _ => {
26806                // Other dialects use EUCLIDEAN_DISTANCE function
26807                self.write_keyword("EUCLIDEAN_DISTANCE");
26808                self.write("(");
26809                self.generate_expression(&e.this)?;
26810                self.write(", ");
26811                self.generate_expression(&e.expression)?;
26812                self.write(")");
26813            }
26814        }
26815        Ok(())
26816    }
26817
26818    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
26819        // EXECUTE AS CALLER|OWNER|user
26820        self.write_keyword("EXECUTE AS");
26821        self.write_space();
26822        self.generate_expression(&e.this)?;
26823        Ok(())
26824    }
26825
26826    fn generate_export(&mut self, e: &Export) -> Result<()> {
26827        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
26828        self.write_keyword("EXPORT DATA");
26829        if let Some(connection) = &e.connection {
26830            self.write_space();
26831            self.write_keyword("WITH CONNECTION");
26832            self.write_space();
26833            self.generate_expression(connection)?;
26834        }
26835        if !e.options.is_empty() {
26836            self.write_space();
26837            self.generate_options_clause(&e.options)?;
26838        }
26839        self.write_space();
26840        self.write_keyword("AS");
26841        self.write_space();
26842        self.generate_expression(&e.this)?;
26843        Ok(())
26844    }
26845
26846    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
26847        // EXTERNAL [this]
26848        self.write_keyword("EXTERNAL");
26849        if let Some(this) = &e.this {
26850            self.write_space();
26851            self.generate_expression(this)?;
26852        }
26853        Ok(())
26854    }
26855
26856    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
26857        // Python: {no}FALLBACK{protection}
26858        if e.no.is_some() {
26859            self.write_keyword("NO ");
26860        }
26861        self.write_keyword("FALLBACK");
26862        if e.protection.is_some() {
26863            self.write_keyword(" PROTECTION");
26864        }
26865        Ok(())
26866    }
26867
26868    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
26869        // BigQuery: FARM_FINGERPRINT(value)
26870        self.write_keyword("FARM_FINGERPRINT");
26871        self.write("(");
26872        for (i, expr) in e.expressions.iter().enumerate() {
26873            if i > 0 {
26874                self.write(", ");
26875            }
26876            self.generate_expression(expr)?;
26877        }
26878        self.write(")");
26879        Ok(())
26880    }
26881
26882    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
26883        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
26884        self.write_keyword("FEATURES_AT_TIME");
26885        self.write("(");
26886        self.generate_expression(&e.this)?;
26887        if let Some(time) = &e.time {
26888            self.write(", ");
26889            self.generate_expression(time)?;
26890        }
26891        if let Some(num_rows) = &e.num_rows {
26892            self.write(", ");
26893            self.generate_expression(num_rows)?;
26894        }
26895        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
26896            self.write(", ");
26897            self.generate_expression(ignore_nulls)?;
26898        }
26899        self.write(")");
26900        Ok(())
26901    }
26902
26903    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
26904        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
26905        let use_limit = !e.percent
26906            && !e.with_ties
26907            && e.count.is_some()
26908            && matches!(
26909                self.config.dialect,
26910                Some(DialectType::Spark)
26911                    | Some(DialectType::Hive)
26912                    | Some(DialectType::DuckDB)
26913                    | Some(DialectType::SQLite)
26914                    | Some(DialectType::MySQL)
26915                    | Some(DialectType::BigQuery)
26916                    | Some(DialectType::Databricks)
26917                    | Some(DialectType::StarRocks)
26918                    | Some(DialectType::Doris)
26919                    | Some(DialectType::Athena)
26920                    | Some(DialectType::ClickHouse)
26921            );
26922
26923        if use_limit {
26924            self.write_keyword("LIMIT");
26925            self.write_space();
26926            self.generate_expression(e.count.as_ref().unwrap())?;
26927            return Ok(());
26928        }
26929
26930        // Python: FETCH direction count limit_options
26931        self.write_keyword("FETCH");
26932        if !e.direction.is_empty() {
26933            self.write_space();
26934            self.write_keyword(&e.direction);
26935        }
26936        if let Some(count) = &e.count {
26937            self.write_space();
26938            self.generate_expression(count)?;
26939        }
26940        // Generate PERCENT, ROWS, WITH TIES/ONLY
26941        if e.percent {
26942            self.write_keyword(" PERCENT");
26943        }
26944        if e.rows {
26945            self.write_keyword(" ROWS");
26946        }
26947        if e.with_ties {
26948            self.write_keyword(" WITH TIES");
26949        } else if e.rows {
26950            self.write_keyword(" ONLY");
26951        } else {
26952            self.write_keyword(" ROWS ONLY");
26953        }
26954        Ok(())
26955    }
26956
26957    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
26958        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
26959        // For Spark/Databricks without hive_format: USING this
26960        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
26961        if e.hive_format.is_some() {
26962            // Hive format: STORED AS ...
26963            self.write_keyword("STORED AS");
26964            self.write_space();
26965            if let Some(this) = &e.this {
26966                // Uppercase the format name (e.g., parquet -> PARQUET)
26967                if let Expression::Identifier(id) = this.as_ref() {
26968                    self.write_keyword(&id.name.to_uppercase());
26969                } else {
26970                    self.generate_expression(this)?;
26971                }
26972            }
26973        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
26974            // Hive: STORED AS format
26975            self.write_keyword("STORED AS");
26976            self.write_space();
26977            if let Some(this) = &e.this {
26978                if let Expression::Identifier(id) = this.as_ref() {
26979                    self.write_keyword(&id.name.to_uppercase());
26980                } else {
26981                    self.generate_expression(this)?;
26982                }
26983            }
26984        } else if matches!(
26985            self.config.dialect,
26986            Some(DialectType::Spark) | Some(DialectType::Databricks)
26987        ) {
26988            // Spark/Databricks: USING format (e.g., USING DELTA)
26989            self.write_keyword("USING");
26990            self.write_space();
26991            if let Some(this) = &e.this {
26992                self.generate_expression(this)?;
26993            }
26994        } else {
26995            // Snowflake/standard format
26996            self.write_keyword("FILE_FORMAT");
26997            self.write(" = ");
26998            if let Some(this) = &e.this {
26999                self.generate_expression(this)?;
27000            } else if !e.expressions.is_empty() {
27001                self.write("(");
27002                for (i, expr) in e.expressions.iter().enumerate() {
27003                    if i > 0 {
27004                        self.write(", ");
27005                    }
27006                    self.generate_expression(expr)?;
27007                }
27008                self.write(")");
27009            }
27010        }
27011        Ok(())
27012    }
27013
27014    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
27015        // agg_func FILTER(WHERE condition)
27016        self.generate_expression(&e.this)?;
27017        self.write_space();
27018        self.write_keyword("FILTER");
27019        self.write("(");
27020        self.write_keyword("WHERE");
27021        self.write_space();
27022        self.generate_expression(&e.expression)?;
27023        self.write(")");
27024        Ok(())
27025    }
27026
27027    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
27028        // FLOAT64(this) or FLOAT64(this, expression)
27029        self.write_keyword("FLOAT64");
27030        self.write("(");
27031        self.generate_expression(&e.this)?;
27032        if let Some(expr) = &e.expression {
27033            self.write(", ");
27034            self.generate_expression(expr)?;
27035        }
27036        self.write(")");
27037        Ok(())
27038    }
27039
27040    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
27041        // FOR this DO expression
27042        self.write_keyword("FOR");
27043        self.write_space();
27044        self.generate_expression(&e.this)?;
27045        self.write_space();
27046        self.write_keyword("DO");
27047        self.write_space();
27048        self.generate_expression(&e.expression)?;
27049        Ok(())
27050    }
27051
27052    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
27053        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
27054        self.write_keyword("FOREIGN KEY");
27055        if !e.expressions.is_empty() {
27056            self.write(" (");
27057            for (i, expr) in e.expressions.iter().enumerate() {
27058                if i > 0 {
27059                    self.write(", ");
27060                }
27061                self.generate_expression(expr)?;
27062            }
27063            self.write(")");
27064        }
27065        if let Some(reference) = &e.reference {
27066            self.write_space();
27067            self.generate_expression(reference)?;
27068        }
27069        if let Some(delete) = &e.delete {
27070            self.write_space();
27071            self.write_keyword("ON DELETE");
27072            self.write_space();
27073            self.generate_expression(delete)?;
27074        }
27075        if let Some(update) = &e.update {
27076            self.write_space();
27077            self.write_keyword("ON UPDATE");
27078            self.write_space();
27079            self.generate_expression(update)?;
27080        }
27081        if !e.options.is_empty() {
27082            self.write_space();
27083            for (i, opt) in e.options.iter().enumerate() {
27084                if i > 0 {
27085                    self.write_space();
27086                }
27087                self.generate_expression(opt)?;
27088            }
27089        }
27090        Ok(())
27091    }
27092
27093    fn generate_format(&mut self, e: &Format) -> Result<()> {
27094        // FORMAT(this, expressions...)
27095        self.write_keyword("FORMAT");
27096        self.write("(");
27097        self.generate_expression(&e.this)?;
27098        for expr in &e.expressions {
27099            self.write(", ");
27100            self.generate_expression(expr)?;
27101        }
27102        self.write(")");
27103        Ok(())
27104    }
27105
27106    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
27107        // Teradata: column (FORMAT 'format_string')
27108        self.generate_expression(&e.this)?;
27109        self.write(" (");
27110        self.write_keyword("FORMAT");
27111        self.write(" '");
27112        self.write(&e.format);
27113        self.write("')");
27114        Ok(())
27115    }
27116
27117    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
27118        // Python: FREESPACE=this[PERCENT]
27119        self.write_keyword("FREESPACE");
27120        self.write("=");
27121        self.generate_expression(&e.this)?;
27122        if e.percent.is_some() {
27123            self.write_keyword(" PERCENT");
27124        }
27125        Ok(())
27126    }
27127
27128    fn generate_from(&mut self, e: &From) -> Result<()> {
27129        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
27130        self.write_keyword("FROM");
27131        self.write_space();
27132
27133        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
27134        // But keep commas when TABLESAMPLE is present
27135        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
27136        use crate::dialects::DialectType;
27137        let has_tablesample = e
27138            .expressions
27139            .iter()
27140            .any(|expr| matches!(expr, Expression::TableSample(_)));
27141        let is_cross_join_dialect = matches!(
27142            self.config.dialect,
27143            Some(DialectType::BigQuery)
27144                | Some(DialectType::Hive)
27145                | Some(DialectType::Spark)
27146                | Some(DialectType::Databricks)
27147                | Some(DialectType::SQLite)
27148                | Some(DialectType::ClickHouse)
27149        );
27150        let source_is_same_as_target2 = self.config.source_dialect.is_some()
27151            && self.config.source_dialect == self.config.dialect;
27152        let source_is_cross_join_dialect2 = matches!(
27153            self.config.source_dialect,
27154            Some(DialectType::BigQuery)
27155                | Some(DialectType::Hive)
27156                | Some(DialectType::Spark)
27157                | Some(DialectType::Databricks)
27158                | Some(DialectType::SQLite)
27159                | Some(DialectType::ClickHouse)
27160        );
27161        let use_cross_join = !has_tablesample
27162            && is_cross_join_dialect
27163            && (source_is_same_as_target2
27164                || source_is_cross_join_dialect2
27165                || self.config.source_dialect.is_none());
27166
27167        // Snowflake wraps standalone VALUES in FROM clause with parentheses
27168        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
27169
27170        for (i, expr) in e.expressions.iter().enumerate() {
27171            if i > 0 {
27172                if use_cross_join {
27173                    self.write(" CROSS JOIN ");
27174                } else {
27175                    self.write(", ");
27176                }
27177            }
27178            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
27179                self.write("(");
27180                self.generate_expression(expr)?;
27181                self.write(")");
27182            } else {
27183                self.generate_expression(expr)?;
27184            }
27185        }
27186        Ok(())
27187    }
27188
27189    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
27190        // FROM_BASE(this, expression) - convert from base N
27191        self.write_keyword("FROM_BASE");
27192        self.write("(");
27193        self.generate_expression(&e.this)?;
27194        self.write(", ");
27195        self.generate_expression(&e.expression)?;
27196        self.write(")");
27197        Ok(())
27198    }
27199
27200    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
27201        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
27202        self.generate_expression(&e.this)?;
27203        if let Some(zone) = &e.zone {
27204            self.write_space();
27205            self.write_keyword("AT TIME ZONE");
27206            self.write_space();
27207            self.generate_expression(zone)?;
27208            self.write_space();
27209            self.write_keyword("AT TIME ZONE");
27210            self.write(" 'UTC'");
27211        }
27212        Ok(())
27213    }
27214
27215    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
27216        // GAP_FILL(this, ts_column, bucket_width, ...)
27217        self.write_keyword("GAP_FILL");
27218        self.write("(");
27219        self.generate_expression(&e.this)?;
27220        if let Some(ts_column) = &e.ts_column {
27221            self.write(", ");
27222            self.generate_expression(ts_column)?;
27223        }
27224        if let Some(bucket_width) = &e.bucket_width {
27225            self.write(", ");
27226            self.generate_expression(bucket_width)?;
27227        }
27228        if let Some(partitioning_columns) = &e.partitioning_columns {
27229            self.write(", ");
27230            self.generate_expression(partitioning_columns)?;
27231        }
27232        if let Some(value_columns) = &e.value_columns {
27233            self.write(", ");
27234            self.generate_expression(value_columns)?;
27235        }
27236        self.write(")");
27237        Ok(())
27238    }
27239
27240    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
27241        // GENERATE_DATE_ARRAY(start, end, step)
27242        self.write_keyword("GENERATE_DATE_ARRAY");
27243        self.write("(");
27244        let mut first = true;
27245        if let Some(start) = &e.start {
27246            self.generate_expression(start)?;
27247            first = false;
27248        }
27249        if let Some(end) = &e.end {
27250            if !first {
27251                self.write(", ");
27252            }
27253            self.generate_expression(end)?;
27254            first = false;
27255        }
27256        if let Some(step) = &e.step {
27257            if !first {
27258                self.write(", ");
27259            }
27260            self.generate_expression(step)?;
27261        }
27262        self.write(")");
27263        Ok(())
27264    }
27265
27266    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
27267        // ML.GENERATE_EMBEDDING(model, content, params)
27268        self.write_keyword("ML.GENERATE_EMBEDDING");
27269        self.write("(");
27270        self.generate_expression(&e.this)?;
27271        self.write(", ");
27272        self.generate_expression(&e.expression)?;
27273        if let Some(params) = &e.params_struct {
27274            self.write(", ");
27275            self.generate_expression(params)?;
27276        }
27277        self.write(")");
27278        Ok(())
27279    }
27280
27281    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
27282        // Dialect-specific function name
27283        let fn_name = match self.config.dialect {
27284            Some(DialectType::Presto)
27285            | Some(DialectType::Trino)
27286            | Some(DialectType::Athena)
27287            | Some(DialectType::Spark)
27288            | Some(DialectType::Databricks)
27289            | Some(DialectType::Hive) => "SEQUENCE",
27290            _ => "GENERATE_SERIES",
27291        };
27292        self.write_keyword(fn_name);
27293        self.write("(");
27294        let mut first = true;
27295        if let Some(start) = &e.start {
27296            self.generate_expression(start)?;
27297            first = false;
27298        }
27299        if let Some(end) = &e.end {
27300            if !first {
27301                self.write(", ");
27302            }
27303            self.generate_expression(end)?;
27304            first = false;
27305        }
27306        if let Some(step) = &e.step {
27307            if !first {
27308                self.write(", ");
27309            }
27310            // For Presto/Trino: convert WEEK intervals to DAY multiples
27311            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
27312            if matches!(
27313                self.config.dialect,
27314                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
27315            ) {
27316                if let Some(converted) = self.convert_week_interval_to_day(step) {
27317                    self.generate_expression(&converted)?;
27318                } else {
27319                    self.generate_expression(step)?;
27320                }
27321            } else {
27322                self.generate_expression(step)?;
27323            }
27324        }
27325        self.write(")");
27326        Ok(())
27327    }
27328
27329    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
27330    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
27331    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
27332        use crate::expressions::*;
27333        if let Expression::Interval(ref iv) = expr {
27334            // Check for structured WEEK unit
27335            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
27336                unit: IntervalUnit::Week,
27337                ..
27338            }) = &iv.unit
27339            {
27340                // Value is in iv.this
27341                let count = match &iv.this {
27342                    Some(Expression::Literal(Literal::String(s))) => s.clone(),
27343                    Some(Expression::Literal(Literal::Number(s))) => s.clone(),
27344                    _ => return None,
27345                };
27346                (true, count)
27347            } else if iv.unit.is_none() {
27348                // Check for string-encoded interval like "1 WEEK"
27349                if let Some(Expression::Literal(Literal::String(s))) = &iv.this {
27350                    let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
27351                    if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
27352                        (true, parts[0].to_string())
27353                    } else {
27354                        (false, String::new())
27355                    }
27356                } else {
27357                    (false, String::new())
27358                }
27359            } else {
27360                (false, String::new())
27361            };
27362
27363            if is_week {
27364                // Build: (N * INTERVAL '7' DAY)
27365                let count_expr = Expression::Literal(Literal::Number(count_str));
27366                let day_interval = Expression::Interval(Box::new(Interval {
27367                    this: Some(Expression::Literal(Literal::String("7".to_string()))),
27368                    unit: Some(IntervalUnitSpec::Simple {
27369                        unit: IntervalUnit::Day,
27370                        use_plural: false,
27371                    }),
27372                }));
27373                let mul = Expression::Mul(Box::new(BinaryOp {
27374                    left: count_expr,
27375                    right: day_interval,
27376                    left_comments: vec![],
27377                    operator_comments: vec![],
27378                    trailing_comments: vec![],
27379                }));
27380                return Some(Expression::Paren(Box::new(Paren {
27381                    this: mul,
27382                    trailing_comments: vec![],
27383                })));
27384            }
27385        }
27386        None
27387    }
27388
27389    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
27390        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
27391        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
27392        self.write("(");
27393        let mut first = true;
27394        if let Some(start) = &e.start {
27395            self.generate_expression(start)?;
27396            first = false;
27397        }
27398        if let Some(end) = &e.end {
27399            if !first {
27400                self.write(", ");
27401            }
27402            self.generate_expression(end)?;
27403            first = false;
27404        }
27405        if let Some(step) = &e.step {
27406            if !first {
27407                self.write(", ");
27408            }
27409            self.generate_expression(step)?;
27410        }
27411        self.write(")");
27412        Ok(())
27413    }
27414
27415    fn generate_generated_as_identity_column_constraint(
27416        &mut self,
27417        e: &GeneratedAsIdentityColumnConstraint,
27418    ) -> Result<()> {
27419        use crate::dialects::DialectType;
27420
27421        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
27422        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
27423            self.write_keyword("AUTOINCREMENT");
27424            if let Some(start) = &e.start {
27425                self.write_keyword(" START ");
27426                self.generate_expression(start)?;
27427            }
27428            if let Some(increment) = &e.increment {
27429                self.write_keyword(" INCREMENT ");
27430                self.generate_expression(increment)?;
27431            }
27432            return Ok(());
27433        }
27434
27435        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
27436        self.write_keyword("GENERATED");
27437        if let Some(this) = &e.this {
27438            // Check if it's a truthy boolean expression
27439            if let Expression::Boolean(b) = this.as_ref() {
27440                if b.value {
27441                    self.write_keyword(" ALWAYS");
27442                } else {
27443                    self.write_keyword(" BY DEFAULT");
27444                    if e.on_null.is_some() {
27445                        self.write_keyword(" ON NULL");
27446                    }
27447                }
27448            } else {
27449                self.write_keyword(" ALWAYS");
27450            }
27451        }
27452        self.write_keyword(" AS IDENTITY");
27453        // Add sequence options if any
27454        let has_options = e.start.is_some()
27455            || e.increment.is_some()
27456            || e.minvalue.is_some()
27457            || e.maxvalue.is_some();
27458        if has_options {
27459            self.write(" (");
27460            let mut first = true;
27461            if let Some(start) = &e.start {
27462                self.write_keyword("START WITH ");
27463                self.generate_expression(start)?;
27464                first = false;
27465            }
27466            if let Some(increment) = &e.increment {
27467                if !first {
27468                    self.write(" ");
27469                }
27470                self.write_keyword("INCREMENT BY ");
27471                self.generate_expression(increment)?;
27472                first = false;
27473            }
27474            if let Some(minvalue) = &e.minvalue {
27475                if !first {
27476                    self.write(" ");
27477                }
27478                self.write_keyword("MINVALUE ");
27479                self.generate_expression(minvalue)?;
27480                first = false;
27481            }
27482            if let Some(maxvalue) = &e.maxvalue {
27483                if !first {
27484                    self.write(" ");
27485                }
27486                self.write_keyword("MAXVALUE ");
27487                self.generate_expression(maxvalue)?;
27488            }
27489            self.write(")");
27490        }
27491        Ok(())
27492    }
27493
27494    fn generate_generated_as_row_column_constraint(
27495        &mut self,
27496        e: &GeneratedAsRowColumnConstraint,
27497    ) -> Result<()> {
27498        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
27499        self.write_keyword("GENERATED ALWAYS AS ROW ");
27500        if e.start.is_some() {
27501            self.write_keyword("START");
27502        } else {
27503            self.write_keyword("END");
27504        }
27505        if e.hidden.is_some() {
27506            self.write_keyword(" HIDDEN");
27507        }
27508        Ok(())
27509    }
27510
27511    fn generate_get(&mut self, e: &Get) -> Result<()> {
27512        // GET this target properties
27513        self.write_keyword("GET");
27514        self.write_space();
27515        self.generate_expression(&e.this)?;
27516        if let Some(target) = &e.target {
27517            self.write_space();
27518            self.generate_expression(target)?;
27519        }
27520        for prop in &e.properties {
27521            self.write_space();
27522            self.generate_expression(prop)?;
27523        }
27524        Ok(())
27525    }
27526
27527    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
27528        // GetExtract generates bracket access: this[expression]
27529        self.generate_expression(&e.this)?;
27530        self.write("[");
27531        self.generate_expression(&e.expression)?;
27532        self.write("]");
27533        Ok(())
27534    }
27535
27536    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
27537        // GETBIT(this, expression) or GET_BIT(this, expression)
27538        self.write_keyword("GETBIT");
27539        self.write("(");
27540        self.generate_expression(&e.this)?;
27541        self.write(", ");
27542        self.generate_expression(&e.expression)?;
27543        self.write(")");
27544        Ok(())
27545    }
27546
27547    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
27548        // [ROLE|GROUP] name (e.g., "ROLE admin", "GROUP qa_users", or just "user1")
27549        if e.is_role {
27550            self.write_keyword("ROLE");
27551            self.write_space();
27552        } else if e.is_group {
27553            self.write_keyword("GROUP");
27554            self.write_space();
27555        }
27556        self.write(&e.name.name);
27557        Ok(())
27558    }
27559
27560    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
27561        // privilege(columns) or just privilege
27562        self.generate_expression(&e.this)?;
27563        if !e.expressions.is_empty() {
27564            self.write("(");
27565            for (i, expr) in e.expressions.iter().enumerate() {
27566                if i > 0 {
27567                    self.write(", ");
27568                }
27569                self.generate_expression(expr)?;
27570            }
27571            self.write(")");
27572        }
27573        Ok(())
27574    }
27575
27576    fn generate_group(&mut self, e: &Group) -> Result<()> {
27577        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
27578        self.write_keyword("GROUP BY");
27579        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
27580        match e.all {
27581            Some(true) => {
27582                self.write_space();
27583                self.write_keyword("ALL");
27584            }
27585            Some(false) => {
27586                self.write_space();
27587                self.write_keyword("DISTINCT");
27588            }
27589            None => {}
27590        }
27591        if !e.expressions.is_empty() {
27592            self.write_space();
27593            for (i, expr) in e.expressions.iter().enumerate() {
27594                if i > 0 {
27595                    self.write(", ");
27596                }
27597                self.generate_expression(expr)?;
27598            }
27599        }
27600        // Handle CUBE, ROLLUP, GROUPING SETS
27601        if let Some(cube) = &e.cube {
27602            if !e.expressions.is_empty() {
27603                self.write(", ");
27604            } else {
27605                self.write_space();
27606            }
27607            self.generate_expression(cube)?;
27608        }
27609        if let Some(rollup) = &e.rollup {
27610            if !e.expressions.is_empty() || e.cube.is_some() {
27611                self.write(", ");
27612            } else {
27613                self.write_space();
27614            }
27615            self.generate_expression(rollup)?;
27616        }
27617        if let Some(grouping_sets) = &e.grouping_sets {
27618            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
27619                self.write(", ");
27620            } else {
27621                self.write_space();
27622            }
27623            self.generate_expression(grouping_sets)?;
27624        }
27625        if let Some(totals) = &e.totals {
27626            self.write_space();
27627            self.write_keyword("WITH TOTALS");
27628            self.generate_expression(totals)?;
27629        }
27630        Ok(())
27631    }
27632
27633    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
27634        // GROUP BY expressions
27635        self.write_keyword("GROUP BY");
27636        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
27637        match e.all {
27638            Some(true) => {
27639                self.write_space();
27640                self.write_keyword("ALL");
27641            }
27642            Some(false) => {
27643                self.write_space();
27644                self.write_keyword("DISTINCT");
27645            }
27646            None => {}
27647        }
27648
27649        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
27650        // These are represented as Cube/Rollup expressions with empty expressions at the end
27651        let mut trailing_cube = false;
27652        let mut trailing_rollup = false;
27653        let mut regular_expressions: Vec<&Expression> = Vec::new();
27654
27655        for expr in &e.expressions {
27656            match expr {
27657                Expression::Cube(c) if c.expressions.is_empty() => {
27658                    trailing_cube = true;
27659                }
27660                Expression::Rollup(r) if r.expressions.is_empty() => {
27661                    trailing_rollup = true;
27662                }
27663                _ => {
27664                    regular_expressions.push(expr);
27665                }
27666            }
27667        }
27668
27669        // In pretty mode, put columns on separate lines
27670        if self.config.pretty {
27671            self.write_newline();
27672            self.indent_level += 1;
27673            for (i, expr) in regular_expressions.iter().enumerate() {
27674                if i > 0 {
27675                    self.write(",");
27676                    self.write_newline();
27677                }
27678                self.write_indent();
27679                self.generate_expression(expr)?;
27680            }
27681            self.indent_level -= 1;
27682        } else {
27683            self.write_space();
27684            for (i, expr) in regular_expressions.iter().enumerate() {
27685                if i > 0 {
27686                    self.write(", ");
27687                }
27688                self.generate_expression(expr)?;
27689            }
27690        }
27691
27692        // Output trailing WITH CUBE or WITH ROLLUP
27693        if trailing_cube {
27694            self.write_space();
27695            self.write_keyword("WITH CUBE");
27696        } else if trailing_rollup {
27697            self.write_space();
27698            self.write_keyword("WITH ROLLUP");
27699        }
27700
27701        // ClickHouse: WITH TOTALS
27702        if e.totals {
27703            self.write_space();
27704            self.write_keyword("WITH TOTALS");
27705        }
27706
27707        Ok(())
27708    }
27709
27710    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
27711        // GROUPING(col1, col2, ...)
27712        self.write_keyword("GROUPING");
27713        self.write("(");
27714        for (i, expr) in e.expressions.iter().enumerate() {
27715            if i > 0 {
27716                self.write(", ");
27717            }
27718            self.generate_expression(expr)?;
27719        }
27720        self.write(")");
27721        Ok(())
27722    }
27723
27724    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
27725        // GROUPING_ID(col1, col2, ...)
27726        self.write_keyword("GROUPING_ID");
27727        self.write("(");
27728        for (i, expr) in e.expressions.iter().enumerate() {
27729            if i > 0 {
27730                self.write(", ");
27731            }
27732            self.generate_expression(expr)?;
27733        }
27734        self.write(")");
27735        Ok(())
27736    }
27737
27738    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
27739        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
27740        self.write_keyword("GROUPING SETS");
27741        self.write(" (");
27742        for (i, expr) in e.expressions.iter().enumerate() {
27743            if i > 0 {
27744                self.write(", ");
27745            }
27746            self.generate_expression(expr)?;
27747        }
27748        self.write(")");
27749        Ok(())
27750    }
27751
27752    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
27753        // HASH_AGG(this, expressions...)
27754        self.write_keyword("HASH_AGG");
27755        self.write("(");
27756        self.generate_expression(&e.this)?;
27757        for expr in &e.expressions {
27758            self.write(", ");
27759            self.generate_expression(expr)?;
27760        }
27761        self.write(")");
27762        Ok(())
27763    }
27764
27765    fn generate_having(&mut self, e: &Having) -> Result<()> {
27766        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
27767        self.write_keyword("HAVING");
27768        self.write_space();
27769        self.generate_expression(&e.this)?;
27770        Ok(())
27771    }
27772
27773    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
27774        // Python: this HAVING MAX|MIN expression
27775        self.generate_expression(&e.this)?;
27776        self.write_space();
27777        self.write_keyword("HAVING");
27778        self.write_space();
27779        if e.max.is_some() {
27780            self.write_keyword("MAX");
27781        } else {
27782            self.write_keyword("MIN");
27783        }
27784        self.write_space();
27785        self.generate_expression(&e.expression)?;
27786        Ok(())
27787    }
27788
27789    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
27790        use crate::dialects::DialectType;
27791        // DuckDB: convert dollar-tagged strings to single-quoted
27792        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
27793            // Extract the string content and output as single-quoted
27794            if let Expression::Literal(Literal::String(ref s)) = *e.this {
27795                return self.generate_string_literal(s);
27796            }
27797        }
27798        // PostgreSQL: preserve dollar-quoting
27799        if matches!(
27800            self.config.dialect,
27801            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
27802        ) {
27803            self.write("$");
27804            if let Some(tag) = &e.tag {
27805                self.generate_expression(tag)?;
27806            }
27807            self.write("$");
27808            self.generate_expression(&e.this)?;
27809            self.write("$");
27810            if let Some(tag) = &e.tag {
27811                self.generate_expression(tag)?;
27812            }
27813            self.write("$");
27814            return Ok(());
27815        }
27816        // Default: output as dollar-tagged
27817        self.write("$");
27818        if let Some(tag) = &e.tag {
27819            self.generate_expression(tag)?;
27820        }
27821        self.write("$");
27822        self.generate_expression(&e.this)?;
27823        self.write("$");
27824        if let Some(tag) = &e.tag {
27825            self.generate_expression(tag)?;
27826        }
27827        self.write("$");
27828        Ok(())
27829    }
27830
27831    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
27832        // HEX_ENCODE(this)
27833        self.write_keyword("HEX_ENCODE");
27834        self.write("(");
27835        self.generate_expression(&e.this)?;
27836        self.write(")");
27837        Ok(())
27838    }
27839
27840    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
27841        // Python: this (kind => expression)
27842        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
27843        match e.this.as_ref() {
27844            Expression::Identifier(id) => self.write(&id.name),
27845            other => self.generate_expression(other)?,
27846        }
27847        self.write(" (");
27848        self.write(&e.kind);
27849        self.write(" => ");
27850        self.generate_expression(&e.expression)?;
27851        self.write(")");
27852        Ok(())
27853    }
27854
27855    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
27856        // HLL(this, expressions...)
27857        self.write_keyword("HLL");
27858        self.write("(");
27859        self.generate_expression(&e.this)?;
27860        for expr in &e.expressions {
27861            self.write(", ");
27862            self.generate_expression(expr)?;
27863        }
27864        self.write(")");
27865        Ok(())
27866    }
27867
27868    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
27869        // Python: IN|OUT|IN OUT
27870        if e.input_.is_some() && e.output.is_some() {
27871            self.write_keyword("IN OUT");
27872        } else if e.input_.is_some() {
27873            self.write_keyword("IN");
27874        } else if e.output.is_some() {
27875            self.write_keyword("OUT");
27876        }
27877        Ok(())
27878    }
27879
27880    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
27881        // Python: INCLUDE this [column_def] [AS alias]
27882        self.write_keyword("INCLUDE");
27883        self.write_space();
27884        self.generate_expression(&e.this)?;
27885        if let Some(column_def) = &e.column_def {
27886            self.write_space();
27887            self.generate_expression(column_def)?;
27888        }
27889        if let Some(alias) = &e.alias {
27890            self.write_space();
27891            self.write_keyword("AS");
27892            self.write_space();
27893            self.write(alias);
27894        }
27895        Ok(())
27896    }
27897
27898    fn generate_index(&mut self, e: &Index) -> Result<()> {
27899        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
27900        if e.unique {
27901            self.write_keyword("UNIQUE");
27902            self.write_space();
27903        }
27904        if e.primary.is_some() {
27905            self.write_keyword("PRIMARY");
27906            self.write_space();
27907        }
27908        if e.amp.is_some() {
27909            self.write_keyword("AMP");
27910            self.write_space();
27911        }
27912        if e.table.is_none() {
27913            self.write_keyword("INDEX");
27914            self.write_space();
27915        }
27916        if let Some(name) = &e.this {
27917            self.generate_expression(name)?;
27918            self.write_space();
27919        }
27920        if let Some(table) = &e.table {
27921            self.write_keyword("ON");
27922            self.write_space();
27923            self.generate_expression(table)?;
27924        }
27925        if !e.params.is_empty() {
27926            self.write("(");
27927            for (i, param) in e.params.iter().enumerate() {
27928                if i > 0 {
27929                    self.write(", ");
27930                }
27931                self.generate_expression(param)?;
27932            }
27933            self.write(")");
27934        }
27935        Ok(())
27936    }
27937
27938    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
27939        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
27940        if let Some(kind) = &e.kind {
27941            self.write(kind);
27942            self.write_space();
27943        }
27944        self.write_keyword("INDEX");
27945        if let Some(this) = &e.this {
27946            self.write_space();
27947            self.generate_expression(this)?;
27948        }
27949        if let Some(index_type) = &e.index_type {
27950            self.write_space();
27951            self.write_keyword("USING");
27952            self.write_space();
27953            self.generate_expression(index_type)?;
27954        }
27955        if !e.expressions.is_empty() {
27956            self.write(" (");
27957            for (i, expr) in e.expressions.iter().enumerate() {
27958                if i > 0 {
27959                    self.write(", ");
27960                }
27961                self.generate_expression(expr)?;
27962            }
27963            self.write(")");
27964        }
27965        for opt in &e.options {
27966            self.write_space();
27967            self.generate_expression(opt)?;
27968        }
27969        Ok(())
27970    }
27971
27972    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
27973        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
27974        if let Some(key_block_size) = &e.key_block_size {
27975            self.write_keyword("KEY_BLOCK_SIZE");
27976            self.write(" = ");
27977            self.generate_expression(key_block_size)?;
27978        } else if let Some(using) = &e.using {
27979            self.write_keyword("USING");
27980            self.write_space();
27981            self.generate_expression(using)?;
27982        } else if let Some(parser) = &e.parser {
27983            self.write_keyword("WITH PARSER");
27984            self.write_space();
27985            self.generate_expression(parser)?;
27986        } else if let Some(comment) = &e.comment {
27987            self.write_keyword("COMMENT");
27988            self.write_space();
27989            self.generate_expression(comment)?;
27990        } else if let Some(visible) = &e.visible {
27991            self.generate_expression(visible)?;
27992        } else if let Some(engine_attr) = &e.engine_attr {
27993            self.write_keyword("ENGINE_ATTRIBUTE");
27994            self.write(" = ");
27995            self.generate_expression(engine_attr)?;
27996        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
27997            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
27998            self.write(" = ");
27999            self.generate_expression(secondary_engine_attr)?;
28000        }
28001        Ok(())
28002    }
28003
28004    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
28005        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
28006        if let Some(using) = &e.using {
28007            self.write_keyword("USING");
28008            self.write_space();
28009            self.generate_expression(using)?;
28010        }
28011        if !e.columns.is_empty() {
28012            self.write("(");
28013            for (i, col) in e.columns.iter().enumerate() {
28014                if i > 0 {
28015                    self.write(", ");
28016                }
28017                self.generate_expression(col)?;
28018            }
28019            self.write(")");
28020        }
28021        if let Some(partition_by) = &e.partition_by {
28022            self.write_space();
28023            self.write_keyword("PARTITION BY");
28024            self.write_space();
28025            self.generate_expression(partition_by)?;
28026        }
28027        if let Some(where_) = &e.where_ {
28028            self.write_space();
28029            self.generate_expression(where_)?;
28030        }
28031        if let Some(include) = &e.include {
28032            self.write_space();
28033            self.write_keyword("INCLUDE");
28034            self.write(" (");
28035            self.generate_expression(include)?;
28036            self.write(")");
28037        }
28038        if let Some(with_storage) = &e.with_storage {
28039            self.write_space();
28040            self.write_keyword("WITH");
28041            self.write(" (");
28042            self.generate_expression(with_storage)?;
28043            self.write(")");
28044        }
28045        if let Some(tablespace) = &e.tablespace {
28046            self.write_space();
28047            self.write_keyword("USING INDEX TABLESPACE");
28048            self.write_space();
28049            self.generate_expression(tablespace)?;
28050        }
28051        Ok(())
28052    }
28053
28054    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
28055        // Python: this INDEX [FOR target] (expressions)
28056        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
28057        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
28058        if let Expression::Identifier(id) = &*e.this {
28059            self.write_keyword(&id.name);
28060        } else {
28061            self.generate_expression(&e.this)?;
28062        }
28063        self.write_space();
28064        self.write_keyword("INDEX");
28065        if let Some(target) = &e.target {
28066            self.write_space();
28067            self.write_keyword("FOR");
28068            self.write_space();
28069            if let Expression::Identifier(id) = &**target {
28070                self.write_keyword(&id.name);
28071            } else {
28072                self.generate_expression(target)?;
28073            }
28074        }
28075        // Always output parentheses (even if empty, e.g. USE INDEX ())
28076        self.write(" (");
28077        for (i, expr) in e.expressions.iter().enumerate() {
28078            if i > 0 {
28079                self.write(", ");
28080            }
28081            self.generate_expression(expr)?;
28082        }
28083        self.write(")");
28084        Ok(())
28085    }
28086
28087    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
28088        // INHERITS (table1, table2, ...)
28089        self.write_keyword("INHERITS");
28090        self.write(" (");
28091        for (i, expr) in e.expressions.iter().enumerate() {
28092            if i > 0 {
28093                self.write(", ");
28094            }
28095            self.generate_expression(expr)?;
28096        }
28097        self.write(")");
28098        Ok(())
28099    }
28100
28101    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
28102        // INPUT(model)
28103        self.write_keyword("INPUT");
28104        self.write("(");
28105        self.generate_expression(&e.this)?;
28106        self.write(")");
28107        Ok(())
28108    }
28109
28110    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
28111        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
28112        if let Some(input_format) = &e.input_format {
28113            self.write_keyword("INPUTFORMAT");
28114            self.write_space();
28115            self.generate_expression(input_format)?;
28116        }
28117        if let Some(output_format) = &e.output_format {
28118            if e.input_format.is_some() {
28119                self.write(" ");
28120            }
28121            self.write_keyword("OUTPUTFORMAT");
28122            self.write_space();
28123            self.generate_expression(output_format)?;
28124        }
28125        Ok(())
28126    }
28127
28128    fn generate_install(&mut self, e: &Install) -> Result<()> {
28129        // [FORCE] INSTALL extension [FROM source]
28130        if e.force.is_some() {
28131            self.write_keyword("FORCE");
28132            self.write_space();
28133        }
28134        self.write_keyword("INSTALL");
28135        self.write_space();
28136        self.generate_expression(&e.this)?;
28137        if let Some(from) = &e.from_ {
28138            self.write_space();
28139            self.write_keyword("FROM");
28140            self.write_space();
28141            self.generate_expression(from)?;
28142        }
28143        Ok(())
28144    }
28145
28146    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
28147        // INTERVAL 'expression' unit
28148        self.write_keyword("INTERVAL");
28149        self.write_space();
28150        // When a unit is specified and the expression is a number,
28151        self.generate_expression(&e.expression)?;
28152        if let Some(unit) = &e.unit {
28153            self.write_space();
28154            self.write(unit);
28155        }
28156        Ok(())
28157    }
28158
28159    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
28160        // unit TO unit (e.g., HOUR TO SECOND)
28161        self.write(&format!("{:?}", e.this).to_uppercase());
28162        self.write_space();
28163        self.write_keyword("TO");
28164        self.write_space();
28165        self.write(&format!("{:?}", e.expression).to_uppercase());
28166        Ok(())
28167    }
28168
28169    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
28170        // INTO [TEMPORARY|UNLOGGED] table
28171        self.write_keyword("INTO");
28172        if e.temporary {
28173            self.write_keyword(" TEMPORARY");
28174        }
28175        if e.unlogged.is_some() {
28176            self.write_keyword(" UNLOGGED");
28177        }
28178        if let Some(this) = &e.this {
28179            self.write_space();
28180            self.generate_expression(this)?;
28181        }
28182        if !e.expressions.is_empty() {
28183            self.write(" (");
28184            for (i, expr) in e.expressions.iter().enumerate() {
28185                if i > 0 {
28186                    self.write(", ");
28187                }
28188                self.generate_expression(expr)?;
28189            }
28190            self.write(")");
28191        }
28192        Ok(())
28193    }
28194
28195    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
28196        // Python: this expression (e.g., _utf8 'string')
28197        self.generate_expression(&e.this)?;
28198        self.write_space();
28199        self.generate_expression(&e.expression)?;
28200        Ok(())
28201    }
28202
28203    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
28204        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
28205        self.write_keyword("WITH");
28206        if e.no.is_some() {
28207            self.write_keyword(" NO");
28208        }
28209        if e.concurrent.is_some() {
28210            self.write_keyword(" CONCURRENT");
28211        }
28212        self.write_keyword(" ISOLATED LOADING");
28213        if let Some(target) = &e.target {
28214            self.write_space();
28215            self.generate_expression(target)?;
28216        }
28217        Ok(())
28218    }
28219
28220    fn generate_json(&mut self, e: &JSON) -> Result<()> {
28221        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
28222        self.write_keyword("JSON");
28223        if let Some(this) = &e.this {
28224            self.write_space();
28225            self.generate_expression(this)?;
28226        }
28227        if let Some(with_) = &e.with_ {
28228            // Check if it's a truthy boolean
28229            if let Expression::Boolean(b) = with_.as_ref() {
28230                if b.value {
28231                    self.write_keyword(" WITH");
28232                } else {
28233                    self.write_keyword(" WITHOUT");
28234                }
28235            }
28236        }
28237        if e.unique {
28238            self.write_keyword(" UNIQUE KEYS");
28239        }
28240        Ok(())
28241    }
28242
28243    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
28244        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
28245        self.write_keyword("JSON_ARRAY");
28246        self.write("(");
28247        for (i, expr) in e.expressions.iter().enumerate() {
28248            if i > 0 {
28249                self.write(", ");
28250            }
28251            self.generate_expression(expr)?;
28252        }
28253        if let Some(null_handling) = &e.null_handling {
28254            self.write_space();
28255            self.generate_expression(null_handling)?;
28256        }
28257        if let Some(return_type) = &e.return_type {
28258            self.write_space();
28259            self.write_keyword("RETURNING");
28260            self.write_space();
28261            self.generate_expression(return_type)?;
28262        }
28263        if e.strict.is_some() {
28264            self.write_space();
28265            self.write_keyword("STRICT");
28266        }
28267        self.write(")");
28268        Ok(())
28269    }
28270
28271    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
28272        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
28273        self.write_keyword("JSON_ARRAYAGG");
28274        self.write("(");
28275        self.generate_expression(&e.this)?;
28276        if let Some(order) = &e.order {
28277            self.write_space();
28278            // Order is stored as an OrderBy expression
28279            if let Expression::OrderBy(ob) = order.as_ref() {
28280                self.write_keyword("ORDER BY");
28281                self.write_space();
28282                for (i, ord) in ob.expressions.iter().enumerate() {
28283                    if i > 0 {
28284                        self.write(", ");
28285                    }
28286                    self.generate_ordered(ord)?;
28287                }
28288            } else {
28289                // Fallback: generate the expression directly
28290                self.generate_expression(order)?;
28291            }
28292        }
28293        if let Some(null_handling) = &e.null_handling {
28294            self.write_space();
28295            self.generate_expression(null_handling)?;
28296        }
28297        if let Some(return_type) = &e.return_type {
28298            self.write_space();
28299            self.write_keyword("RETURNING");
28300            self.write_space();
28301            self.generate_expression(return_type)?;
28302        }
28303        if e.strict.is_some() {
28304            self.write_space();
28305            self.write_keyword("STRICT");
28306        }
28307        self.write(")");
28308        Ok(())
28309    }
28310
28311    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
28312        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
28313        self.write_keyword("JSON_OBJECTAGG");
28314        self.write("(");
28315        for (i, expr) in e.expressions.iter().enumerate() {
28316            if i > 0 {
28317                self.write(", ");
28318            }
28319            self.generate_expression(expr)?;
28320        }
28321        if let Some(null_handling) = &e.null_handling {
28322            self.write_space();
28323            self.generate_expression(null_handling)?;
28324        }
28325        if let Some(unique_keys) = &e.unique_keys {
28326            self.write_space();
28327            if let Expression::Boolean(b) = unique_keys.as_ref() {
28328                if b.value {
28329                    self.write_keyword("WITH UNIQUE KEYS");
28330                } else {
28331                    self.write_keyword("WITHOUT UNIQUE KEYS");
28332                }
28333            }
28334        }
28335        if let Some(return_type) = &e.return_type {
28336            self.write_space();
28337            self.write_keyword("RETURNING");
28338            self.write_space();
28339            self.generate_expression(return_type)?;
28340        }
28341        self.write(")");
28342        Ok(())
28343    }
28344
28345    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
28346        // JSON_ARRAY_APPEND(this, path, value, ...)
28347        self.write_keyword("JSON_ARRAY_APPEND");
28348        self.write("(");
28349        self.generate_expression(&e.this)?;
28350        for expr in &e.expressions {
28351            self.write(", ");
28352            self.generate_expression(expr)?;
28353        }
28354        self.write(")");
28355        Ok(())
28356    }
28357
28358    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
28359        // JSON_ARRAY_CONTAINS(this, expression)
28360        self.write_keyword("JSON_ARRAY_CONTAINS");
28361        self.write("(");
28362        self.generate_expression(&e.this)?;
28363        self.write(", ");
28364        self.generate_expression(&e.expression)?;
28365        self.write(")");
28366        Ok(())
28367    }
28368
28369    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
28370        // JSON_ARRAY_INSERT(this, path, value, ...)
28371        self.write_keyword("JSON_ARRAY_INSERT");
28372        self.write("(");
28373        self.generate_expression(&e.this)?;
28374        for expr in &e.expressions {
28375            self.write(", ");
28376            self.generate_expression(expr)?;
28377        }
28378        self.write(")");
28379        Ok(())
28380    }
28381
28382    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
28383        // JSONB_EXISTS(this, path)
28384        self.write_keyword("JSONB_EXISTS");
28385        self.write("(");
28386        self.generate_expression(&e.this)?;
28387        if let Some(path) = &e.path {
28388            self.write(", ");
28389            self.generate_expression(path)?;
28390        }
28391        self.write(")");
28392        Ok(())
28393    }
28394
28395    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
28396        // JSONB_EXTRACT_SCALAR(this, expression)
28397        self.write_keyword("JSONB_EXTRACT_SCALAR");
28398        self.write("(");
28399        self.generate_expression(&e.this)?;
28400        self.write(", ");
28401        self.generate_expression(&e.expression)?;
28402        self.write(")");
28403        Ok(())
28404    }
28405
28406    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
28407        // JSONB_OBJECT_AGG(this, expression)
28408        self.write_keyword("JSONB_OBJECT_AGG");
28409        self.write("(");
28410        self.generate_expression(&e.this)?;
28411        self.write(", ");
28412        self.generate_expression(&e.expression)?;
28413        self.write(")");
28414        Ok(())
28415    }
28416
28417    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
28418        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
28419        if let Some(nested_schema) = &e.nested_schema {
28420            self.write_keyword("NESTED");
28421            if let Some(path) = &e.path {
28422                self.write_space();
28423                self.write_keyword("PATH");
28424                self.write_space();
28425                self.generate_expression(path)?;
28426            }
28427            self.write_space();
28428            self.generate_expression(nested_schema)?;
28429        } else {
28430            if let Some(this) = &e.this {
28431                self.generate_expression(this)?;
28432            }
28433            if let Some(kind) = &e.kind {
28434                self.write_space();
28435                self.write(kind);
28436            }
28437            if let Some(path) = &e.path {
28438                self.write_space();
28439                self.write_keyword("PATH");
28440                self.write_space();
28441                self.generate_expression(path)?;
28442            }
28443            if e.ordinality.is_some() {
28444                self.write_keyword(" FOR ORDINALITY");
28445            }
28446        }
28447        Ok(())
28448    }
28449
28450    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
28451        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
28452        self.write_keyword("JSON_EXISTS");
28453        self.write("(");
28454        self.generate_expression(&e.this)?;
28455        if let Some(path) = &e.path {
28456            self.write(", ");
28457            self.generate_expression(path)?;
28458        }
28459        if let Some(passing) = &e.passing {
28460            self.write_space();
28461            self.write_keyword("PASSING");
28462            self.write_space();
28463            self.generate_expression(passing)?;
28464        }
28465        if let Some(on_condition) = &e.on_condition {
28466            self.write_space();
28467            self.generate_expression(on_condition)?;
28468        }
28469        self.write(")");
28470        Ok(())
28471    }
28472
28473    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
28474        self.generate_expression(&e.this)?;
28475        self.write(".:");
28476        self.generate_data_type(&e.to)?;
28477        Ok(())
28478    }
28479
28480    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
28481        // JSON_EXTRACT_ARRAY(this, expression)
28482        self.write_keyword("JSON_EXTRACT_ARRAY");
28483        self.write("(");
28484        self.generate_expression(&e.this)?;
28485        if let Some(expr) = &e.expression {
28486            self.write(", ");
28487            self.generate_expression(expr)?;
28488        }
28489        self.write(")");
28490        Ok(())
28491    }
28492
28493    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
28494        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
28495        if let Some(option) = &e.option {
28496            self.generate_expression(option)?;
28497            self.write_space();
28498        }
28499        self.write_keyword("QUOTES");
28500        if e.scalar.is_some() {
28501            self.write_keyword(" SCALAR_ONLY");
28502        }
28503        Ok(())
28504    }
28505
28506    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
28507        // JSON_EXTRACT_SCALAR(this, expression)
28508        self.write_keyword("JSON_EXTRACT_SCALAR");
28509        self.write("(");
28510        self.generate_expression(&e.this)?;
28511        self.write(", ");
28512        self.generate_expression(&e.expression)?;
28513        self.write(")");
28514        Ok(())
28515    }
28516
28517    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
28518        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
28519        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
28520        // Otherwise output JSON_EXTRACT(this, expression)
28521        if e.variant_extract.is_some() {
28522            use crate::dialects::DialectType;
28523            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
28524                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
28525                self.generate_expression(&e.this)?;
28526                self.write(":");
28527                // The expression is a string literal containing the path (e.g., 'price' or 'price.foo')
28528                // We need to output it without quotes
28529                match e.expression.as_ref() {
28530                    Expression::Literal(Literal::String(s)) => {
28531                        self.write(s);
28532                    }
28533                    _ => {
28534                        // Fallback: generate as-is (shouldn't happen in typical cases)
28535                        self.generate_expression(&e.expression)?;
28536                    }
28537                }
28538            } else {
28539                // Snowflake and others: use GET_PATH(col, 'path')
28540                self.write_keyword("GET_PATH");
28541                self.write("(");
28542                self.generate_expression(&e.this)?;
28543                self.write(", ");
28544                self.generate_expression(&e.expression)?;
28545                self.write(")");
28546            }
28547        } else {
28548            self.write_keyword("JSON_EXTRACT");
28549            self.write("(");
28550            self.generate_expression(&e.this)?;
28551            self.write(", ");
28552            self.generate_expression(&e.expression)?;
28553            for expr in &e.expressions {
28554                self.write(", ");
28555                self.generate_expression(expr)?;
28556            }
28557            self.write(")");
28558        }
28559        Ok(())
28560    }
28561
28562    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
28563        // Output: {expr} FORMAT JSON
28564        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
28565        if let Some(this) = &e.this {
28566            self.generate_expression(this)?;
28567            self.write_space();
28568        }
28569        self.write_keyword("FORMAT JSON");
28570        Ok(())
28571    }
28572
28573    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
28574        // key: value (for JSON objects)
28575        self.generate_expression(&e.this)?;
28576        self.write(": ");
28577        self.generate_expression(&e.expression)?;
28578        Ok(())
28579    }
28580
28581    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
28582        // JSON_KEYS(this, expression, expressions...)
28583        self.write_keyword("JSON_KEYS");
28584        self.write("(");
28585        self.generate_expression(&e.this)?;
28586        if let Some(expr) = &e.expression {
28587            self.write(", ");
28588            self.generate_expression(expr)?;
28589        }
28590        for expr in &e.expressions {
28591            self.write(", ");
28592            self.generate_expression(expr)?;
28593        }
28594        self.write(")");
28595        Ok(())
28596    }
28597
28598    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
28599        // JSON_KEYS(this, expression)
28600        self.write_keyword("JSON_KEYS");
28601        self.write("(");
28602        self.generate_expression(&e.this)?;
28603        if let Some(expr) = &e.expression {
28604            self.write(", ");
28605            self.generate_expression(expr)?;
28606        }
28607        self.write(")");
28608        Ok(())
28609    }
28610
28611    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
28612        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
28613        // The path components are concatenated without spaces
28614        let mut path_str = String::new();
28615        for expr in &e.expressions {
28616            match expr {
28617                Expression::JSONPathRoot(_) => {
28618                    path_str.push('$');
28619                }
28620                Expression::JSONPathKey(k) => {
28621                    // .key or ."key" (quote if key has special characters)
28622                    if let Expression::Literal(crate::expressions::Literal::String(s)) =
28623                        k.this.as_ref()
28624                    {
28625                        path_str.push('.');
28626                        // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
28627                        let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
28628                        if needs_quoting {
28629                            path_str.push('"');
28630                            path_str.push_str(s);
28631                            path_str.push('"');
28632                        } else {
28633                            path_str.push_str(s);
28634                        }
28635                    }
28636                }
28637                Expression::JSONPathSubscript(s) => {
28638                    // [index]
28639                    if let Expression::Literal(crate::expressions::Literal::Number(n)) =
28640                        s.this.as_ref()
28641                    {
28642                        path_str.push('[');
28643                        path_str.push_str(n);
28644                        path_str.push(']');
28645                    }
28646                }
28647                _ => {
28648                    // For other path parts, try to generate them
28649                    let mut temp_gen = Self::with_config(self.config.clone());
28650                    temp_gen.generate_expression(expr)?;
28651                    path_str.push_str(&temp_gen.output);
28652                }
28653            }
28654        }
28655        // Output as quoted string
28656        self.write("'");
28657        self.write(&path_str);
28658        self.write("'");
28659        Ok(())
28660    }
28661
28662    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
28663        // JSON path filter: ?(predicate)
28664        self.write("?(");
28665        self.generate_expression(&e.this)?;
28666        self.write(")");
28667        Ok(())
28668    }
28669
28670    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
28671        // JSON path key: .key or ["key"]
28672        self.write(".");
28673        self.generate_expression(&e.this)?;
28674        Ok(())
28675    }
28676
28677    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
28678        // JSON path recursive descent: ..
28679        self.write("..");
28680        if let Some(this) = &e.this {
28681            self.generate_expression(this)?;
28682        }
28683        Ok(())
28684    }
28685
28686    fn generate_json_path_root(&mut self) -> Result<()> {
28687        // JSON path root: $
28688        self.write("$");
28689        Ok(())
28690    }
28691
28692    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
28693        // JSON path script: (expression)
28694        self.write("(");
28695        self.generate_expression(&e.this)?;
28696        self.write(")");
28697        Ok(())
28698    }
28699
28700    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
28701        // JSON path selector: *
28702        self.generate_expression(&e.this)?;
28703        Ok(())
28704    }
28705
28706    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
28707        // JSON path slice: [start:end:step]
28708        self.write("[");
28709        if let Some(start) = &e.start {
28710            self.generate_expression(start)?;
28711        }
28712        self.write(":");
28713        if let Some(end) = &e.end {
28714            self.generate_expression(end)?;
28715        }
28716        if let Some(step) = &e.step {
28717            self.write(":");
28718            self.generate_expression(step)?;
28719        }
28720        self.write("]");
28721        Ok(())
28722    }
28723
28724    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
28725        // JSON path subscript: [index] or [*]
28726        self.write("[");
28727        self.generate_expression(&e.this)?;
28728        self.write("]");
28729        Ok(())
28730    }
28731
28732    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
28733        // JSON path union: [key1, key2, ...]
28734        self.write("[");
28735        for (i, expr) in e.expressions.iter().enumerate() {
28736            if i > 0 {
28737                self.write(", ");
28738            }
28739            self.generate_expression(expr)?;
28740        }
28741        self.write("]");
28742        Ok(())
28743    }
28744
28745    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
28746        // JSON_REMOVE(this, path1, path2, ...)
28747        self.write_keyword("JSON_REMOVE");
28748        self.write("(");
28749        self.generate_expression(&e.this)?;
28750        for expr in &e.expressions {
28751            self.write(", ");
28752            self.generate_expression(expr)?;
28753        }
28754        self.write(")");
28755        Ok(())
28756    }
28757
28758    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
28759        // COLUMNS(col1 type, col2 type, ...)
28760        // When pretty printing and content is too wide, format with each column on a separate line
28761        self.write_keyword("COLUMNS");
28762        self.write("(");
28763
28764        if self.config.pretty && !e.expressions.is_empty() {
28765            // First, generate all expressions into strings to check width
28766            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
28767            for expr in &e.expressions {
28768                let mut temp_gen = Generator::with_config(self.config.clone());
28769                temp_gen.generate_expression(expr)?;
28770                expr_strings.push(temp_gen.output);
28771            }
28772
28773            // Check if total width exceeds max_text_width
28774            if self.too_wide(&expr_strings) {
28775                // Pretty print: each column on its own line
28776                self.write_newline();
28777                self.indent_level += 1;
28778                for (i, expr_str) in expr_strings.iter().enumerate() {
28779                    if i > 0 {
28780                        self.write(",");
28781                        self.write_newline();
28782                    }
28783                    self.write_indent();
28784                    self.write(expr_str);
28785                }
28786                self.write_newline();
28787                self.indent_level -= 1;
28788                self.write_indent();
28789            } else {
28790                // Compact: all on one line
28791                for (i, expr_str) in expr_strings.iter().enumerate() {
28792                    if i > 0 {
28793                        self.write(", ");
28794                    }
28795                    self.write(expr_str);
28796                }
28797            }
28798        } else {
28799            // Non-pretty mode: compact format
28800            for (i, expr) in e.expressions.iter().enumerate() {
28801                if i > 0 {
28802                    self.write(", ");
28803                }
28804                self.generate_expression(expr)?;
28805            }
28806        }
28807        self.write(")");
28808        Ok(())
28809    }
28810
28811    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
28812        // JSON_SET(this, path, value, ...)
28813        self.write_keyword("JSON_SET");
28814        self.write("(");
28815        self.generate_expression(&e.this)?;
28816        for expr in &e.expressions {
28817            self.write(", ");
28818            self.generate_expression(expr)?;
28819        }
28820        self.write(")");
28821        Ok(())
28822    }
28823
28824    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
28825        // JSON_STRIP_NULLS(this, expression)
28826        self.write_keyword("JSON_STRIP_NULLS");
28827        self.write("(");
28828        self.generate_expression(&e.this)?;
28829        if let Some(expr) = &e.expression {
28830            self.write(", ");
28831            self.generate_expression(expr)?;
28832        }
28833        self.write(")");
28834        Ok(())
28835    }
28836
28837    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
28838        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
28839        self.write_keyword("JSON_TABLE");
28840        self.write("(");
28841        self.generate_expression(&e.this)?;
28842        if let Some(path) = &e.path {
28843            self.write(", ");
28844            self.generate_expression(path)?;
28845        }
28846        if let Some(error_handling) = &e.error_handling {
28847            self.write_space();
28848            self.generate_expression(error_handling)?;
28849        }
28850        if let Some(empty_handling) = &e.empty_handling {
28851            self.write_space();
28852            self.generate_expression(empty_handling)?;
28853        }
28854        if let Some(schema) = &e.schema {
28855            self.write_space();
28856            self.generate_expression(schema)?;
28857        }
28858        self.write(")");
28859        Ok(())
28860    }
28861
28862    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
28863        // JSON_TYPE(this)
28864        self.write_keyword("JSON_TYPE");
28865        self.write("(");
28866        self.generate_expression(&e.this)?;
28867        self.write(")");
28868        Ok(())
28869    }
28870
28871    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
28872        // JSON_VALUE(this, path RETURNING type ON condition)
28873        self.write_keyword("JSON_VALUE");
28874        self.write("(");
28875        self.generate_expression(&e.this)?;
28876        if let Some(path) = &e.path {
28877            self.write(", ");
28878            self.generate_expression(path)?;
28879        }
28880        if let Some(returning) = &e.returning {
28881            self.write_space();
28882            self.write_keyword("RETURNING");
28883            self.write_space();
28884            self.generate_expression(returning)?;
28885        }
28886        if let Some(on_condition) = &e.on_condition {
28887            self.write_space();
28888            self.generate_expression(on_condition)?;
28889        }
28890        self.write(")");
28891        Ok(())
28892    }
28893
28894    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
28895        // JSON_VALUE_ARRAY(this)
28896        self.write_keyword("JSON_VALUE_ARRAY");
28897        self.write("(");
28898        self.generate_expression(&e.this)?;
28899        self.write(")");
28900        Ok(())
28901    }
28902
28903    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
28904        // JAROWINKLER_SIMILARITY(str1, str2)
28905        self.write_keyword("JAROWINKLER_SIMILARITY");
28906        self.write("(");
28907        self.generate_expression(&e.this)?;
28908        self.write(", ");
28909        self.generate_expression(&e.expression)?;
28910        self.write(")");
28911        Ok(())
28912    }
28913
28914    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
28915        // Python: this(expressions)
28916        self.generate_expression(&e.this)?;
28917        self.write("(");
28918        for (i, expr) in e.expressions.iter().enumerate() {
28919            if i > 0 {
28920                self.write(", ");
28921            }
28922            self.generate_expression(expr)?;
28923        }
28924        self.write(")");
28925        Ok(())
28926    }
28927
28928    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
28929        // Python: {no}{local}{dual}{before}{after}JOURNAL
28930        if e.no.is_some() {
28931            self.write_keyword("NO ");
28932        }
28933        if let Some(local) = &e.local {
28934            self.generate_expression(local)?;
28935            self.write_space();
28936        }
28937        if e.dual.is_some() {
28938            self.write_keyword("DUAL ");
28939        }
28940        if e.before.is_some() {
28941            self.write_keyword("BEFORE ");
28942        }
28943        if e.after.is_some() {
28944            self.write_keyword("AFTER ");
28945        }
28946        self.write_keyword("JOURNAL");
28947        Ok(())
28948    }
28949
28950    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
28951        // LANGUAGE language_name
28952        self.write_keyword("LANGUAGE");
28953        self.write_space();
28954        self.generate_expression(&e.this)?;
28955        Ok(())
28956    }
28957
28958    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
28959        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
28960        if e.view.is_some() {
28961            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
28962            self.write_keyword("LATERAL VIEW");
28963            if e.outer.is_some() {
28964                self.write_space();
28965                self.write_keyword("OUTER");
28966            }
28967            self.write_space();
28968            self.generate_expression(&e.this)?;
28969            if let Some(alias) = &e.alias {
28970                self.write_space();
28971                self.write(alias);
28972            }
28973        } else {
28974            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
28975            self.write_keyword("LATERAL");
28976            self.write_space();
28977            self.generate_expression(&e.this)?;
28978            if e.ordinality.is_some() {
28979                self.write_space();
28980                self.write_keyword("WITH ORDINALITY");
28981            }
28982            if let Some(alias) = &e.alias {
28983                self.write_space();
28984                self.write_keyword("AS");
28985                self.write_space();
28986                self.write(alias);
28987                if !e.column_aliases.is_empty() {
28988                    self.write("(");
28989                    for (i, col) in e.column_aliases.iter().enumerate() {
28990                        if i > 0 {
28991                            self.write(", ");
28992                        }
28993                        self.write(col);
28994                    }
28995                    self.write(")");
28996                }
28997            }
28998        }
28999        Ok(())
29000    }
29001
29002    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
29003        // Python: LIKE this [options]
29004        self.write_keyword("LIKE");
29005        self.write_space();
29006        self.generate_expression(&e.this)?;
29007        for expr in &e.expressions {
29008            self.write_space();
29009            self.generate_expression(expr)?;
29010        }
29011        Ok(())
29012    }
29013
29014    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
29015        self.write_keyword("LIMIT");
29016        self.write_space();
29017        self.write_limit_expr(&e.this)?;
29018        if e.percent {
29019            self.write_space();
29020            self.write_keyword("PERCENT");
29021        }
29022        // Emit any comments that were captured from before the LIMIT keyword
29023        for comment in &e.comments {
29024            self.write(" ");
29025            self.write_formatted_comment(comment);
29026        }
29027        Ok(())
29028    }
29029
29030    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
29031        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
29032        if e.percent.is_some() {
29033            self.write_keyword(" PERCENT");
29034        }
29035        if e.rows.is_some() {
29036            self.write_keyword(" ROWS");
29037        }
29038        if e.with_ties.is_some() {
29039            self.write_keyword(" WITH TIES");
29040        } else if e.rows.is_some() {
29041            self.write_keyword(" ONLY");
29042        }
29043        Ok(())
29044    }
29045
29046    fn generate_list(&mut self, e: &List) -> Result<()> {
29047        use crate::dialects::DialectType;
29048        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
29049
29050        // Check if this is a subquery-based list (LIST(SELECT ...))
29051        if e.expressions.len() == 1 {
29052            if let Expression::Select(_) = &e.expressions[0] {
29053                self.write_keyword("LIST");
29054                self.write("(");
29055                self.generate_expression(&e.expressions[0])?;
29056                self.write(")");
29057                return Ok(());
29058            }
29059        }
29060
29061        // For Materialize, output as LIST[expr, expr, ...]
29062        if is_materialize {
29063            self.write_keyword("LIST");
29064            self.write("[");
29065            for (i, expr) in e.expressions.iter().enumerate() {
29066                if i > 0 {
29067                    self.write(", ");
29068                }
29069                self.generate_expression(expr)?;
29070            }
29071            self.write("]");
29072        } else {
29073            // For other dialects, output as LIST(expr, expr, ...)
29074            self.write_keyword("LIST");
29075            self.write("(");
29076            for (i, expr) in e.expressions.iter().enumerate() {
29077                if i > 0 {
29078                    self.write(", ");
29079                }
29080                self.generate_expression(expr)?;
29081            }
29082            self.write(")");
29083        }
29084        Ok(())
29085    }
29086
29087    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
29088        // Check if this is a subquery-based map (MAP(SELECT ...))
29089        if let Expression::Select(_) = &*e.this {
29090            self.write_keyword("MAP");
29091            self.write("(");
29092            self.generate_expression(&e.this)?;
29093            self.write(")");
29094            return Ok(());
29095        }
29096
29097        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
29098
29099        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
29100        self.write_keyword("MAP");
29101        if is_duckdb {
29102            self.write(" {");
29103        } else {
29104            self.write("[");
29105        }
29106        if let Expression::Struct(s) = &*e.this {
29107            for (i, (_, expr)) in s.fields.iter().enumerate() {
29108                if i > 0 {
29109                    self.write(", ");
29110                }
29111                if let Expression::PropertyEQ(op) = expr {
29112                    self.generate_expression(&op.left)?;
29113                    if is_duckdb {
29114                        self.write(": ");
29115                    } else {
29116                        self.write(" => ");
29117                    }
29118                    self.generate_expression(&op.right)?;
29119                } else {
29120                    self.generate_expression(expr)?;
29121                }
29122            }
29123        }
29124        if is_duckdb {
29125            self.write("}");
29126        } else {
29127            self.write("]");
29128        }
29129        Ok(())
29130    }
29131
29132    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
29133        // Python: LOCALTIME or LOCALTIME(precision)
29134        self.write_keyword("LOCALTIME");
29135        if let Some(precision) = &e.this {
29136            self.write("(");
29137            self.generate_expression(precision)?;
29138            self.write(")");
29139        }
29140        Ok(())
29141    }
29142
29143    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
29144        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
29145        self.write_keyword("LOCALTIMESTAMP");
29146        if let Some(precision) = &e.this {
29147            self.write("(");
29148            self.generate_expression(precision)?;
29149            self.write(")");
29150        }
29151        Ok(())
29152    }
29153
29154    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
29155        // LOCATION 'path'
29156        self.write_keyword("LOCATION");
29157        self.write_space();
29158        self.generate_expression(&e.this)?;
29159        Ok(())
29160    }
29161
29162    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
29163        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
29164        if e.update.is_some() {
29165            if e.key.is_some() {
29166                self.write_keyword("FOR NO KEY UPDATE");
29167            } else {
29168                self.write_keyword("FOR UPDATE");
29169            }
29170        } else {
29171            if e.key.is_some() {
29172                self.write_keyword("FOR KEY SHARE");
29173            } else {
29174                self.write_keyword("FOR SHARE");
29175            }
29176        }
29177        if !e.expressions.is_empty() {
29178            self.write_keyword(" OF ");
29179            for (i, expr) in e.expressions.iter().enumerate() {
29180                if i > 0 {
29181                    self.write(", ");
29182                }
29183                self.generate_expression(expr)?;
29184            }
29185        }
29186        // Handle wait option following Python sqlglot convention:
29187        // - Boolean(true) -> NOWAIT
29188        // - Boolean(false) -> SKIP LOCKED
29189        // - Literal (number) -> WAIT n
29190        if let Some(wait) = &e.wait {
29191            match wait.as_ref() {
29192                Expression::Boolean(b) => {
29193                    if b.value {
29194                        self.write_keyword(" NOWAIT");
29195                    } else {
29196                        self.write_keyword(" SKIP LOCKED");
29197                    }
29198                }
29199                _ => {
29200                    // It's a literal (number), output WAIT n
29201                    self.write_keyword(" WAIT ");
29202                    self.generate_expression(wait)?;
29203                }
29204            }
29205        }
29206        Ok(())
29207    }
29208
29209    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
29210        // LOCK property
29211        self.write_keyword("LOCK");
29212        self.write_space();
29213        self.generate_expression(&e.this)?;
29214        Ok(())
29215    }
29216
29217    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
29218        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
29219        self.write_keyword("LOCKING");
29220        self.write_space();
29221        self.write(&e.kind);
29222        if let Some(this) = &e.this {
29223            self.write_space();
29224            self.generate_expression(this)?;
29225        }
29226        if let Some(for_or_in) = &e.for_or_in {
29227            self.write_space();
29228            self.generate_expression(for_or_in)?;
29229        }
29230        if let Some(lock_type) = &e.lock_type {
29231            self.write_space();
29232            self.generate_expression(lock_type)?;
29233        }
29234        if e.override_.is_some() {
29235            self.write_keyword(" OVERRIDE");
29236        }
29237        Ok(())
29238    }
29239
29240    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
29241        // this expression
29242        self.generate_expression(&e.this)?;
29243        self.write_space();
29244        self.generate_expression(&e.expression)?;
29245        Ok(())
29246    }
29247
29248    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
29249        // [NO] LOG
29250        if e.no.is_some() {
29251            self.write_keyword("NO ");
29252        }
29253        self.write_keyword("LOG");
29254        Ok(())
29255    }
29256
29257    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
29258        // MD5(this, expressions...)
29259        self.write_keyword("MD5");
29260        self.write("(");
29261        self.generate_expression(&e.this)?;
29262        for expr in &e.expressions {
29263            self.write(", ");
29264            self.generate_expression(expr)?;
29265        }
29266        self.write(")");
29267        Ok(())
29268    }
29269
29270    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
29271        // ML.FORECAST(model, [params])
29272        self.write_keyword("ML.FORECAST");
29273        self.write("(");
29274        self.generate_expression(&e.this)?;
29275        if let Some(expression) = &e.expression {
29276            self.write(", ");
29277            self.generate_expression(expression)?;
29278        }
29279        if let Some(params) = &e.params_struct {
29280            self.write(", ");
29281            self.generate_expression(params)?;
29282        }
29283        self.write(")");
29284        Ok(())
29285    }
29286
29287    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
29288        // ML.TRANSLATE(model, input, [params])
29289        self.write_keyword("ML.TRANSLATE");
29290        self.write("(");
29291        self.generate_expression(&e.this)?;
29292        self.write(", ");
29293        self.generate_expression(&e.expression)?;
29294        if let Some(params) = &e.params_struct {
29295            self.write(", ");
29296            self.generate_expression(params)?;
29297        }
29298        self.write(")");
29299        Ok(())
29300    }
29301
29302    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
29303        // MAKE_INTERVAL(years => x, months => y, ...)
29304        self.write_keyword("MAKE_INTERVAL");
29305        self.write("(");
29306        let mut first = true;
29307        if let Some(year) = &e.year {
29308            self.write("years => ");
29309            self.generate_expression(year)?;
29310            first = false;
29311        }
29312        if let Some(month) = &e.month {
29313            if !first {
29314                self.write(", ");
29315            }
29316            self.write("months => ");
29317            self.generate_expression(month)?;
29318            first = false;
29319        }
29320        if let Some(week) = &e.week {
29321            if !first {
29322                self.write(", ");
29323            }
29324            self.write("weeks => ");
29325            self.generate_expression(week)?;
29326            first = false;
29327        }
29328        if let Some(day) = &e.day {
29329            if !first {
29330                self.write(", ");
29331            }
29332            self.write("days => ");
29333            self.generate_expression(day)?;
29334            first = false;
29335        }
29336        if let Some(hour) = &e.hour {
29337            if !first {
29338                self.write(", ");
29339            }
29340            self.write("hours => ");
29341            self.generate_expression(hour)?;
29342            first = false;
29343        }
29344        if let Some(minute) = &e.minute {
29345            if !first {
29346                self.write(", ");
29347            }
29348            self.write("mins => ");
29349            self.generate_expression(minute)?;
29350            first = false;
29351        }
29352        if let Some(second) = &e.second {
29353            if !first {
29354                self.write(", ");
29355            }
29356            self.write("secs => ");
29357            self.generate_expression(second)?;
29358        }
29359        self.write(")");
29360        Ok(())
29361    }
29362
29363    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
29364        // MANHATTAN_DISTANCE(vector1, vector2)
29365        self.write_keyword("MANHATTAN_DISTANCE");
29366        self.write("(");
29367        self.generate_expression(&e.this)?;
29368        self.write(", ");
29369        self.generate_expression(&e.expression)?;
29370        self.write(")");
29371        Ok(())
29372    }
29373
29374    fn generate_map(&mut self, e: &Map) -> Result<()> {
29375        // MAP(key1, value1, key2, value2, ...)
29376        self.write_keyword("MAP");
29377        self.write("(");
29378        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
29379            if i > 0 {
29380                self.write(", ");
29381            }
29382            self.generate_expression(key)?;
29383            self.write(", ");
29384            self.generate_expression(value)?;
29385        }
29386        self.write(")");
29387        Ok(())
29388    }
29389
29390    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
29391        // MAP_CAT(map1, map2)
29392        self.write_keyword("MAP_CAT");
29393        self.write("(");
29394        self.generate_expression(&e.this)?;
29395        self.write(", ");
29396        self.generate_expression(&e.expression)?;
29397        self.write(")");
29398        Ok(())
29399    }
29400
29401    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
29402        // MAP_DELETE(map, key1, key2, ...)
29403        self.write_keyword("MAP_DELETE");
29404        self.write("(");
29405        self.generate_expression(&e.this)?;
29406        for expr in &e.expressions {
29407            self.write(", ");
29408            self.generate_expression(expr)?;
29409        }
29410        self.write(")");
29411        Ok(())
29412    }
29413
29414    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
29415        // MAP_INSERT(map, key, value, [update_flag])
29416        self.write_keyword("MAP_INSERT");
29417        self.write("(");
29418        self.generate_expression(&e.this)?;
29419        if let Some(key) = &e.key {
29420            self.write(", ");
29421            self.generate_expression(key)?;
29422        }
29423        if let Some(value) = &e.value {
29424            self.write(", ");
29425            self.generate_expression(value)?;
29426        }
29427        if let Some(update_flag) = &e.update_flag {
29428            self.write(", ");
29429            self.generate_expression(update_flag)?;
29430        }
29431        self.write(")");
29432        Ok(())
29433    }
29434
29435    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
29436        // MAP_PICK(map, key1, key2, ...)
29437        self.write_keyword("MAP_PICK");
29438        self.write("(");
29439        self.generate_expression(&e.this)?;
29440        for expr in &e.expressions {
29441            self.write(", ");
29442            self.generate_expression(expr)?;
29443        }
29444        self.write(")");
29445        Ok(())
29446    }
29447
29448    fn generate_masking_policy_column_constraint(
29449        &mut self,
29450        e: &MaskingPolicyColumnConstraint,
29451    ) -> Result<()> {
29452        // Python: MASKING POLICY name [USING (cols)]
29453        self.write_keyword("MASKING POLICY");
29454        self.write_space();
29455        self.generate_expression(&e.this)?;
29456        if !e.expressions.is_empty() {
29457            self.write_keyword(" USING");
29458            self.write(" (");
29459            for (i, expr) in e.expressions.iter().enumerate() {
29460                if i > 0 {
29461                    self.write(", ");
29462                }
29463                self.generate_expression(expr)?;
29464            }
29465            self.write(")");
29466        }
29467        Ok(())
29468    }
29469
29470    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
29471        if matches!(
29472            self.config.dialect,
29473            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
29474        ) {
29475            if e.expressions.len() > 1 {
29476                self.write("(");
29477            }
29478            for (i, expr) in e.expressions.iter().enumerate() {
29479                if i > 0 {
29480                    self.write_keyword(" OR ");
29481                }
29482                self.generate_expression(expr)?;
29483                self.write_space();
29484                self.write("@@");
29485                self.write_space();
29486                self.generate_expression(&e.this)?;
29487            }
29488            if e.expressions.len() > 1 {
29489                self.write(")");
29490            }
29491            return Ok(());
29492        }
29493
29494        // MATCH(columns) AGAINST(expr [modifier])
29495        self.write_keyword("MATCH");
29496        self.write("(");
29497        for (i, expr) in e.expressions.iter().enumerate() {
29498            if i > 0 {
29499                self.write(", ");
29500            }
29501            self.generate_expression(expr)?;
29502        }
29503        self.write(")");
29504        self.write_keyword(" AGAINST");
29505        self.write("(");
29506        self.generate_expression(&e.this)?;
29507        if let Some(modifier) = &e.modifier {
29508            self.write_space();
29509            self.generate_expression(modifier)?;
29510        }
29511        self.write(")");
29512        Ok(())
29513    }
29514
29515    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
29516        // Python: [window_frame] this
29517        if let Some(window_frame) = &e.window_frame {
29518            self.write(&format!("{:?}", window_frame).to_uppercase());
29519            self.write_space();
29520        }
29521        self.generate_expression(&e.this)?;
29522        Ok(())
29523    }
29524
29525    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
29526        // MATERIALIZED [this]
29527        self.write_keyword("MATERIALIZED");
29528        if let Some(this) = &e.this {
29529            self.write_space();
29530            self.generate_expression(this)?;
29531        }
29532        Ok(())
29533    }
29534
29535    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
29536        // MERGE INTO target USING source ON condition WHEN ...
29537        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
29538        if let Some(with_) = &e.with_ {
29539            self.generate_expression(with_)?;
29540            self.write_space();
29541        }
29542        self.write_keyword("MERGE INTO");
29543        self.write_space();
29544        self.generate_expression(&e.this)?;
29545
29546        // USING clause - newline before in pretty mode
29547        if self.config.pretty {
29548            self.write_newline();
29549            self.write_indent();
29550        } else {
29551            self.write_space();
29552        }
29553        self.write_keyword("USING");
29554        self.write_space();
29555        self.generate_expression(&e.using)?;
29556
29557        // ON clause - newline before in pretty mode
29558        if let Some(on) = &e.on {
29559            if self.config.pretty {
29560                self.write_newline();
29561                self.write_indent();
29562            } else {
29563                self.write_space();
29564            }
29565            self.write_keyword("ON");
29566            self.write_space();
29567            self.generate_expression(on)?;
29568        }
29569        // DuckDB USING (key_columns) clause
29570        if let Some(using_cond) = &e.using_cond {
29571            self.write_space();
29572            self.write_keyword("USING");
29573            self.write_space();
29574            self.write("(");
29575            // using_cond is a Tuple containing the column identifiers
29576            if let Expression::Tuple(tuple) = using_cond.as_ref() {
29577                for (i, col) in tuple.expressions.iter().enumerate() {
29578                    if i > 0 {
29579                        self.write(", ");
29580                    }
29581                    self.generate_expression(col)?;
29582                }
29583            } else {
29584                self.generate_expression(using_cond)?;
29585            }
29586            self.write(")");
29587        }
29588        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
29589        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
29590        if matches!(
29591            self.config.dialect,
29592            Some(crate::DialectType::PostgreSQL)
29593                | Some(crate::DialectType::Redshift)
29594                | Some(crate::DialectType::Trino)
29595                | Some(crate::DialectType::Presto)
29596                | Some(crate::DialectType::Athena)
29597        ) {
29598            let mut names = Vec::new();
29599            match e.this.as_ref() {
29600                Expression::Alias(a) => {
29601                    // e.g., "x AS z" -> strip both "x" and "z"
29602                    if let Expression::Table(t) = &a.this {
29603                        names.push(t.name.name.clone());
29604                    } else if let Expression::Identifier(id) = &a.this {
29605                        names.push(id.name.clone());
29606                    }
29607                    names.push(a.alias.name.clone());
29608                }
29609                Expression::Table(t) => {
29610                    names.push(t.name.name.clone());
29611                }
29612                Expression::Identifier(id) => {
29613                    names.push(id.name.clone());
29614                }
29615                _ => {}
29616            }
29617            self.merge_strip_qualifiers = names;
29618        }
29619
29620        // WHEN clauses - newline before each in pretty mode
29621        if let Some(whens) = &e.whens {
29622            if self.config.pretty {
29623                self.write_newline();
29624                self.write_indent();
29625            } else {
29626                self.write_space();
29627            }
29628            self.generate_expression(whens)?;
29629        }
29630
29631        // Restore merge_strip_qualifiers
29632        self.merge_strip_qualifiers = saved_merge_strip;
29633
29634        // OUTPUT/RETURNING clause - newline before in pretty mode
29635        if let Some(returning) = &e.returning {
29636            if self.config.pretty {
29637                self.write_newline();
29638                self.write_indent();
29639            } else {
29640                self.write_space();
29641            }
29642            self.generate_expression(returning)?;
29643        }
29644        Ok(())
29645    }
29646
29647    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
29648        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
29649        if e.no.is_some() {
29650            self.write_keyword("NO MERGEBLOCKRATIO");
29651        } else if e.default.is_some() {
29652            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
29653        } else {
29654            self.write_keyword("MERGEBLOCKRATIO");
29655            self.write("=");
29656            if let Some(this) = &e.this {
29657                self.generate_expression(this)?;
29658            }
29659            if e.percent.is_some() {
29660                self.write_keyword(" PERCENT");
29661            }
29662        }
29663        Ok(())
29664    }
29665
29666    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
29667        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
29668        self.write_keyword("TTL");
29669        let pretty_clickhouse = self.config.pretty
29670            && matches!(
29671                self.config.dialect,
29672                Some(crate::dialects::DialectType::ClickHouse)
29673            );
29674
29675        if pretty_clickhouse {
29676            self.write_newline();
29677            self.indent_level += 1;
29678            for (i, expr) in e.expressions.iter().enumerate() {
29679                if i > 0 {
29680                    self.write(",");
29681                    self.write_newline();
29682                }
29683                self.write_indent();
29684                self.generate_expression(expr)?;
29685            }
29686            self.indent_level -= 1;
29687        } else {
29688            self.write_space();
29689            for (i, expr) in e.expressions.iter().enumerate() {
29690                if i > 0 {
29691                    self.write(", ");
29692                }
29693                self.generate_expression(expr)?;
29694            }
29695        }
29696
29697        if let Some(where_) = &e.where_ {
29698            if pretty_clickhouse {
29699                self.write_newline();
29700                if let Expression::Where(w) = where_.as_ref() {
29701                    self.write_indent();
29702                    self.write_keyword("WHERE");
29703                    self.write_newline();
29704                    self.indent_level += 1;
29705                    self.write_indent();
29706                    self.generate_expression(&w.this)?;
29707                    self.indent_level -= 1;
29708                } else {
29709                    self.write_indent();
29710                    self.generate_expression(where_)?;
29711                }
29712            } else {
29713                self.write_space();
29714                self.generate_expression(where_)?;
29715            }
29716        }
29717        if let Some(group) = &e.group {
29718            if pretty_clickhouse {
29719                self.write_newline();
29720                if let Expression::Group(g) = group.as_ref() {
29721                    self.write_indent();
29722                    self.write_keyword("GROUP BY");
29723                    self.write_newline();
29724                    self.indent_level += 1;
29725                    for (i, expr) in g.expressions.iter().enumerate() {
29726                        if i > 0 {
29727                            self.write(",");
29728                            self.write_newline();
29729                        }
29730                        self.write_indent();
29731                        self.generate_expression(expr)?;
29732                    }
29733                    self.indent_level -= 1;
29734                } else {
29735                    self.write_indent();
29736                    self.generate_expression(group)?;
29737                }
29738            } else {
29739                self.write_space();
29740                self.generate_expression(group)?;
29741            }
29742        }
29743        if let Some(aggregates) = &e.aggregates {
29744            if pretty_clickhouse {
29745                self.write_newline();
29746                self.write_indent();
29747                self.write_keyword("SET");
29748                self.write_newline();
29749                self.indent_level += 1;
29750                if let Expression::Tuple(t) = aggregates.as_ref() {
29751                    for (i, agg) in t.expressions.iter().enumerate() {
29752                        if i > 0 {
29753                            self.write(",");
29754                            self.write_newline();
29755                        }
29756                        self.write_indent();
29757                        self.generate_expression(agg)?;
29758                    }
29759                } else {
29760                    self.write_indent();
29761                    self.generate_expression(aggregates)?;
29762                }
29763                self.indent_level -= 1;
29764            } else {
29765                self.write_space();
29766                self.write_keyword("SET");
29767                self.write_space();
29768                self.generate_expression(aggregates)?;
29769            }
29770        }
29771        Ok(())
29772    }
29773
29774    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
29775        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
29776        self.generate_expression(&e.this)?;
29777        if e.delete.is_some() {
29778            self.write_keyword(" DELETE");
29779        }
29780        if let Some(recompress) = &e.recompress {
29781            self.write_keyword(" RECOMPRESS ");
29782            self.generate_expression(recompress)?;
29783        }
29784        if let Some(to_disk) = &e.to_disk {
29785            self.write_keyword(" TO DISK ");
29786            self.generate_expression(to_disk)?;
29787        }
29788        if let Some(to_volume) = &e.to_volume {
29789            self.write_keyword(" TO VOLUME ");
29790            self.generate_expression(to_volume)?;
29791        }
29792        Ok(())
29793    }
29794
29795    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
29796        // MINHASH(this, expressions...)
29797        self.write_keyword("MINHASH");
29798        self.write("(");
29799        self.generate_expression(&e.this)?;
29800        for expr in &e.expressions {
29801            self.write(", ");
29802            self.generate_expression(expr)?;
29803        }
29804        self.write(")");
29805        Ok(())
29806    }
29807
29808    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
29809        // model!attribute - Snowflake syntax
29810        self.generate_expression(&e.this)?;
29811        self.write("!");
29812        self.generate_expression(&e.expression)?;
29813        Ok(())
29814    }
29815
29816    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
29817        // MONTHNAME(this)
29818        self.write_keyword("MONTHNAME");
29819        self.write("(");
29820        self.generate_expression(&e.this)?;
29821        self.write(")");
29822        Ok(())
29823    }
29824
29825    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
29826        // Output leading comments
29827        for comment in &e.leading_comments {
29828            self.write_formatted_comment(comment);
29829            if self.config.pretty {
29830                self.write_newline();
29831                self.write_indent();
29832            } else {
29833                self.write_space();
29834            }
29835        }
29836        // Python: INSERT kind expressions source
29837        self.write_keyword("INSERT");
29838        self.write_space();
29839        self.write(&e.kind);
29840        if self.config.pretty {
29841            self.indent_level += 1;
29842            for expr in &e.expressions {
29843                self.write_newline();
29844                self.write_indent();
29845                self.generate_expression(expr)?;
29846            }
29847            self.indent_level -= 1;
29848        } else {
29849            for expr in &e.expressions {
29850                self.write_space();
29851                self.generate_expression(expr)?;
29852            }
29853        }
29854        if let Some(source) = &e.source {
29855            if self.config.pretty {
29856                self.write_newline();
29857                self.write_indent();
29858            } else {
29859                self.write_space();
29860            }
29861            self.generate_expression(source)?;
29862        }
29863        Ok(())
29864    }
29865
29866    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
29867        // Python: NEXT VALUE FOR this [OVER (order)]
29868        self.write_keyword("NEXT VALUE FOR");
29869        self.write_space();
29870        self.generate_expression(&e.this)?;
29871        if let Some(order) = &e.order {
29872            self.write_space();
29873            self.write_keyword("OVER");
29874            self.write(" (");
29875            self.generate_expression(order)?;
29876            self.write(")");
29877        }
29878        Ok(())
29879    }
29880
29881    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
29882        // NORMAL(mean, stddev, gen)
29883        self.write_keyword("NORMAL");
29884        self.write("(");
29885        self.generate_expression(&e.this)?;
29886        if let Some(stddev) = &e.stddev {
29887            self.write(", ");
29888            self.generate_expression(stddev)?;
29889        }
29890        if let Some(gen) = &e.gen {
29891            self.write(", ");
29892            self.generate_expression(gen)?;
29893        }
29894        self.write(")");
29895        Ok(())
29896    }
29897
29898    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
29899        // NORMALIZE(this, form) or CASEFOLD version
29900        if e.is_casefold.is_some() {
29901            self.write_keyword("NORMALIZE_AND_CASEFOLD");
29902        } else {
29903            self.write_keyword("NORMALIZE");
29904        }
29905        self.write("(");
29906        self.generate_expression(&e.this)?;
29907        if let Some(form) = &e.form {
29908            self.write(", ");
29909            self.generate_expression(form)?;
29910        }
29911        self.write(")");
29912        Ok(())
29913    }
29914
29915    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
29916        // Python: [NOT ]NULL
29917        if e.allow_null.is_none() {
29918            self.write_keyword("NOT ");
29919        }
29920        self.write_keyword("NULL");
29921        Ok(())
29922    }
29923
29924    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
29925        // NULLIF(this, expression)
29926        self.write_keyword("NULLIF");
29927        self.write("(");
29928        self.generate_expression(&e.this)?;
29929        self.write(", ");
29930        self.generate_expression(&e.expression)?;
29931        self.write(")");
29932        Ok(())
29933    }
29934
29935    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
29936        // FORMAT(this, format, culture)
29937        self.write_keyword("FORMAT");
29938        self.write("(");
29939        self.generate_expression(&e.this)?;
29940        self.write(", '");
29941        self.write(&e.format);
29942        self.write("'");
29943        if let Some(culture) = &e.culture {
29944            self.write(", ");
29945            self.generate_expression(culture)?;
29946        }
29947        self.write(")");
29948        Ok(())
29949    }
29950
29951    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
29952        // OBJECT_AGG(key, value)
29953        self.write_keyword("OBJECT_AGG");
29954        self.write("(");
29955        self.generate_expression(&e.this)?;
29956        self.write(", ");
29957        self.generate_expression(&e.expression)?;
29958        self.write(")");
29959        Ok(())
29960    }
29961
29962    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
29963        // Python: Just returns the name
29964        self.generate_expression(&e.this)?;
29965        Ok(())
29966    }
29967
29968    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
29969        // OBJECT_INSERT(obj, key, value, [update_flag])
29970        self.write_keyword("OBJECT_INSERT");
29971        self.write("(");
29972        self.generate_expression(&e.this)?;
29973        if let Some(key) = &e.key {
29974            self.write(", ");
29975            self.generate_expression(key)?;
29976        }
29977        if let Some(value) = &e.value {
29978            self.write(", ");
29979            self.generate_expression(value)?;
29980        }
29981        if let Some(update_flag) = &e.update_flag {
29982            self.write(", ");
29983            self.generate_expression(update_flag)?;
29984        }
29985        self.write(")");
29986        Ok(())
29987    }
29988
29989    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
29990        // OFFSET value [ROW|ROWS]
29991        self.write_keyword("OFFSET");
29992        self.write_space();
29993        self.generate_expression(&e.this)?;
29994        // Output ROWS keyword only for TSQL/Oracle targets
29995        if e.rows == Some(true)
29996            && matches!(
29997                self.config.dialect,
29998                Some(crate::dialects::DialectType::TSQL)
29999                    | Some(crate::dialects::DialectType::Oracle)
30000            )
30001        {
30002            self.write_space();
30003            self.write_keyword("ROWS");
30004        }
30005        Ok(())
30006    }
30007
30008    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
30009        // QUALIFY condition (Snowflake/BigQuery)
30010        self.write_keyword("QUALIFY");
30011        self.write_space();
30012        self.generate_expression(&e.this)?;
30013        Ok(())
30014    }
30015
30016    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
30017        // ON CLUSTER cluster_name
30018        self.write_keyword("ON CLUSTER");
30019        self.write_space();
30020        self.generate_expression(&e.this)?;
30021        Ok(())
30022    }
30023
30024    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
30025        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
30026        self.write_keyword("ON COMMIT");
30027        if e.delete.is_some() {
30028            self.write_keyword(" DELETE ROWS");
30029        } else {
30030            self.write_keyword(" PRESERVE ROWS");
30031        }
30032        Ok(())
30033    }
30034
30035    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
30036        // Python: error/empty/null handling
30037        if let Some(empty) = &e.empty {
30038            self.generate_expression(empty)?;
30039            self.write_keyword(" ON EMPTY");
30040        }
30041        if let Some(error) = &e.error {
30042            if e.empty.is_some() {
30043                self.write_space();
30044            }
30045            self.generate_expression(error)?;
30046            self.write_keyword(" ON ERROR");
30047        }
30048        if let Some(null) = &e.null {
30049            if e.empty.is_some() || e.error.is_some() {
30050                self.write_space();
30051            }
30052            self.generate_expression(null)?;
30053            self.write_keyword(" ON NULL");
30054        }
30055        Ok(())
30056    }
30057
30058    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
30059        // Materialize doesn't support ON CONFLICT - skip entirely
30060        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
30061            return Ok(());
30062        }
30063        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
30064        if e.duplicate.is_some() {
30065            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
30066            self.write_keyword("ON DUPLICATE KEY UPDATE");
30067            for (i, expr) in e.expressions.iter().enumerate() {
30068                if i > 0 {
30069                    self.write(",");
30070                }
30071                self.write_space();
30072                self.generate_expression(expr)?;
30073            }
30074            return Ok(());
30075        } else {
30076            self.write_keyword("ON CONFLICT");
30077        }
30078        if let Some(constraint) = &e.constraint {
30079            self.write_keyword(" ON CONSTRAINT ");
30080            self.generate_expression(constraint)?;
30081        }
30082        if let Some(conflict_keys) = &e.conflict_keys {
30083            // conflict_keys can be a Tuple containing expressions
30084            if let Expression::Tuple(t) = conflict_keys.as_ref() {
30085                self.write("(");
30086                for (i, expr) in t.expressions.iter().enumerate() {
30087                    if i > 0 {
30088                        self.write(", ");
30089                    }
30090                    self.generate_expression(expr)?;
30091                }
30092                self.write(")");
30093            } else {
30094                self.write("(");
30095                self.generate_expression(conflict_keys)?;
30096                self.write(")");
30097            }
30098        }
30099        if let Some(index_predicate) = &e.index_predicate {
30100            self.write_keyword(" WHERE ");
30101            self.generate_expression(index_predicate)?;
30102        }
30103        if let Some(action) = &e.action {
30104            // Check if action is "NOTHING" or an UPDATE set
30105            if let Expression::Identifier(id) = action.as_ref() {
30106                if id.name == "NOTHING" || id.name.to_uppercase() == "NOTHING" {
30107                    self.write_keyword(" DO NOTHING");
30108                } else {
30109                    self.write_keyword(" DO ");
30110                    self.generate_expression(action)?;
30111                }
30112            } else if let Expression::Tuple(t) = action.as_ref() {
30113                // DO UPDATE SET col1 = val1, col2 = val2
30114                self.write_keyword(" DO UPDATE SET ");
30115                for (i, expr) in t.expressions.iter().enumerate() {
30116                    if i > 0 {
30117                        self.write(", ");
30118                    }
30119                    self.generate_expression(expr)?;
30120                }
30121            } else {
30122                self.write_keyword(" DO ");
30123                self.generate_expression(action)?;
30124            }
30125        }
30126        // WHERE clause for the UPDATE action
30127        if let Some(where_) = &e.where_ {
30128            self.write_keyword(" WHERE ");
30129            self.generate_expression(where_)?;
30130        }
30131        Ok(())
30132    }
30133
30134    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
30135        // ON property_value
30136        self.write_keyword("ON");
30137        self.write_space();
30138        self.generate_expression(&e.this)?;
30139        Ok(())
30140    }
30141
30142    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
30143        // Python: this expression (e.g., column opclass)
30144        self.generate_expression(&e.this)?;
30145        self.write_space();
30146        self.generate_expression(&e.expression)?;
30147        Ok(())
30148    }
30149
30150    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
30151        // Python: OPENJSON(this[, path]) [WITH (columns)]
30152        self.write_keyword("OPENJSON");
30153        self.write("(");
30154        self.generate_expression(&e.this)?;
30155        if let Some(path) = &e.path {
30156            self.write(", ");
30157            self.generate_expression(path)?;
30158        }
30159        self.write(")");
30160        if !e.expressions.is_empty() {
30161            self.write_keyword(" WITH");
30162            if self.config.pretty {
30163                self.write(" (\n");
30164                self.indent_level += 2;
30165                for (i, expr) in e.expressions.iter().enumerate() {
30166                    if i > 0 {
30167                        self.write(",\n");
30168                    }
30169                    self.write_indent();
30170                    self.generate_expression(expr)?;
30171                }
30172                self.write("\n");
30173                self.indent_level -= 2;
30174                self.write(")");
30175            } else {
30176                self.write(" (");
30177                for (i, expr) in e.expressions.iter().enumerate() {
30178                    if i > 0 {
30179                        self.write(", ");
30180                    }
30181                    self.generate_expression(expr)?;
30182                }
30183                self.write(")");
30184            }
30185        }
30186        Ok(())
30187    }
30188
30189    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
30190        // Python: this kind [path] [AS JSON]
30191        self.generate_expression(&e.this)?;
30192        self.write_space();
30193        // Use parsed data_type if available, otherwise fall back to kind string
30194        if let Some(ref dt) = e.data_type {
30195            self.generate_data_type(dt)?;
30196        } else if !e.kind.is_empty() {
30197            self.write(&e.kind);
30198        }
30199        if let Some(path) = &e.path {
30200            self.write_space();
30201            self.generate_expression(path)?;
30202        }
30203        if e.as_json.is_some() {
30204            self.write_keyword(" AS JSON");
30205        }
30206        Ok(())
30207    }
30208
30209    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
30210        // this OPERATOR(op) expression
30211        self.generate_expression(&e.this)?;
30212        self.write_space();
30213        if let Some(op) = &e.operator {
30214            self.write_keyword("OPERATOR");
30215            self.write("(");
30216            self.generate_expression(op)?;
30217            self.write(")");
30218        }
30219        // Emit inline comments between OPERATOR() and the RHS
30220        for comment in &e.comments {
30221            self.write_space();
30222            self.write_formatted_comment(comment);
30223        }
30224        self.write_space();
30225        self.generate_expression(&e.expression)?;
30226        Ok(())
30227    }
30228
30229    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
30230        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
30231        self.write_keyword("ORDER BY");
30232        let pretty_clickhouse_single_paren = self.config.pretty
30233            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
30234            && e.expressions.len() == 1
30235            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
30236        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
30237            && e.expressions.len() == 1
30238            && matches!(e.expressions[0].this, Expression::Tuple(_))
30239            && !e.expressions[0].desc
30240            && e.expressions[0].nulls_first.is_none();
30241
30242        if pretty_clickhouse_single_paren {
30243            self.write_space();
30244            if let Expression::Paren(p) = &e.expressions[0].this {
30245                self.write("(");
30246                self.write_newline();
30247                self.indent_level += 1;
30248                self.write_indent();
30249                self.generate_expression(&p.this)?;
30250                self.indent_level -= 1;
30251                self.write_newline();
30252                self.write(")");
30253            }
30254            return Ok(());
30255        }
30256
30257        if clickhouse_single_tuple {
30258            self.write_space();
30259            if let Expression::Tuple(t) = &e.expressions[0].this {
30260                self.write("(");
30261                for (i, expr) in t.expressions.iter().enumerate() {
30262                    if i > 0 {
30263                        self.write(", ");
30264                    }
30265                    self.generate_expression(expr)?;
30266                }
30267                self.write(")");
30268            }
30269            return Ok(());
30270        }
30271
30272        self.write_space();
30273        for (i, ordered) in e.expressions.iter().enumerate() {
30274            if i > 0 {
30275                self.write(", ");
30276            }
30277            self.generate_expression(&ordered.this)?;
30278            if ordered.desc {
30279                self.write_space();
30280                self.write_keyword("DESC");
30281            } else if ordered.explicit_asc {
30282                self.write_space();
30283                self.write_keyword("ASC");
30284            }
30285            if let Some(nulls_first) = ordered.nulls_first {
30286                // In Dremio, NULLS LAST is the default, so skip generating it
30287                let skip_nulls_last =
30288                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
30289                if !skip_nulls_last {
30290                    self.write_space();
30291                    self.write_keyword("NULLS");
30292                    self.write_space();
30293                    if nulls_first {
30294                        self.write_keyword("FIRST");
30295                    } else {
30296                        self.write_keyword("LAST");
30297                    }
30298                }
30299            }
30300        }
30301        Ok(())
30302    }
30303
30304    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
30305        // OUTPUT(model)
30306        self.write_keyword("OUTPUT");
30307        self.write("(");
30308        if self.config.pretty {
30309            self.indent_level += 1;
30310            self.write_newline();
30311            self.write_indent();
30312            self.generate_expression(&e.this)?;
30313            self.indent_level -= 1;
30314            self.write_newline();
30315        } else {
30316            self.generate_expression(&e.this)?;
30317        }
30318        self.write(")");
30319        Ok(())
30320    }
30321
30322    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
30323        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
30324        self.write_keyword("TRUNCATE");
30325        if let Some(this) = &e.this {
30326            self.write_space();
30327            self.generate_expression(this)?;
30328        }
30329        if e.with_count.is_some() {
30330            self.write_keyword(" WITH COUNT");
30331        } else {
30332            self.write_keyword(" WITHOUT COUNT");
30333        }
30334        Ok(())
30335    }
30336
30337    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
30338        // Python: name(expressions)(params)
30339        self.generate_expression(&e.this)?;
30340        self.write("(");
30341        for (i, expr) in e.expressions.iter().enumerate() {
30342            if i > 0 {
30343                self.write(", ");
30344            }
30345            self.generate_expression(expr)?;
30346        }
30347        self.write(")(");
30348        for (i, param) in e.params.iter().enumerate() {
30349            if i > 0 {
30350                self.write(", ");
30351            }
30352            self.generate_expression(param)?;
30353        }
30354        self.write(")");
30355        Ok(())
30356    }
30357
30358    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
30359        // PARSE_DATETIME(format, this) or similar
30360        self.write_keyword("PARSE_DATETIME");
30361        self.write("(");
30362        if let Some(format) = &e.format {
30363            self.write("'");
30364            self.write(format);
30365            self.write("', ");
30366        }
30367        self.generate_expression(&e.this)?;
30368        if let Some(zone) = &e.zone {
30369            self.write(", ");
30370            self.generate_expression(zone)?;
30371        }
30372        self.write(")");
30373        Ok(())
30374    }
30375
30376    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
30377        // PARSE_IP(this, type, permissive)
30378        self.write_keyword("PARSE_IP");
30379        self.write("(");
30380        self.generate_expression(&e.this)?;
30381        if let Some(type_) = &e.type_ {
30382            self.write(", ");
30383            self.generate_expression(type_)?;
30384        }
30385        if let Some(permissive) = &e.permissive {
30386            self.write(", ");
30387            self.generate_expression(permissive)?;
30388        }
30389        self.write(")");
30390        Ok(())
30391    }
30392
30393    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
30394        // PARSE_JSON(this, [expression])
30395        self.write_keyword("PARSE_JSON");
30396        self.write("(");
30397        self.generate_expression(&e.this)?;
30398        if let Some(expression) = &e.expression {
30399            self.write(", ");
30400            self.generate_expression(expression)?;
30401        }
30402        self.write(")");
30403        Ok(())
30404    }
30405
30406    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
30407        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
30408        self.write_keyword("PARSE_TIME");
30409        self.write("(");
30410        self.write(&format!("'{}'", e.format));
30411        self.write(", ");
30412        self.generate_expression(&e.this)?;
30413        self.write(")");
30414        Ok(())
30415    }
30416
30417    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
30418        // PARSE_URL(this, [part_to_extract], [key], [permissive])
30419        self.write_keyword("PARSE_URL");
30420        self.write("(");
30421        self.generate_expression(&e.this)?;
30422        if let Some(part) = &e.part_to_extract {
30423            self.write(", ");
30424            self.generate_expression(part)?;
30425        }
30426        if let Some(key) = &e.key {
30427            self.write(", ");
30428            self.generate_expression(key)?;
30429        }
30430        if let Some(permissive) = &e.permissive {
30431            self.write(", ");
30432            self.generate_expression(permissive)?;
30433        }
30434        self.write(")");
30435        Ok(())
30436    }
30437
30438    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
30439        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
30440        if e.subpartition {
30441            self.write_keyword("SUBPARTITION");
30442        } else {
30443            self.write_keyword("PARTITION");
30444        }
30445        self.write("(");
30446        for (i, expr) in e.expressions.iter().enumerate() {
30447            if i > 0 {
30448                self.write(", ");
30449            }
30450            self.generate_expression(expr)?;
30451        }
30452        self.write(")");
30453        Ok(())
30454    }
30455
30456    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
30457        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
30458        if let Some(this) = &e.this {
30459            if let Some(expression) = &e.expression {
30460                // WITH (MODULUS this, REMAINDER expression)
30461                self.write_keyword("WITH");
30462                self.write(" (");
30463                self.write_keyword("MODULUS");
30464                self.write_space();
30465                self.generate_expression(this)?;
30466                self.write(", ");
30467                self.write_keyword("REMAINDER");
30468                self.write_space();
30469                self.generate_expression(expression)?;
30470                self.write(")");
30471            } else {
30472                // IN (this) - this could be a list
30473                self.write_keyword("IN");
30474                self.write(" (");
30475                self.generate_partition_bound_values(this)?;
30476                self.write(")");
30477            }
30478        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
30479            // FROM (from_expressions) TO (to_expressions)
30480            self.write_keyword("FROM");
30481            self.write(" (");
30482            self.generate_partition_bound_values(from)?;
30483            self.write(") ");
30484            self.write_keyword("TO");
30485            self.write(" (");
30486            self.generate_partition_bound_values(to)?;
30487            self.write(")");
30488        }
30489        Ok(())
30490    }
30491
30492    /// Generate partition bound values - handles Tuple expressions by outputting
30493    /// contents without wrapping parens (since caller provides the parens)
30494    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
30495        if let Expression::Tuple(t) = expr {
30496            for (i, e) in t.expressions.iter().enumerate() {
30497                if i > 0 {
30498                    self.write(", ");
30499                }
30500                self.generate_expression(e)?;
30501            }
30502            Ok(())
30503        } else {
30504            self.generate_expression(expr)
30505        }
30506    }
30507
30508    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
30509        // PARTITION BY LIST (partition_expressions) (create_expressions)
30510        self.write_keyword("PARTITION BY LIST");
30511        if let Some(partition_exprs) = &e.partition_expressions {
30512            self.write(" (");
30513            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
30514            self.generate_doris_partition_expressions(partition_exprs)?;
30515            self.write(")");
30516        }
30517        if let Some(create_exprs) = &e.create_expressions {
30518            self.write(" (");
30519            // Unwrap Tuple for partition definitions
30520            self.generate_doris_partition_definitions(create_exprs)?;
30521            self.write(")");
30522        }
30523        Ok(())
30524    }
30525
30526    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
30527        // PARTITION BY RANGE (partition_expressions) (create_expressions)
30528        self.write_keyword("PARTITION BY RANGE");
30529        if let Some(partition_exprs) = &e.partition_expressions {
30530            self.write(" (");
30531            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
30532            self.generate_doris_partition_expressions(partition_exprs)?;
30533            self.write(")");
30534        }
30535        if let Some(create_exprs) = &e.create_expressions {
30536            self.write(" (");
30537            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
30538            self.generate_doris_partition_definitions(create_exprs)?;
30539            self.write(")");
30540        }
30541        Ok(())
30542    }
30543
30544    /// Generate Doris partition column expressions (unwrap Tuple)
30545    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
30546        if let Expression::Tuple(t) = expr {
30547            for (i, e) in t.expressions.iter().enumerate() {
30548                if i > 0 {
30549                    self.write(", ");
30550                }
30551                self.generate_expression(e)?;
30552            }
30553        } else {
30554            self.generate_expression(expr)?;
30555        }
30556        Ok(())
30557    }
30558
30559    /// Generate Doris partition definitions (comma-separated Partition expressions)
30560    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
30561        match expr {
30562            Expression::Tuple(t) => {
30563                // Multiple partitions, comma-separated
30564                for (i, part) in t.expressions.iter().enumerate() {
30565                    if i > 0 {
30566                        self.write(", ");
30567                    }
30568                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
30569                    if let Expression::Partition(p) = part {
30570                        for (j, inner) in p.expressions.iter().enumerate() {
30571                            if j > 0 {
30572                                self.write(", ");
30573                            }
30574                            self.generate_expression(inner)?;
30575                        }
30576                    } else {
30577                        self.generate_expression(part)?;
30578                    }
30579                }
30580            }
30581            Expression::PartitionByRangePropertyDynamic(_) => {
30582                // Dynamic partition - FROM/TO/INTERVAL
30583                self.generate_expression(expr)?;
30584            }
30585            _ => {
30586                self.generate_expression(expr)?;
30587            }
30588        }
30589        Ok(())
30590    }
30591
30592    fn generate_partition_by_range_property_dynamic(
30593        &mut self,
30594        e: &PartitionByRangePropertyDynamic,
30595    ) -> Result<()> {
30596        if e.use_start_end {
30597            // StarRocks: START ('val') END ('val') EVERY (expr)
30598            if let Some(start) = &e.start {
30599                self.write_keyword("START");
30600                self.write(" (");
30601                self.generate_expression(start)?;
30602                self.write(")");
30603            }
30604            if let Some(end) = &e.end {
30605                self.write_space();
30606                self.write_keyword("END");
30607                self.write(" (");
30608                self.generate_expression(end)?;
30609                self.write(")");
30610            }
30611            if let Some(every) = &e.every {
30612                self.write_space();
30613                self.write_keyword("EVERY");
30614                self.write(" (");
30615                // Use unquoted interval format for StarRocks
30616                self.generate_doris_interval(every)?;
30617                self.write(")");
30618            }
30619        } else {
30620            // Doris: FROM (start) TO (end) INTERVAL n UNIT
30621            if let Some(start) = &e.start {
30622                self.write_keyword("FROM");
30623                self.write(" (");
30624                self.generate_expression(start)?;
30625                self.write(")");
30626            }
30627            if let Some(end) = &e.end {
30628                self.write_space();
30629                self.write_keyword("TO");
30630                self.write(" (");
30631                self.generate_expression(end)?;
30632                self.write(")");
30633            }
30634            if let Some(every) = &e.every {
30635                self.write_space();
30636                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
30637                self.generate_doris_interval(every)?;
30638            }
30639        }
30640        Ok(())
30641    }
30642
30643    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
30644    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
30645        if let Expression::Interval(interval) = expr {
30646            self.write_keyword("INTERVAL");
30647            if let Some(ref value) = interval.this {
30648                self.write_space();
30649                // If the value is a string literal that looks like a number,
30650                // output it without quotes (matching Python sqlglot's
30651                // partitionbyrangepropertydynamic_sql which converts back to number)
30652                match value {
30653                    Expression::Literal(Literal::String(s))
30654                        if s.chars()
30655                            .all(|c| c.is_ascii_digit() || c == '.' || c == '-')
30656                            && !s.is_empty() =>
30657                    {
30658                        self.write(s);
30659                    }
30660                    _ => {
30661                        self.generate_expression(value)?;
30662                    }
30663                }
30664            }
30665            if let Some(ref unit_spec) = interval.unit {
30666                self.write_space();
30667                self.write_interval_unit_spec(unit_spec)?;
30668            }
30669            Ok(())
30670        } else {
30671            self.generate_expression(expr)
30672        }
30673    }
30674
30675    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
30676        // TRUNCATE(expression, this)
30677        self.write_keyword("TRUNCATE");
30678        self.write("(");
30679        self.generate_expression(&e.expression)?;
30680        self.write(", ");
30681        self.generate_expression(&e.this)?;
30682        self.write(")");
30683        Ok(())
30684    }
30685
30686    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
30687        // Doris: PARTITION name VALUES IN (val1, val2)
30688        self.write_keyword("PARTITION");
30689        self.write_space();
30690        self.generate_expression(&e.this)?;
30691        self.write_space();
30692        self.write_keyword("VALUES IN");
30693        self.write(" (");
30694        for (i, expr) in e.expressions.iter().enumerate() {
30695            if i > 0 {
30696                self.write(", ");
30697            }
30698            self.generate_expression(expr)?;
30699        }
30700        self.write(")");
30701        Ok(())
30702    }
30703
30704    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
30705        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
30706        // TSQL ranges have no expressions and just use `this TO expression`
30707        if e.expressions.is_empty() && e.expression.is_some() {
30708            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
30709            self.generate_expression(&e.this)?;
30710            self.write_space();
30711            self.write_keyword("TO");
30712            self.write_space();
30713            self.generate_expression(e.expression.as_ref().unwrap())?;
30714            return Ok(());
30715        }
30716
30717        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
30718        self.write_keyword("PARTITION");
30719        self.write_space();
30720        self.generate_expression(&e.this)?;
30721        self.write_space();
30722
30723        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
30724        if e.expressions.len() == 1 {
30725            // Single value: VALUES LESS THAN (val)
30726            self.write_keyword("VALUES LESS THAN");
30727            self.write(" (");
30728            self.generate_expression(&e.expressions[0])?;
30729            self.write(")");
30730        } else if !e.expressions.is_empty() {
30731            // Multiple values with Tuple: VALUES [(val1), (val2))
30732            self.write_keyword("VALUES");
30733            self.write(" [");
30734            for (i, expr) in e.expressions.iter().enumerate() {
30735                if i > 0 {
30736                    self.write(", ");
30737                }
30738                // If the expr is a Tuple, generate its contents wrapped in parens
30739                if let Expression::Tuple(t) = expr {
30740                    self.write("(");
30741                    for (j, inner) in t.expressions.iter().enumerate() {
30742                        if j > 0 {
30743                            self.write(", ");
30744                        }
30745                        self.generate_expression(inner)?;
30746                    }
30747                    self.write(")");
30748                } else {
30749                    self.write("(");
30750                    self.generate_expression(expr)?;
30751                    self.write(")");
30752                }
30753            }
30754            self.write(")");
30755        }
30756        Ok(())
30757    }
30758
30759    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
30760        // BUCKET(this, expression)
30761        self.write_keyword("BUCKET");
30762        self.write("(");
30763        self.generate_expression(&e.this)?;
30764        self.write(", ");
30765        self.generate_expression(&e.expression)?;
30766        self.write(")");
30767        Ok(())
30768    }
30769
30770    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
30771        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
30772        if matches!(
30773            self.config.dialect,
30774            Some(crate::dialects::DialectType::Teradata)
30775                | Some(crate::dialects::DialectType::ClickHouse)
30776        ) {
30777            self.write_keyword("PARTITION BY");
30778        } else {
30779            self.write_keyword("PARTITIONED BY");
30780        }
30781        self.write_space();
30782        // In pretty mode, always use multiline tuple format for PARTITIONED BY
30783        if self.config.pretty {
30784            if let Expression::Tuple(ref tuple) = *e.this {
30785                self.write("(");
30786                self.write_newline();
30787                self.indent_level += 1;
30788                for (i, expr) in tuple.expressions.iter().enumerate() {
30789                    if i > 0 {
30790                        self.write(",");
30791                        self.write_newline();
30792                    }
30793                    self.write_indent();
30794                    self.generate_expression(expr)?;
30795                }
30796                self.indent_level -= 1;
30797                self.write_newline();
30798                self.write(")");
30799            } else {
30800                self.generate_expression(&e.this)?;
30801            }
30802        } else {
30803            self.generate_expression(&e.this)?;
30804        }
30805        Ok(())
30806    }
30807
30808    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
30809        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
30810        self.write_keyword("PARTITION OF");
30811        self.write_space();
30812        self.generate_expression(&e.this)?;
30813        // Check if expression is a PartitionBoundSpec
30814        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
30815            self.write_space();
30816            self.write_keyword("FOR VALUES");
30817            self.write_space();
30818            self.generate_expression(&e.expression)?;
30819        } else {
30820            self.write_space();
30821            self.write_keyword("DEFAULT");
30822        }
30823        Ok(())
30824    }
30825
30826    fn generate_period_for_system_time_constraint(
30827        &mut self,
30828        e: &PeriodForSystemTimeConstraint,
30829    ) -> Result<()> {
30830        // PERIOD FOR SYSTEM_TIME (this, expression)
30831        self.write_keyword("PERIOD FOR SYSTEM_TIME");
30832        self.write(" (");
30833        self.generate_expression(&e.this)?;
30834        self.write(", ");
30835        self.generate_expression(&e.expression)?;
30836        self.write(")");
30837        Ok(())
30838    }
30839
30840    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
30841        // value AS alias
30842        // The alias can be an identifier or an expression (e.g., string concatenation)
30843        self.generate_expression(&e.this)?;
30844        self.write_space();
30845        self.write_keyword("AS");
30846        self.write_space();
30847        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
30848        if self.config.unpivot_aliases_are_identifiers {
30849            match &e.alias {
30850                Expression::Literal(Literal::String(s)) => {
30851                    // Convert string literal to identifier
30852                    self.generate_identifier(&Identifier::new(s.clone()))?;
30853                }
30854                Expression::Literal(Literal::Number(n)) => {
30855                    // Convert number literal to quoted identifier
30856                    let mut id = Identifier::new(n.clone());
30857                    id.quoted = true;
30858                    self.generate_identifier(&id)?;
30859                }
30860                other => {
30861                    self.generate_expression(other)?;
30862                }
30863            }
30864        } else {
30865            self.generate_expression(&e.alias)?;
30866        }
30867        Ok(())
30868    }
30869
30870    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
30871        // ANY or ANY [expression]
30872        self.write_keyword("ANY");
30873        if let Some(this) = &e.this {
30874            self.write_space();
30875            self.generate_expression(this)?;
30876        }
30877        Ok(())
30878    }
30879
30880    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
30881        // ML.PREDICT(MODEL this, expression, [params_struct])
30882        self.write_keyword("ML.PREDICT");
30883        self.write("(");
30884        self.write_keyword("MODEL");
30885        self.write_space();
30886        self.generate_expression(&e.this)?;
30887        self.write(", ");
30888        self.generate_expression(&e.expression)?;
30889        if let Some(params) = &e.params_struct {
30890            self.write(", ");
30891            self.generate_expression(params)?;
30892        }
30893        self.write(")");
30894        Ok(())
30895    }
30896
30897    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
30898        // PREVIOUS_DAY(this, expression)
30899        self.write_keyword("PREVIOUS_DAY");
30900        self.write("(");
30901        self.generate_expression(&e.this)?;
30902        self.write(", ");
30903        self.generate_expression(&e.expression)?;
30904        self.write(")");
30905        Ok(())
30906    }
30907
30908    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
30909        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
30910        self.write_keyword("PRIMARY KEY");
30911        if let Some(name) = &e.this {
30912            self.write_space();
30913            self.generate_expression(name)?;
30914        }
30915        if !e.expressions.is_empty() {
30916            self.write(" (");
30917            for (i, expr) in e.expressions.iter().enumerate() {
30918                if i > 0 {
30919                    self.write(", ");
30920                }
30921                self.generate_expression(expr)?;
30922            }
30923            self.write(")");
30924        }
30925        if let Some(include) = &e.include {
30926            self.write_space();
30927            self.generate_expression(include)?;
30928        }
30929        if !e.options.is_empty() {
30930            self.write_space();
30931            for (i, opt) in e.options.iter().enumerate() {
30932                if i > 0 {
30933                    self.write_space();
30934                }
30935                self.generate_expression(opt)?;
30936            }
30937        }
30938        Ok(())
30939    }
30940
30941    fn generate_primary_key_column_constraint(
30942        &mut self,
30943        _e: &PrimaryKeyColumnConstraint,
30944    ) -> Result<()> {
30945        // PRIMARY KEY constraint at column level
30946        self.write_keyword("PRIMARY KEY");
30947        Ok(())
30948    }
30949
30950    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
30951        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
30952        self.write_keyword("PATH");
30953        self.write_space();
30954        self.generate_expression(&e.this)?;
30955        Ok(())
30956    }
30957
30958    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
30959        // PROJECTION this (expression)
30960        self.write_keyword("PROJECTION");
30961        self.write_space();
30962        self.generate_expression(&e.this)?;
30963        self.write(" (");
30964        self.generate_expression(&e.expression)?;
30965        self.write(")");
30966        Ok(())
30967    }
30968
30969    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
30970        // Properties list
30971        for (i, prop) in e.expressions.iter().enumerate() {
30972            if i > 0 {
30973                self.write(", ");
30974            }
30975            self.generate_expression(prop)?;
30976        }
30977        Ok(())
30978    }
30979
30980    fn generate_property(&mut self, e: &Property) -> Result<()> {
30981        // name=value
30982        self.generate_expression(&e.this)?;
30983        if let Some(value) = &e.value {
30984            self.write("=");
30985            self.generate_expression(value)?;
30986        }
30987        Ok(())
30988    }
30989
30990    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
30991    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
30992        self.write_keyword("OPTIONS");
30993        self.write(" (");
30994        for (i, opt) in options.iter().enumerate() {
30995            if i > 0 {
30996                self.write(", ");
30997            }
30998            self.generate_option_expression(opt)?;
30999        }
31000        self.write(")");
31001        Ok(())
31002    }
31003
31004    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
31005    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
31006        self.write_keyword("PROPERTIES");
31007        self.write(" (");
31008        for (i, prop) in properties.iter().enumerate() {
31009            if i > 0 {
31010                self.write(", ");
31011            }
31012            self.generate_option_expression(prop)?;
31013        }
31014        self.write(")");
31015        Ok(())
31016    }
31017
31018    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
31019    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
31020        self.write_keyword("ENVIRONMENT");
31021        self.write(" (");
31022        for (i, env_item) in environment.iter().enumerate() {
31023            if i > 0 {
31024                self.write(", ");
31025            }
31026            self.generate_environment_expression(env_item)?;
31027        }
31028        self.write(")");
31029        Ok(())
31030    }
31031
31032    /// Generate an environment expression with spaces around =
31033    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
31034        match expr {
31035            Expression::Eq(eq) => {
31036                // Generate key = value with spaces (Databricks ENVIRONMENT style)
31037                self.generate_expression(&eq.left)?;
31038                self.write(" = ");
31039                self.generate_expression(&eq.right)?;
31040                Ok(())
31041            }
31042            _ => self.generate_expression(expr),
31043        }
31044    }
31045
31046    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
31047    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
31048        self.write_keyword("TBLPROPERTIES");
31049        if self.config.pretty {
31050            self.write(" (");
31051            self.write_newline();
31052            self.indent_level += 1;
31053            for (i, opt) in options.iter().enumerate() {
31054                if i > 0 {
31055                    self.write(",");
31056                    self.write_newline();
31057                }
31058                self.write_indent();
31059                self.generate_option_expression(opt)?;
31060            }
31061            self.indent_level -= 1;
31062            self.write_newline();
31063            self.write(")");
31064        } else {
31065            self.write(" (");
31066            for (i, opt) in options.iter().enumerate() {
31067                if i > 0 {
31068                    self.write(", ");
31069                }
31070                self.generate_option_expression(opt)?;
31071            }
31072            self.write(")");
31073        }
31074        Ok(())
31075    }
31076
31077    /// Generate an option expression without spaces around =
31078    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
31079        match expr {
31080            Expression::Eq(eq) => {
31081                // Generate key=value without spaces
31082                self.generate_expression(&eq.left)?;
31083                self.write("=");
31084                self.generate_expression(&eq.right)?;
31085                Ok(())
31086            }
31087            _ => self.generate_expression(expr),
31088        }
31089    }
31090
31091    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
31092        // Just output the name
31093        self.generate_expression(&e.this)?;
31094        Ok(())
31095    }
31096
31097    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
31098        // PUT source_file @stage [options]
31099        self.write_keyword("PUT");
31100        self.write_space();
31101
31102        // Source file path - preserve original quoting
31103        if e.source_quoted {
31104            self.write("'");
31105            self.write(&e.source);
31106            self.write("'");
31107        } else {
31108            self.write(&e.source);
31109        }
31110
31111        self.write_space();
31112
31113        // Target stage reference - output the string directly (includes @)
31114        if let Expression::Literal(Literal::String(s)) = &e.target {
31115            self.write(s);
31116        } else {
31117            self.generate_expression(&e.target)?;
31118        }
31119
31120        // Optional parameters: KEY=VALUE
31121        for param in &e.params {
31122            self.write_space();
31123            self.write(&param.name);
31124            if let Some(ref value) = param.value {
31125                self.write("=");
31126                self.generate_expression(value)?;
31127            }
31128        }
31129
31130        Ok(())
31131    }
31132
31133    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
31134        // QUANTILE(this, quantile)
31135        self.write_keyword("QUANTILE");
31136        self.write("(");
31137        self.generate_expression(&e.this)?;
31138        if let Some(quantile) = &e.quantile {
31139            self.write(", ");
31140            self.generate_expression(quantile)?;
31141        }
31142        self.write(")");
31143        Ok(())
31144    }
31145
31146    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
31147        // QUERY_BAND = this [UPDATE] [FOR scope]
31148        if matches!(
31149            self.config.dialect,
31150            Some(crate::dialects::DialectType::Teradata)
31151        ) {
31152            self.write_keyword("SET");
31153            self.write_space();
31154        }
31155        self.write_keyword("QUERY_BAND");
31156        self.write(" = ");
31157        self.generate_expression(&e.this)?;
31158        if e.update.is_some() {
31159            self.write_space();
31160            self.write_keyword("UPDATE");
31161        }
31162        if let Some(scope) = &e.scope {
31163            self.write_space();
31164            self.write_keyword("FOR");
31165            self.write_space();
31166            self.generate_expression(scope)?;
31167        }
31168        Ok(())
31169    }
31170
31171    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
31172        // this = expression
31173        self.generate_expression(&e.this)?;
31174        if let Some(expression) = &e.expression {
31175            self.write(" = ");
31176            self.generate_expression(expression)?;
31177        }
31178        Ok(())
31179    }
31180
31181    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
31182        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
31183        self.write_keyword("TRANSFORM");
31184        self.write("(");
31185        for (i, expr) in e.expressions.iter().enumerate() {
31186            if i > 0 {
31187                self.write(", ");
31188            }
31189            self.generate_expression(expr)?;
31190        }
31191        self.write(")");
31192        if let Some(row_format_before) = &e.row_format_before {
31193            self.write_space();
31194            self.generate_expression(row_format_before)?;
31195        }
31196        if let Some(record_writer) = &e.record_writer {
31197            self.write_space();
31198            self.write_keyword("RECORDWRITER");
31199            self.write_space();
31200            self.generate_expression(record_writer)?;
31201        }
31202        if let Some(command_script) = &e.command_script {
31203            self.write_space();
31204            self.write_keyword("USING");
31205            self.write_space();
31206            self.generate_expression(command_script)?;
31207        }
31208        if let Some(schema) = &e.schema {
31209            self.write_space();
31210            self.write_keyword("AS");
31211            self.write_space();
31212            self.generate_expression(schema)?;
31213        }
31214        if let Some(row_format_after) = &e.row_format_after {
31215            self.write_space();
31216            self.generate_expression(row_format_after)?;
31217        }
31218        if let Some(record_reader) = &e.record_reader {
31219            self.write_space();
31220            self.write_keyword("RECORDREADER");
31221            self.write_space();
31222            self.generate_expression(record_reader)?;
31223        }
31224        Ok(())
31225    }
31226
31227    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
31228        // RANDN([seed])
31229        self.write_keyword("RANDN");
31230        self.write("(");
31231        if let Some(this) = &e.this {
31232            self.generate_expression(this)?;
31233        }
31234        self.write(")");
31235        Ok(())
31236    }
31237
31238    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
31239        // RANDSTR(this, [generator])
31240        self.write_keyword("RANDSTR");
31241        self.write("(");
31242        self.generate_expression(&e.this)?;
31243        if let Some(generator) = &e.generator {
31244            self.write(", ");
31245            self.generate_expression(generator)?;
31246        }
31247        self.write(")");
31248        Ok(())
31249    }
31250
31251    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
31252        // RANGE_BUCKET(this, expression)
31253        self.write_keyword("RANGE_BUCKET");
31254        self.write("(");
31255        self.generate_expression(&e.this)?;
31256        self.write(", ");
31257        self.generate_expression(&e.expression)?;
31258        self.write(")");
31259        Ok(())
31260    }
31261
31262    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
31263        // RANGE_N(this BETWEEN expressions [EACH each])
31264        self.write_keyword("RANGE_N");
31265        self.write("(");
31266        self.generate_expression(&e.this)?;
31267        self.write_space();
31268        self.write_keyword("BETWEEN");
31269        self.write_space();
31270        for (i, expr) in e.expressions.iter().enumerate() {
31271            if i > 0 {
31272                self.write(", ");
31273            }
31274            self.generate_expression(expr)?;
31275        }
31276        if let Some(each) = &e.each {
31277            self.write_space();
31278            self.write_keyword("EACH");
31279            self.write_space();
31280            self.generate_expression(each)?;
31281        }
31282        self.write(")");
31283        Ok(())
31284    }
31285
31286    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
31287        // READ_CSV(this, expressions...)
31288        self.write_keyword("READ_CSV");
31289        self.write("(");
31290        self.generate_expression(&e.this)?;
31291        for expr in &e.expressions {
31292            self.write(", ");
31293            self.generate_expression(expr)?;
31294        }
31295        self.write(")");
31296        Ok(())
31297    }
31298
31299    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
31300        // READ_PARQUET(expressions...)
31301        self.write_keyword("READ_PARQUET");
31302        self.write("(");
31303        for (i, expr) in e.expressions.iter().enumerate() {
31304            if i > 0 {
31305                self.write(", ");
31306            }
31307            self.generate_expression(expr)?;
31308        }
31309        self.write(")");
31310        Ok(())
31311    }
31312
31313    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
31314        // SEARCH kind FIRST BY this SET expression [USING using]
31315        // or CYCLE this SET expression [USING using]
31316        if e.kind == "CYCLE" {
31317            self.write_keyword("CYCLE");
31318        } else {
31319            self.write_keyword("SEARCH");
31320            self.write_space();
31321            self.write(&e.kind);
31322            self.write_space();
31323            self.write_keyword("FIRST BY");
31324        }
31325        self.write_space();
31326        self.generate_expression(&e.this)?;
31327        self.write_space();
31328        self.write_keyword("SET");
31329        self.write_space();
31330        self.generate_expression(&e.expression)?;
31331        if let Some(using) = &e.using {
31332            self.write_space();
31333            self.write_keyword("USING");
31334            self.write_space();
31335            self.generate_expression(using)?;
31336        }
31337        Ok(())
31338    }
31339
31340    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
31341        // REDUCE(this, initial, merge, [finish])
31342        self.write_keyword("REDUCE");
31343        self.write("(");
31344        self.generate_expression(&e.this)?;
31345        if let Some(initial) = &e.initial {
31346            self.write(", ");
31347            self.generate_expression(initial)?;
31348        }
31349        if let Some(merge) = &e.merge {
31350            self.write(", ");
31351            self.generate_expression(merge)?;
31352        }
31353        if let Some(finish) = &e.finish {
31354            self.write(", ");
31355            self.generate_expression(finish)?;
31356        }
31357        self.write(")");
31358        Ok(())
31359    }
31360
31361    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
31362        // REFERENCES this (expressions) [options]
31363        self.write_keyword("REFERENCES");
31364        self.write_space();
31365        self.generate_expression(&e.this)?;
31366        if !e.expressions.is_empty() {
31367            self.write(" (");
31368            for (i, expr) in e.expressions.iter().enumerate() {
31369                if i > 0 {
31370                    self.write(", ");
31371                }
31372                self.generate_expression(expr)?;
31373            }
31374            self.write(")");
31375        }
31376        for opt in &e.options {
31377            self.write_space();
31378            self.generate_expression(opt)?;
31379        }
31380        Ok(())
31381    }
31382
31383    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
31384        // REFRESH [kind] this
31385        self.write_keyword("REFRESH");
31386        if !e.kind.is_empty() {
31387            self.write_space();
31388            self.write_keyword(&e.kind);
31389        }
31390        self.write_space();
31391        self.generate_expression(&e.this)?;
31392        Ok(())
31393    }
31394
31395    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
31396        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
31397        self.write_keyword("REFRESH");
31398        self.write_space();
31399        self.write_keyword(&e.method);
31400
31401        if let Some(ref kind) = e.kind {
31402            self.write_space();
31403            self.write_keyword("ON");
31404            self.write_space();
31405            self.write_keyword(kind);
31406
31407            // EVERY n UNIT
31408            if let Some(ref every) = e.every {
31409                self.write_space();
31410                self.write_keyword("EVERY");
31411                self.write_space();
31412                self.generate_expression(every)?;
31413                if let Some(ref unit) = e.unit {
31414                    self.write_space();
31415                    self.write_keyword(unit);
31416                }
31417            }
31418
31419            // STARTS 'datetime'
31420            if let Some(ref starts) = e.starts {
31421                self.write_space();
31422                self.write_keyword("STARTS");
31423                self.write_space();
31424                self.generate_expression(starts)?;
31425            }
31426        }
31427        Ok(())
31428    }
31429
31430    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
31431        // REGEXP_COUNT(this, expression, position, parameters)
31432        self.write_keyword("REGEXP_COUNT");
31433        self.write("(");
31434        self.generate_expression(&e.this)?;
31435        self.write(", ");
31436        self.generate_expression(&e.expression)?;
31437        if let Some(position) = &e.position {
31438            self.write(", ");
31439            self.generate_expression(position)?;
31440        }
31441        if let Some(parameters) = &e.parameters {
31442            self.write(", ");
31443            self.generate_expression(parameters)?;
31444        }
31445        self.write(")");
31446        Ok(())
31447    }
31448
31449    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
31450        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
31451        self.write_keyword("REGEXP_EXTRACT_ALL");
31452        self.write("(");
31453        self.generate_expression(&e.this)?;
31454        self.write(", ");
31455        self.generate_expression(&e.expression)?;
31456        if let Some(group) = &e.group {
31457            self.write(", ");
31458            self.generate_expression(group)?;
31459        }
31460        self.write(")");
31461        Ok(())
31462    }
31463
31464    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
31465        // REGEXP_FULL_MATCH(this, expression)
31466        self.write_keyword("REGEXP_FULL_MATCH");
31467        self.write("(");
31468        self.generate_expression(&e.this)?;
31469        self.write(", ");
31470        self.generate_expression(&e.expression)?;
31471        self.write(")");
31472        Ok(())
31473    }
31474
31475    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
31476        use crate::dialects::DialectType;
31477        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
31478        if matches!(
31479            self.config.dialect,
31480            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
31481        ) && e.flag.is_none()
31482        {
31483            self.generate_expression(&e.this)?;
31484            self.write(" ~* ");
31485            self.generate_expression(&e.expression)?;
31486        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
31487            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
31488            self.write_keyword("REGEXP_LIKE");
31489            self.write("(");
31490            self.generate_expression(&e.this)?;
31491            self.write(", ");
31492            self.generate_expression(&e.expression)?;
31493            self.write(", ");
31494            if let Some(flag) = &e.flag {
31495                self.generate_expression(flag)?;
31496            } else {
31497                self.write("'i'");
31498            }
31499            self.write(")");
31500        } else {
31501            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
31502            self.generate_expression(&e.this)?;
31503            self.write_space();
31504            self.write_keyword("REGEXP_ILIKE");
31505            self.write_space();
31506            self.generate_expression(&e.expression)?;
31507            if let Some(flag) = &e.flag {
31508                self.write(", ");
31509                self.generate_expression(flag)?;
31510            }
31511        }
31512        Ok(())
31513    }
31514
31515    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
31516        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
31517        self.write_keyword("REGEXP_INSTR");
31518        self.write("(");
31519        self.generate_expression(&e.this)?;
31520        self.write(", ");
31521        self.generate_expression(&e.expression)?;
31522        if let Some(position) = &e.position {
31523            self.write(", ");
31524            self.generate_expression(position)?;
31525        }
31526        if let Some(occurrence) = &e.occurrence {
31527            self.write(", ");
31528            self.generate_expression(occurrence)?;
31529        }
31530        if let Some(option) = &e.option {
31531            self.write(", ");
31532            self.generate_expression(option)?;
31533        }
31534        if let Some(parameters) = &e.parameters {
31535            self.write(", ");
31536            self.generate_expression(parameters)?;
31537        }
31538        if let Some(group) = &e.group {
31539            self.write(", ");
31540            self.generate_expression(group)?;
31541        }
31542        self.write(")");
31543        Ok(())
31544    }
31545
31546    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
31547        // REGEXP_SPLIT(this, expression, limit)
31548        self.write_keyword("REGEXP_SPLIT");
31549        self.write("(");
31550        self.generate_expression(&e.this)?;
31551        self.write(", ");
31552        self.generate_expression(&e.expression)?;
31553        if let Some(limit) = &e.limit {
31554            self.write(", ");
31555            self.generate_expression(limit)?;
31556        }
31557        self.write(")");
31558        Ok(())
31559    }
31560
31561    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
31562        // REGR_AVGX(this, expression)
31563        self.write_keyword("REGR_AVGX");
31564        self.write("(");
31565        self.generate_expression(&e.this)?;
31566        self.write(", ");
31567        self.generate_expression(&e.expression)?;
31568        self.write(")");
31569        Ok(())
31570    }
31571
31572    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
31573        // REGR_AVGY(this, expression)
31574        self.write_keyword("REGR_AVGY");
31575        self.write("(");
31576        self.generate_expression(&e.this)?;
31577        self.write(", ");
31578        self.generate_expression(&e.expression)?;
31579        self.write(")");
31580        Ok(())
31581    }
31582
31583    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
31584        // REGR_COUNT(this, expression)
31585        self.write_keyword("REGR_COUNT");
31586        self.write("(");
31587        self.generate_expression(&e.this)?;
31588        self.write(", ");
31589        self.generate_expression(&e.expression)?;
31590        self.write(")");
31591        Ok(())
31592    }
31593
31594    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
31595        // REGR_INTERCEPT(this, expression)
31596        self.write_keyword("REGR_INTERCEPT");
31597        self.write("(");
31598        self.generate_expression(&e.this)?;
31599        self.write(", ");
31600        self.generate_expression(&e.expression)?;
31601        self.write(")");
31602        Ok(())
31603    }
31604
31605    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
31606        // REGR_R2(this, expression)
31607        self.write_keyword("REGR_R2");
31608        self.write("(");
31609        self.generate_expression(&e.this)?;
31610        self.write(", ");
31611        self.generate_expression(&e.expression)?;
31612        self.write(")");
31613        Ok(())
31614    }
31615
31616    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
31617        // REGR_SLOPE(this, expression)
31618        self.write_keyword("REGR_SLOPE");
31619        self.write("(");
31620        self.generate_expression(&e.this)?;
31621        self.write(", ");
31622        self.generate_expression(&e.expression)?;
31623        self.write(")");
31624        Ok(())
31625    }
31626
31627    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
31628        // REGR_SXX(this, expression)
31629        self.write_keyword("REGR_SXX");
31630        self.write("(");
31631        self.generate_expression(&e.this)?;
31632        self.write(", ");
31633        self.generate_expression(&e.expression)?;
31634        self.write(")");
31635        Ok(())
31636    }
31637
31638    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
31639        // REGR_SXY(this, expression)
31640        self.write_keyword("REGR_SXY");
31641        self.write("(");
31642        self.generate_expression(&e.this)?;
31643        self.write(", ");
31644        self.generate_expression(&e.expression)?;
31645        self.write(")");
31646        Ok(())
31647    }
31648
31649    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
31650        // REGR_SYY(this, expression)
31651        self.write_keyword("REGR_SYY");
31652        self.write("(");
31653        self.generate_expression(&e.this)?;
31654        self.write(", ");
31655        self.generate_expression(&e.expression)?;
31656        self.write(")");
31657        Ok(())
31658    }
31659
31660    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
31661        // REGR_VALX(this, expression)
31662        self.write_keyword("REGR_VALX");
31663        self.write("(");
31664        self.generate_expression(&e.this)?;
31665        self.write(", ");
31666        self.generate_expression(&e.expression)?;
31667        self.write(")");
31668        Ok(())
31669    }
31670
31671    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
31672        // REGR_VALY(this, expression)
31673        self.write_keyword("REGR_VALY");
31674        self.write("(");
31675        self.generate_expression(&e.this)?;
31676        self.write(", ");
31677        self.generate_expression(&e.expression)?;
31678        self.write(")");
31679        Ok(())
31680    }
31681
31682    fn generate_remote_with_connection_model_property(
31683        &mut self,
31684        e: &RemoteWithConnectionModelProperty,
31685    ) -> Result<()> {
31686        // REMOTE WITH CONNECTION this
31687        self.write_keyword("REMOTE WITH CONNECTION");
31688        self.write_space();
31689        self.generate_expression(&e.this)?;
31690        Ok(())
31691    }
31692
31693    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
31694        // RENAME COLUMN [IF EXISTS] this TO new_name
31695        self.write_keyword("RENAME COLUMN");
31696        if e.exists {
31697            self.write_space();
31698            self.write_keyword("IF EXISTS");
31699        }
31700        self.write_space();
31701        self.generate_expression(&e.this)?;
31702        if let Some(to) = &e.to {
31703            self.write_space();
31704            self.write_keyword("TO");
31705            self.write_space();
31706            self.generate_expression(to)?;
31707        }
31708        Ok(())
31709    }
31710
31711    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
31712        // REPLACE PARTITION expression [FROM source]
31713        self.write_keyword("REPLACE PARTITION");
31714        self.write_space();
31715        self.generate_expression(&e.expression)?;
31716        if let Some(source) = &e.source {
31717            self.write_space();
31718            self.write_keyword("FROM");
31719            self.write_space();
31720            self.generate_expression(source)?;
31721        }
31722        Ok(())
31723    }
31724
31725    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
31726        // RETURNING expressions [INTO into]
31727        // TSQL and Fabric use OUTPUT instead of RETURNING
31728        let keyword = match self.config.dialect {
31729            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
31730            _ => "RETURNING",
31731        };
31732        self.write_keyword(keyword);
31733        self.write_space();
31734        for (i, expr) in e.expressions.iter().enumerate() {
31735            if i > 0 {
31736                self.write(", ");
31737            }
31738            self.generate_expression(expr)?;
31739        }
31740        if let Some(into) = &e.into {
31741            self.write_space();
31742            self.write_keyword("INTO");
31743            self.write_space();
31744            self.generate_expression(into)?;
31745        }
31746        Ok(())
31747    }
31748
31749    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
31750        // OUTPUT expressions [INTO into_table]
31751        self.write_space();
31752        self.write_keyword("OUTPUT");
31753        self.write_space();
31754        for (i, expr) in output.columns.iter().enumerate() {
31755            if i > 0 {
31756                self.write(", ");
31757            }
31758            self.generate_expression(expr)?;
31759        }
31760        if let Some(into_table) = &output.into_table {
31761            self.write_space();
31762            self.write_keyword("INTO");
31763            self.write_space();
31764            self.generate_expression(into_table)?;
31765        }
31766        Ok(())
31767    }
31768
31769    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
31770        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
31771        self.write_keyword("RETURNS");
31772        if e.is_table.is_some() {
31773            self.write_space();
31774            self.write_keyword("TABLE");
31775        }
31776        if let Some(table) = &e.table {
31777            self.write_space();
31778            self.generate_expression(table)?;
31779        } else if let Some(this) = &e.this {
31780            self.write_space();
31781            self.generate_expression(this)?;
31782        }
31783        if e.null.is_some() {
31784            self.write_space();
31785            self.write_keyword("NULL ON NULL INPUT");
31786        }
31787        Ok(())
31788    }
31789
31790    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
31791        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
31792        self.write_keyword("ROLLBACK");
31793
31794        // TSQL always uses ROLLBACK TRANSACTION
31795        if e.this.is_none()
31796            && matches!(
31797                self.config.dialect,
31798                Some(DialectType::TSQL) | Some(DialectType::Fabric)
31799            )
31800        {
31801            self.write_space();
31802            self.write_keyword("TRANSACTION");
31803        }
31804
31805        // Check if this has TRANSACTION keyword or transaction name
31806        if let Some(this) = &e.this {
31807            // Check if it's just the "TRANSACTION" marker or an actual transaction name
31808            let is_transaction_marker = matches!(
31809                this.as_ref(),
31810                Expression::Identifier(id) if id.name == "TRANSACTION"
31811            );
31812
31813            self.write_space();
31814            self.write_keyword("TRANSACTION");
31815
31816            // If it's a real transaction name, output it
31817            if !is_transaction_marker {
31818                self.write_space();
31819                self.generate_expression(this)?;
31820            }
31821        }
31822
31823        // Output TO savepoint
31824        if let Some(savepoint) = &e.savepoint {
31825            self.write_space();
31826            self.write_keyword("TO");
31827            self.write_space();
31828            self.generate_expression(savepoint)?;
31829        }
31830        Ok(())
31831    }
31832
31833    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
31834        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
31835        if e.expressions.is_empty() {
31836            self.write_keyword("WITH ROLLUP");
31837        } else {
31838            self.write_keyword("ROLLUP");
31839            self.write("(");
31840            for (i, expr) in e.expressions.iter().enumerate() {
31841                if i > 0 {
31842                    self.write(", ");
31843                }
31844                self.generate_expression(expr)?;
31845            }
31846            self.write(")");
31847        }
31848        Ok(())
31849    }
31850
31851    fn generate_row_format_delimited_property(
31852        &mut self,
31853        e: &RowFormatDelimitedProperty,
31854    ) -> Result<()> {
31855        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
31856        self.write_keyword("ROW FORMAT DELIMITED");
31857        if let Some(fields) = &e.fields {
31858            self.write_space();
31859            self.write_keyword("FIELDS TERMINATED BY");
31860            self.write_space();
31861            self.generate_expression(fields)?;
31862        }
31863        if let Some(escaped) = &e.escaped {
31864            self.write_space();
31865            self.write_keyword("ESCAPED BY");
31866            self.write_space();
31867            self.generate_expression(escaped)?;
31868        }
31869        if let Some(items) = &e.collection_items {
31870            self.write_space();
31871            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
31872            self.write_space();
31873            self.generate_expression(items)?;
31874        }
31875        if let Some(keys) = &e.map_keys {
31876            self.write_space();
31877            self.write_keyword("MAP KEYS TERMINATED BY");
31878            self.write_space();
31879            self.generate_expression(keys)?;
31880        }
31881        if let Some(lines) = &e.lines {
31882            self.write_space();
31883            self.write_keyword("LINES TERMINATED BY");
31884            self.write_space();
31885            self.generate_expression(lines)?;
31886        }
31887        if let Some(null) = &e.null {
31888            self.write_space();
31889            self.write_keyword("NULL DEFINED AS");
31890            self.write_space();
31891            self.generate_expression(null)?;
31892        }
31893        if let Some(serde) = &e.serde {
31894            self.write_space();
31895            self.generate_expression(serde)?;
31896        }
31897        Ok(())
31898    }
31899
31900    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
31901        // ROW FORMAT this
31902        self.write_keyword("ROW FORMAT");
31903        self.write_space();
31904        self.generate_expression(&e.this)?;
31905        Ok(())
31906    }
31907
31908    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
31909        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
31910        self.write_keyword("ROW FORMAT SERDE");
31911        self.write_space();
31912        self.generate_expression(&e.this)?;
31913        if let Some(props) = &e.serde_properties {
31914            self.write_space();
31915            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
31916            self.generate_expression(props)?;
31917        }
31918        Ok(())
31919    }
31920
31921    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
31922        // SHA2(this, length)
31923        self.write_keyword("SHA2");
31924        self.write("(");
31925        self.generate_expression(&e.this)?;
31926        if let Some(length) = e.length {
31927            self.write(", ");
31928            self.write(&length.to_string());
31929        }
31930        self.write(")");
31931        Ok(())
31932    }
31933
31934    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
31935        // SHA2_DIGEST(this, length)
31936        self.write_keyword("SHA2_DIGEST");
31937        self.write("(");
31938        self.generate_expression(&e.this)?;
31939        if let Some(length) = e.length {
31940            self.write(", ");
31941            self.write(&length.to_string());
31942        }
31943        self.write(")");
31944        Ok(())
31945    }
31946
31947    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
31948        let name = if matches!(
31949            self.config.dialect,
31950            Some(crate::dialects::DialectType::Spark)
31951                | Some(crate::dialects::DialectType::Databricks)
31952        ) {
31953            "TRY_ADD"
31954        } else {
31955            "SAFE_ADD"
31956        };
31957        self.write_keyword(name);
31958        self.write("(");
31959        self.generate_expression(&e.this)?;
31960        self.write(", ");
31961        self.generate_expression(&e.expression)?;
31962        self.write(")");
31963        Ok(())
31964    }
31965
31966    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
31967        // SAFE_DIVIDE(this, expression)
31968        self.write_keyword("SAFE_DIVIDE");
31969        self.write("(");
31970        self.generate_expression(&e.this)?;
31971        self.write(", ");
31972        self.generate_expression(&e.expression)?;
31973        self.write(")");
31974        Ok(())
31975    }
31976
31977    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
31978        let name = if matches!(
31979            self.config.dialect,
31980            Some(crate::dialects::DialectType::Spark)
31981                | Some(crate::dialects::DialectType::Databricks)
31982        ) {
31983            "TRY_MULTIPLY"
31984        } else {
31985            "SAFE_MULTIPLY"
31986        };
31987        self.write_keyword(name);
31988        self.write("(");
31989        self.generate_expression(&e.this)?;
31990        self.write(", ");
31991        self.generate_expression(&e.expression)?;
31992        self.write(")");
31993        Ok(())
31994    }
31995
31996    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
31997        let name = if matches!(
31998            self.config.dialect,
31999            Some(crate::dialects::DialectType::Spark)
32000                | Some(crate::dialects::DialectType::Databricks)
32001        ) {
32002            "TRY_SUBTRACT"
32003        } else {
32004            "SAFE_SUBTRACT"
32005        };
32006        self.write_keyword(name);
32007        self.write("(");
32008        self.generate_expression(&e.this)?;
32009        self.write(", ");
32010        self.generate_expression(&e.expression)?;
32011        self.write(")");
32012        Ok(())
32013    }
32014
32015    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
32016    /// METHOD (size UNIT) [REPEATABLE (seed)]
32017    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
32018        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
32019        if matches!(sample.method, SampleMethod::Bucket) {
32020            self.write(" (");
32021            self.write_keyword("BUCKET");
32022            self.write_space();
32023            if let Some(ref num) = sample.bucket_numerator {
32024                self.generate_expression(num)?;
32025            }
32026            self.write_space();
32027            self.write_keyword("OUT OF");
32028            self.write_space();
32029            if let Some(ref denom) = sample.bucket_denominator {
32030                self.generate_expression(denom)?;
32031            }
32032            if let Some(ref field) = sample.bucket_field {
32033                self.write_space();
32034                self.write_keyword("ON");
32035                self.write_space();
32036                self.generate_expression(field)?;
32037            }
32038            self.write(")");
32039            return Ok(());
32040        }
32041
32042        // Output method name if explicitly specified, or for dialects that always require it
32043        let is_snowflake = matches!(
32044            self.config.dialect,
32045            Some(crate::dialects::DialectType::Snowflake)
32046        );
32047        let is_postgres = matches!(
32048            self.config.dialect,
32049            Some(crate::dialects::DialectType::PostgreSQL)
32050                | Some(crate::dialects::DialectType::Redshift)
32051        );
32052        // Databricks and Spark don't output method names
32053        let is_databricks = matches!(
32054            self.config.dialect,
32055            Some(crate::dialects::DialectType::Databricks)
32056        );
32057        let is_spark = matches!(
32058            self.config.dialect,
32059            Some(crate::dialects::DialectType::Spark)
32060        );
32061        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
32062        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
32063        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
32064        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
32065            self.write_space();
32066            if !sample.explicit_method && (is_snowflake || force_method) {
32067                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
32068                self.write_keyword("BERNOULLI");
32069            } else {
32070                match sample.method {
32071                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
32072                    SampleMethod::System => self.write_keyword("SYSTEM"),
32073                    SampleMethod::Block => self.write_keyword("BLOCK"),
32074                    SampleMethod::Row => self.write_keyword("ROW"),
32075                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
32076                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
32077                    SampleMethod::Bucket => {} // handled above
32078                }
32079            }
32080        }
32081
32082        // Output size, with or without parentheses depending on dialect
32083        let emit_size_no_parens = !self.config.tablesample_requires_parens;
32084        if emit_size_no_parens {
32085            self.write_space();
32086            match &sample.size {
32087                Expression::Tuple(tuple) => {
32088                    for (i, expr) in tuple.expressions.iter().enumerate() {
32089                        if i > 0 {
32090                            self.write(", ");
32091                        }
32092                        self.generate_expression(expr)?;
32093                    }
32094                }
32095                expr => self.generate_expression(expr)?,
32096            }
32097        } else {
32098            self.write(" (");
32099            self.generate_expression(&sample.size)?;
32100        }
32101
32102        // Determine unit
32103        let is_rows_method = matches!(
32104            sample.method,
32105            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
32106        );
32107        let is_percent = matches!(
32108            sample.method,
32109            SampleMethod::Percent
32110                | SampleMethod::System
32111                | SampleMethod::Bernoulli
32112                | SampleMethod::Block
32113        );
32114
32115        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
32116        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
32117        // For Databricks and Spark, always output PERCENT for percentage samples.
32118        let is_presto = matches!(
32119            self.config.dialect,
32120            Some(crate::dialects::DialectType::Presto)
32121                | Some(crate::dialects::DialectType::Trino)
32122                | Some(crate::dialects::DialectType::Athena)
32123        );
32124        let should_output_unit = if is_databricks || is_spark {
32125            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
32126            is_percent || is_rows_method || sample.unit_after_size
32127        } else if is_snowflake || is_postgres || is_presto {
32128            sample.unit_after_size
32129        } else {
32130            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
32131        };
32132
32133        if should_output_unit {
32134            self.write_space();
32135            if sample.is_percent {
32136                self.write_keyword("PERCENT");
32137            } else if is_rows_method && !sample.unit_after_size {
32138                self.write_keyword("ROWS");
32139            } else if sample.unit_after_size {
32140                match sample.method {
32141                    SampleMethod::Percent
32142                    | SampleMethod::System
32143                    | SampleMethod::Bernoulli
32144                    | SampleMethod::Block => {
32145                        self.write_keyword("PERCENT");
32146                    }
32147                    SampleMethod::Row | SampleMethod::Reservoir => {
32148                        self.write_keyword("ROWS");
32149                    }
32150                    _ => self.write_keyword("ROWS"),
32151                }
32152            } else {
32153                self.write_keyword("PERCENT");
32154            }
32155        }
32156
32157        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32158            if let Some(ref offset) = sample.offset {
32159                self.write_space();
32160                self.write_keyword("OFFSET");
32161                self.write_space();
32162                self.generate_expression(offset)?;
32163            }
32164        }
32165        if !emit_size_no_parens {
32166            self.write(")");
32167        }
32168
32169        Ok(())
32170    }
32171
32172    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
32173        // SAMPLE this (ClickHouse uses SAMPLE BY)
32174        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32175            self.write_keyword("SAMPLE BY");
32176        } else {
32177            self.write_keyword("SAMPLE");
32178        }
32179        self.write_space();
32180        self.generate_expression(&e.this)?;
32181        Ok(())
32182    }
32183
32184    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
32185        // this (expressions...)
32186        if let Some(this) = &e.this {
32187            self.generate_expression(this)?;
32188        }
32189        if !e.expressions.is_empty() {
32190            // Add space before column list if there's a preceding expression
32191            if e.this.is_some() {
32192                self.write_space();
32193            }
32194            self.write("(");
32195            for (i, expr) in e.expressions.iter().enumerate() {
32196                if i > 0 {
32197                    self.write(", ");
32198                }
32199                self.generate_expression(expr)?;
32200            }
32201            self.write(")");
32202        }
32203        Ok(())
32204    }
32205
32206    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
32207        // COMMENT this
32208        self.write_keyword("COMMENT");
32209        self.write_space();
32210        self.generate_expression(&e.this)?;
32211        Ok(())
32212    }
32213
32214    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
32215        // [this::]expression
32216        if let Some(this) = &e.this {
32217            self.generate_expression(this)?;
32218            self.write("::");
32219        }
32220        self.generate_expression(&e.expression)?;
32221        Ok(())
32222    }
32223
32224    fn generate_search(&mut self, e: &Search) -> Result<()> {
32225        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
32226        self.write_keyword("SEARCH");
32227        self.write("(");
32228        self.generate_expression(&e.this)?;
32229        self.write(", ");
32230        self.generate_expression(&e.expression)?;
32231        if let Some(json_scope) = &e.json_scope {
32232            self.write(", ");
32233            self.generate_expression(json_scope)?;
32234        }
32235        if let Some(analyzer) = &e.analyzer {
32236            self.write(", ");
32237            self.generate_expression(analyzer)?;
32238        }
32239        if let Some(analyzer_options) = &e.analyzer_options {
32240            self.write(", ");
32241            self.generate_expression(analyzer_options)?;
32242        }
32243        if let Some(search_mode) = &e.search_mode {
32244            self.write(", ");
32245            self.generate_expression(search_mode)?;
32246        }
32247        self.write(")");
32248        Ok(())
32249    }
32250
32251    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
32252        // SEARCH_IP(this, expression)
32253        self.write_keyword("SEARCH_IP");
32254        self.write("(");
32255        self.generate_expression(&e.this)?;
32256        self.write(", ");
32257        self.generate_expression(&e.expression)?;
32258        self.write(")");
32259        Ok(())
32260    }
32261
32262    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
32263        // SECURITY this
32264        self.write_keyword("SECURITY");
32265        self.write_space();
32266        self.generate_expression(&e.this)?;
32267        Ok(())
32268    }
32269
32270    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
32271        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
32272        self.write("SEMANTIC_VIEW(");
32273
32274        if self.config.pretty {
32275            // Pretty print: each clause on its own line
32276            self.write_newline();
32277            self.indent_level += 1;
32278            self.write_indent();
32279            self.generate_expression(&e.this)?;
32280
32281            if let Some(metrics) = &e.metrics {
32282                self.write_newline();
32283                self.write_indent();
32284                self.write_keyword("METRICS");
32285                self.write_space();
32286                self.generate_semantic_view_tuple(metrics)?;
32287            }
32288            if let Some(dimensions) = &e.dimensions {
32289                self.write_newline();
32290                self.write_indent();
32291                self.write_keyword("DIMENSIONS");
32292                self.write_space();
32293                self.generate_semantic_view_tuple(dimensions)?;
32294            }
32295            if let Some(facts) = &e.facts {
32296                self.write_newline();
32297                self.write_indent();
32298                self.write_keyword("FACTS");
32299                self.write_space();
32300                self.generate_semantic_view_tuple(facts)?;
32301            }
32302            if let Some(where_) = &e.where_ {
32303                self.write_newline();
32304                self.write_indent();
32305                self.write_keyword("WHERE");
32306                self.write_space();
32307                self.generate_expression(where_)?;
32308            }
32309            self.write_newline();
32310            self.indent_level -= 1;
32311            self.write_indent();
32312        } else {
32313            // Compact: all on one line
32314            self.generate_expression(&e.this)?;
32315            if let Some(metrics) = &e.metrics {
32316                self.write_space();
32317                self.write_keyword("METRICS");
32318                self.write_space();
32319                self.generate_semantic_view_tuple(metrics)?;
32320            }
32321            if let Some(dimensions) = &e.dimensions {
32322                self.write_space();
32323                self.write_keyword("DIMENSIONS");
32324                self.write_space();
32325                self.generate_semantic_view_tuple(dimensions)?;
32326            }
32327            if let Some(facts) = &e.facts {
32328                self.write_space();
32329                self.write_keyword("FACTS");
32330                self.write_space();
32331                self.generate_semantic_view_tuple(facts)?;
32332            }
32333            if let Some(where_) = &e.where_ {
32334                self.write_space();
32335                self.write_keyword("WHERE");
32336                self.write_space();
32337                self.generate_expression(where_)?;
32338            }
32339        }
32340        self.write(")");
32341        Ok(())
32342    }
32343
32344    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
32345    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
32346        if let Expression::Tuple(t) = expr {
32347            for (i, e) in t.expressions.iter().enumerate() {
32348                if i > 0 {
32349                    self.write(", ");
32350                }
32351                self.generate_expression(e)?;
32352            }
32353        } else {
32354            self.generate_expression(expr)?;
32355        }
32356        Ok(())
32357    }
32358
32359    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
32360        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
32361        if let Some(start) = &e.start {
32362            self.write_keyword("START WITH");
32363            self.write_space();
32364            self.generate_expression(start)?;
32365        }
32366        if let Some(increment) = &e.increment {
32367            self.write_space();
32368            self.write_keyword("INCREMENT BY");
32369            self.write_space();
32370            self.generate_expression(increment)?;
32371        }
32372        if let Some(minvalue) = &e.minvalue {
32373            self.write_space();
32374            self.write_keyword("MINVALUE");
32375            self.write_space();
32376            self.generate_expression(minvalue)?;
32377        }
32378        if let Some(maxvalue) = &e.maxvalue {
32379            self.write_space();
32380            self.write_keyword("MAXVALUE");
32381            self.write_space();
32382            self.generate_expression(maxvalue)?;
32383        }
32384        if let Some(cache) = &e.cache {
32385            self.write_space();
32386            self.write_keyword("CACHE");
32387            self.write_space();
32388            self.generate_expression(cache)?;
32389        }
32390        if let Some(owned) = &e.owned {
32391            self.write_space();
32392            self.write_keyword("OWNED BY");
32393            self.write_space();
32394            self.generate_expression(owned)?;
32395        }
32396        for opt in &e.options {
32397            self.write_space();
32398            self.generate_expression(opt)?;
32399        }
32400        Ok(())
32401    }
32402
32403    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
32404        // [WITH] SERDEPROPERTIES (expressions)
32405        if e.with_.is_some() {
32406            self.write_keyword("WITH");
32407            self.write_space();
32408        }
32409        self.write_keyword("SERDEPROPERTIES");
32410        self.write(" (");
32411        for (i, expr) in e.expressions.iter().enumerate() {
32412            if i > 0 {
32413                self.write(", ");
32414            }
32415            // Generate key=value without spaces around =
32416            match expr {
32417                Expression::Eq(eq) => {
32418                    self.generate_expression(&eq.left)?;
32419                    self.write("=");
32420                    self.generate_expression(&eq.right)?;
32421                }
32422                _ => self.generate_expression(expr)?,
32423            }
32424        }
32425        self.write(")");
32426        Ok(())
32427    }
32428
32429    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
32430        // @@[kind.]this
32431        self.write("@@");
32432        if let Some(kind) = &e.kind {
32433            self.write(kind);
32434            self.write(".");
32435        }
32436        self.generate_expression(&e.this)?;
32437        Ok(())
32438    }
32439
32440    fn generate_set(&mut self, e: &Set) -> Result<()> {
32441        // SET/UNSET [TAG] expressions
32442        if e.unset.is_some() {
32443            self.write_keyword("UNSET");
32444        } else {
32445            self.write_keyword("SET");
32446        }
32447        if e.tag.is_some() {
32448            self.write_space();
32449            self.write_keyword("TAG");
32450        }
32451        if !e.expressions.is_empty() {
32452            self.write_space();
32453            for (i, expr) in e.expressions.iter().enumerate() {
32454                if i > 0 {
32455                    self.write(", ");
32456                }
32457                self.generate_expression(expr)?;
32458            }
32459        }
32460        Ok(())
32461    }
32462
32463    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
32464        // SET this or SETCONFIG this
32465        self.write_keyword("SET");
32466        self.write_space();
32467        self.generate_expression(&e.this)?;
32468        Ok(())
32469    }
32470
32471    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
32472        // [kind] name = value
32473        if let Some(kind) = &e.kind {
32474            self.write_keyword(kind);
32475            self.write_space();
32476        }
32477        self.generate_expression(&e.name)?;
32478        self.write(" = ");
32479        self.generate_expression(&e.value)?;
32480        Ok(())
32481    }
32482
32483    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
32484        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
32485        if let Some(with_) = &e.with_ {
32486            self.generate_expression(with_)?;
32487            self.write_space();
32488        }
32489        self.generate_expression(&e.this)?;
32490        self.write_space();
32491        // kind should be UNION, INTERSECT, EXCEPT, etc.
32492        if let Some(kind) = &e.kind {
32493            self.write_keyword(kind);
32494        }
32495        if e.distinct {
32496            self.write_space();
32497            self.write_keyword("DISTINCT");
32498        } else {
32499            self.write_space();
32500            self.write_keyword("ALL");
32501        }
32502        if e.by_name.is_some() {
32503            self.write_space();
32504            self.write_keyword("BY NAME");
32505        }
32506        self.write_space();
32507        self.generate_expression(&e.expression)?;
32508        Ok(())
32509    }
32510
32511    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
32512        // SET or MULTISET
32513        if e.multi.is_some() {
32514            self.write_keyword("MULTISET");
32515        } else {
32516            self.write_keyword("SET");
32517        }
32518        Ok(())
32519    }
32520
32521    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
32522        // SETTINGS expressions
32523        self.write_keyword("SETTINGS");
32524        if self.config.pretty && e.expressions.len() > 1 {
32525            // Pretty print: each setting on its own line, indented
32526            self.indent_level += 1;
32527            for (i, expr) in e.expressions.iter().enumerate() {
32528                if i > 0 {
32529                    self.write(",");
32530                }
32531                self.write_newline();
32532                self.write_indent();
32533                self.generate_expression(expr)?;
32534            }
32535            self.indent_level -= 1;
32536        } else {
32537            self.write_space();
32538            for (i, expr) in e.expressions.iter().enumerate() {
32539                if i > 0 {
32540                    self.write(", ");
32541                }
32542                self.generate_expression(expr)?;
32543            }
32544        }
32545        Ok(())
32546    }
32547
32548    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
32549        // SHARING = this
32550        self.write_keyword("SHARING");
32551        if let Some(this) = &e.this {
32552            self.write(" = ");
32553            self.generate_expression(this)?;
32554        }
32555        Ok(())
32556    }
32557
32558    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
32559        // Python array slicing: begin:end:step
32560        if let Some(begin) = &e.this {
32561            self.generate_expression(begin)?;
32562        }
32563        self.write(":");
32564        if let Some(end) = &e.expression {
32565            self.generate_expression(end)?;
32566        }
32567        if let Some(step) = &e.step {
32568            self.write(":");
32569            self.generate_expression(step)?;
32570        }
32571        Ok(())
32572    }
32573
32574    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
32575        // SORT_ARRAY(this, asc)
32576        self.write_keyword("SORT_ARRAY");
32577        self.write("(");
32578        self.generate_expression(&e.this)?;
32579        if let Some(asc) = &e.asc {
32580            self.write(", ");
32581            self.generate_expression(asc)?;
32582        }
32583        self.write(")");
32584        Ok(())
32585    }
32586
32587    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
32588        // SORT BY expressions
32589        self.write_keyword("SORT BY");
32590        self.write_space();
32591        for (i, expr) in e.expressions.iter().enumerate() {
32592            if i > 0 {
32593                self.write(", ");
32594            }
32595            self.generate_ordered(expr)?;
32596        }
32597        Ok(())
32598    }
32599
32600    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
32601        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
32602        if e.compound.is_some() {
32603            self.write_keyword("COMPOUND");
32604            self.write_space();
32605        }
32606        self.write_keyword("SORTKEY");
32607        self.write("(");
32608        // If this is a Tuple, unwrap its contents to avoid double parentheses
32609        if let Expression::Tuple(t) = e.this.as_ref() {
32610            for (i, expr) in t.expressions.iter().enumerate() {
32611                if i > 0 {
32612                    self.write(", ");
32613                }
32614                self.generate_expression(expr)?;
32615            }
32616        } else {
32617            self.generate_expression(&e.this)?;
32618        }
32619        self.write(")");
32620        Ok(())
32621    }
32622
32623    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
32624        // SPLIT_PART(this, delimiter, part_index)
32625        self.write_keyword("SPLIT_PART");
32626        self.write("(");
32627        self.generate_expression(&e.this)?;
32628        if let Some(delimiter) = &e.delimiter {
32629            self.write(", ");
32630            self.generate_expression(delimiter)?;
32631        }
32632        if let Some(part_index) = &e.part_index {
32633            self.write(", ");
32634            self.generate_expression(part_index)?;
32635        }
32636        self.write(")");
32637        Ok(())
32638    }
32639
32640    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
32641        // READS SQL DATA or MODIFIES SQL DATA, etc.
32642        self.generate_expression(&e.this)?;
32643        Ok(())
32644    }
32645
32646    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
32647        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
32648        self.write_keyword("SQL SECURITY");
32649        self.write_space();
32650        self.generate_expression(&e.this)?;
32651        Ok(())
32652    }
32653
32654    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
32655        // ST_DISTANCE(this, expression, [use_spheroid])
32656        self.write_keyword("ST_DISTANCE");
32657        self.write("(");
32658        self.generate_expression(&e.this)?;
32659        self.write(", ");
32660        self.generate_expression(&e.expression)?;
32661        if let Some(use_spheroid) = &e.use_spheroid {
32662            self.write(", ");
32663            self.generate_expression(use_spheroid)?;
32664        }
32665        self.write(")");
32666        Ok(())
32667    }
32668
32669    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
32670        // ST_POINT(this, expression)
32671        self.write_keyword("ST_POINT");
32672        self.write("(");
32673        self.generate_expression(&e.this)?;
32674        self.write(", ");
32675        self.generate_expression(&e.expression)?;
32676        self.write(")");
32677        Ok(())
32678    }
32679
32680    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
32681        // IMMUTABLE, STABLE, VOLATILE
32682        self.generate_expression(&e.this)?;
32683        Ok(())
32684    }
32685
32686    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
32687        // STANDARD_HASH(this, [expression])
32688        self.write_keyword("STANDARD_HASH");
32689        self.write("(");
32690        self.generate_expression(&e.this)?;
32691        if let Some(expression) = &e.expression {
32692            self.write(", ");
32693            self.generate_expression(expression)?;
32694        }
32695        self.write(")");
32696        Ok(())
32697    }
32698
32699    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
32700        // STORED BY this
32701        self.write_keyword("STORED BY");
32702        self.write_space();
32703        self.generate_expression(&e.this)?;
32704        Ok(())
32705    }
32706
32707    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
32708        // STRPOS(this, substr) or STRPOS(this, substr, position)
32709        // Different dialects have different function names
32710        use crate::dialects::DialectType;
32711        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
32712            // Snowflake: CHARINDEX(substr, str[, position])
32713            self.write_keyword("CHARINDEX");
32714            self.write("(");
32715            if let Some(substr) = &e.substr {
32716                self.generate_expression(substr)?;
32717                self.write(", ");
32718            }
32719            self.generate_expression(&e.this)?;
32720            if let Some(position) = &e.position {
32721                self.write(", ");
32722                self.generate_expression(position)?;
32723            }
32724            self.write(")");
32725        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32726            self.write_keyword("POSITION");
32727            self.write("(");
32728            self.generate_expression(&e.this)?;
32729            if let Some(substr) = &e.substr {
32730                self.write(", ");
32731                self.generate_expression(substr)?;
32732            }
32733            if let Some(position) = &e.position {
32734                self.write(", ");
32735                self.generate_expression(position)?;
32736            }
32737            if let Some(occurrence) = &e.occurrence {
32738                self.write(", ");
32739                self.generate_expression(occurrence)?;
32740            }
32741            self.write(")");
32742        } else if matches!(
32743            self.config.dialect,
32744            Some(DialectType::SQLite)
32745                | Some(DialectType::Oracle)
32746                | Some(DialectType::BigQuery)
32747                | Some(DialectType::Teradata)
32748        ) {
32749            self.write_keyword("INSTR");
32750            self.write("(");
32751            self.generate_expression(&e.this)?;
32752            if let Some(substr) = &e.substr {
32753                self.write(", ");
32754                self.generate_expression(substr)?;
32755            }
32756            if let Some(position) = &e.position {
32757                self.write(", ");
32758                self.generate_expression(position)?;
32759            } else if e.occurrence.is_some() {
32760                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
32761                // Default start position is 1
32762                self.write(", 1");
32763            }
32764            if let Some(occurrence) = &e.occurrence {
32765                self.write(", ");
32766                self.generate_expression(occurrence)?;
32767            }
32768            self.write(")");
32769        } else if matches!(
32770            self.config.dialect,
32771            Some(DialectType::MySQL)
32772                | Some(DialectType::SingleStore)
32773                | Some(DialectType::Doris)
32774                | Some(DialectType::StarRocks)
32775                | Some(DialectType::Hive)
32776                | Some(DialectType::Spark)
32777                | Some(DialectType::Databricks)
32778        ) {
32779            // LOCATE(substr, str[, position]) - substr first
32780            self.write_keyword("LOCATE");
32781            self.write("(");
32782            if let Some(substr) = &e.substr {
32783                self.generate_expression(substr)?;
32784                self.write(", ");
32785            }
32786            self.generate_expression(&e.this)?;
32787            if let Some(position) = &e.position {
32788                self.write(", ");
32789                self.generate_expression(position)?;
32790            }
32791            self.write(")");
32792        } else if matches!(self.config.dialect, Some(DialectType::TSQL)) {
32793            // CHARINDEX(substr, str[, position])
32794            self.write_keyword("CHARINDEX");
32795            self.write("(");
32796            if let Some(substr) = &e.substr {
32797                self.generate_expression(substr)?;
32798                self.write(", ");
32799            }
32800            self.generate_expression(&e.this)?;
32801            if let Some(position) = &e.position {
32802                self.write(", ");
32803                self.generate_expression(position)?;
32804            }
32805            self.write(")");
32806        } else if matches!(
32807            self.config.dialect,
32808            Some(DialectType::PostgreSQL)
32809                | Some(DialectType::Materialize)
32810                | Some(DialectType::RisingWave)
32811                | Some(DialectType::Redshift)
32812        ) {
32813            // POSITION(substr IN str) syntax
32814            self.write_keyword("POSITION");
32815            self.write("(");
32816            if let Some(substr) = &e.substr {
32817                self.generate_expression(substr)?;
32818                self.write(" IN ");
32819            }
32820            self.generate_expression(&e.this)?;
32821            self.write(")");
32822        } else {
32823            self.write_keyword("STRPOS");
32824            self.write("(");
32825            self.generate_expression(&e.this)?;
32826            if let Some(substr) = &e.substr {
32827                self.write(", ");
32828                self.generate_expression(substr)?;
32829            }
32830            if let Some(position) = &e.position {
32831                self.write(", ");
32832                self.generate_expression(position)?;
32833            }
32834            if let Some(occurrence) = &e.occurrence {
32835                self.write(", ");
32836                self.generate_expression(occurrence)?;
32837            }
32838            self.write(")");
32839        }
32840        Ok(())
32841    }
32842
32843    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
32844        match self.config.dialect {
32845            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
32846                // TO_DATE(this, java_format)
32847                self.write_keyword("TO_DATE");
32848                self.write("(");
32849                self.generate_expression(&e.this)?;
32850                if let Some(format) = &e.format {
32851                    self.write(", '");
32852                    self.write(&Self::strftime_to_java_format(format));
32853                    self.write("'");
32854                }
32855                self.write(")");
32856            }
32857            Some(DialectType::DuckDB) => {
32858                // CAST(STRPTIME(this, format) AS DATE)
32859                self.write_keyword("CAST");
32860                self.write("(");
32861                self.write_keyword("STRPTIME");
32862                self.write("(");
32863                self.generate_expression(&e.this)?;
32864                if let Some(format) = &e.format {
32865                    self.write(", '");
32866                    self.write(format);
32867                    self.write("'");
32868                }
32869                self.write(")");
32870                self.write_keyword(" AS ");
32871                self.write_keyword("DATE");
32872                self.write(")");
32873            }
32874            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
32875                // TO_DATE(this, pg_format)
32876                self.write_keyword("TO_DATE");
32877                self.write("(");
32878                self.generate_expression(&e.this)?;
32879                if let Some(format) = &e.format {
32880                    self.write(", '");
32881                    self.write(&Self::strftime_to_postgres_format(format));
32882                    self.write("'");
32883                }
32884                self.write(")");
32885            }
32886            Some(DialectType::BigQuery) => {
32887                // PARSE_DATE(format, this) - note: format comes first for BigQuery
32888                self.write_keyword("PARSE_DATE");
32889                self.write("(");
32890                if let Some(format) = &e.format {
32891                    self.write("'");
32892                    self.write(format);
32893                    self.write("'");
32894                    self.write(", ");
32895                }
32896                self.generate_expression(&e.this)?;
32897                self.write(")");
32898            }
32899            Some(DialectType::Teradata) => {
32900                // CAST(this AS DATE FORMAT 'teradata_fmt')
32901                self.write_keyword("CAST");
32902                self.write("(");
32903                self.generate_expression(&e.this)?;
32904                self.write_keyword(" AS ");
32905                self.write_keyword("DATE");
32906                if let Some(format) = &e.format {
32907                    self.write_keyword(" FORMAT ");
32908                    self.write("'");
32909                    self.write(&Self::strftime_to_teradata_format(format));
32910                    self.write("'");
32911                }
32912                self.write(")");
32913            }
32914            _ => {
32915                // STR_TO_DATE(this, format) - MySQL default
32916                self.write_keyword("STR_TO_DATE");
32917                self.write("(");
32918                self.generate_expression(&e.this)?;
32919                if let Some(format) = &e.format {
32920                    self.write(", '");
32921                    self.write(format);
32922                    self.write("'");
32923                }
32924                self.write(")");
32925            }
32926        }
32927        Ok(())
32928    }
32929
32930    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
32931    fn strftime_to_teradata_format(fmt: &str) -> String {
32932        let mut result = fmt.to_string();
32933        result = result.replace("%Y", "YYYY");
32934        result = result.replace("%y", "YY");
32935        result = result.replace("%m", "MM");
32936        result = result.replace("%B", "MMMM");
32937        result = result.replace("%b", "MMM");
32938        result = result.replace("%d", "DD");
32939        result = result.replace("%j", "DDD");
32940        result = result.replace("%H", "HH");
32941        result = result.replace("%M", "MI");
32942        result = result.replace("%S", "SS");
32943        result = result.replace("%f", "SSSSSS");
32944        result = result.replace("%A", "EEEE");
32945        result = result.replace("%a", "EEE");
32946        result
32947    }
32948
32949    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
32950    /// Public static version for use by other modules
32951    pub fn strftime_to_java_format_static(fmt: &str) -> String {
32952        Self::strftime_to_java_format(fmt)
32953    }
32954
32955    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
32956    fn strftime_to_java_format(fmt: &str) -> String {
32957        let mut result = fmt.to_string();
32958        // Handle non-padded variants BEFORE their padded counterparts
32959        result = result.replace("%-d", "d");
32960        result = result.replace("%-m", "M");
32961        result = result.replace("%-H", "H");
32962        result = result.replace("%-M", "m");
32963        result = result.replace("%-S", "s");
32964        result = result.replace("%Y", "yyyy");
32965        result = result.replace("%y", "yy");
32966        result = result.replace("%m", "MM");
32967        result = result.replace("%B", "MMMM");
32968        result = result.replace("%b", "MMM");
32969        result = result.replace("%d", "dd");
32970        result = result.replace("%j", "DDD");
32971        result = result.replace("%H", "HH");
32972        result = result.replace("%M", "mm");
32973        result = result.replace("%S", "ss");
32974        result = result.replace("%f", "SSSSSS");
32975        result = result.replace("%A", "EEEE");
32976        result = result.replace("%a", "EEE");
32977        result
32978    }
32979
32980    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
32981    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
32982    fn strftime_to_tsql_format(fmt: &str) -> String {
32983        let mut result = fmt.to_string();
32984        // Handle non-padded variants BEFORE their padded counterparts
32985        result = result.replace("%-d", "d");
32986        result = result.replace("%-m", "M");
32987        result = result.replace("%-H", "H");
32988        result = result.replace("%-M", "m");
32989        result = result.replace("%-S", "s");
32990        result = result.replace("%Y", "yyyy");
32991        result = result.replace("%y", "yy");
32992        result = result.replace("%m", "MM");
32993        result = result.replace("%B", "MMMM");
32994        result = result.replace("%b", "MMM");
32995        result = result.replace("%d", "dd");
32996        result = result.replace("%j", "DDD");
32997        result = result.replace("%H", "HH");
32998        result = result.replace("%M", "mm");
32999        result = result.replace("%S", "ss");
33000        result = result.replace("%f", "ffffff");
33001        result = result.replace("%A", "dddd");
33002        result = result.replace("%a", "ddd");
33003        result
33004    }
33005
33006    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
33007    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
33008    fn decompose_json_path(path: &str) -> Vec<String> {
33009        let mut parts = Vec::new();
33010        // Strip leading $ and optional .
33011        let path = if path.starts_with("$.") {
33012            &path[2..]
33013        } else if path.starts_with('$') {
33014            &path[1..]
33015        } else {
33016            path
33017        };
33018        if path.is_empty() {
33019            return parts;
33020        }
33021        let mut current = String::new();
33022        let chars: Vec<char> = path.chars().collect();
33023        let mut i = 0;
33024        while i < chars.len() {
33025            match chars[i] {
33026                '.' => {
33027                    if !current.is_empty() {
33028                        parts.push(current.clone());
33029                        current.clear();
33030                    }
33031                    i += 1;
33032                }
33033                '[' => {
33034                    if !current.is_empty() {
33035                        parts.push(current.clone());
33036                        current.clear();
33037                    }
33038                    i += 1;
33039                    // Read the content inside brackets
33040                    let mut bracket_content = String::new();
33041                    while i < chars.len() && chars[i] != ']' {
33042                        // Skip quotes inside brackets
33043                        if chars[i] == '"' || chars[i] == '\'' {
33044                            let quote = chars[i];
33045                            i += 1;
33046                            while i < chars.len() && chars[i] != quote {
33047                                bracket_content.push(chars[i]);
33048                                i += 1;
33049                            }
33050                            if i < chars.len() {
33051                                i += 1;
33052                            } // skip closing quote
33053                        } else {
33054                            bracket_content.push(chars[i]);
33055                            i += 1;
33056                        }
33057                    }
33058                    if i < chars.len() {
33059                        i += 1;
33060                    } // skip ]
33061                      // Skip wildcard [*] - don't add as a part
33062                    if bracket_content != "*" {
33063                        parts.push(bracket_content);
33064                    }
33065                }
33066                _ => {
33067                    current.push(chars[i]);
33068                    i += 1;
33069                }
33070            }
33071        }
33072        if !current.is_empty() {
33073            parts.push(current);
33074        }
33075        parts
33076    }
33077
33078    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
33079    fn strftime_to_postgres_format(fmt: &str) -> String {
33080        let mut result = fmt.to_string();
33081        // Handle non-padded variants BEFORE their padded counterparts
33082        result = result.replace("%-d", "FMDD");
33083        result = result.replace("%-m", "FMMM");
33084        result = result.replace("%-H", "FMHH24");
33085        result = result.replace("%-M", "FMMI");
33086        result = result.replace("%-S", "FMSS");
33087        result = result.replace("%Y", "YYYY");
33088        result = result.replace("%y", "YY");
33089        result = result.replace("%m", "MM");
33090        result = result.replace("%B", "Month");
33091        result = result.replace("%b", "Mon");
33092        result = result.replace("%d", "DD");
33093        result = result.replace("%j", "DDD");
33094        result = result.replace("%H", "HH24");
33095        result = result.replace("%M", "MI");
33096        result = result.replace("%S", "SS");
33097        result = result.replace("%f", "US");
33098        result = result.replace("%A", "Day");
33099        result = result.replace("%a", "Dy");
33100        result
33101    }
33102
33103    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
33104    fn strftime_to_snowflake_format(fmt: &str) -> String {
33105        let mut result = fmt.to_string();
33106        // Handle %-d (non-padded day) before %d (padded day)
33107        result = result.replace("%-d", "dd");
33108        result = result.replace("%-m", "mm"); // non-padded month
33109        result = result.replace("%Y", "yyyy");
33110        result = result.replace("%y", "yy");
33111        result = result.replace("%m", "mm");
33112        result = result.replace("%d", "DD");
33113        result = result.replace("%H", "hh24");
33114        result = result.replace("%M", "mi");
33115        result = result.replace("%S", "ss");
33116        result = result.replace("%f", "ff");
33117        result
33118    }
33119
33120    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
33121        // STR_TO_MAP(this, pair_delim, key_value_delim)
33122        self.write_keyword("STR_TO_MAP");
33123        self.write("(");
33124        self.generate_expression(&e.this)?;
33125        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
33126        let needs_defaults = matches!(
33127            self.config.dialect,
33128            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
33129        );
33130        if let Some(pair_delim) = &e.pair_delim {
33131            self.write(", ");
33132            self.generate_expression(pair_delim)?;
33133        } else if needs_defaults {
33134            self.write(", ','");
33135        }
33136        if let Some(key_value_delim) = &e.key_value_delim {
33137            self.write(", ");
33138            self.generate_expression(key_value_delim)?;
33139        } else if needs_defaults {
33140            self.write(", ':'");
33141        }
33142        self.write(")");
33143        Ok(())
33144    }
33145
33146    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
33147        // Detect format style: strftime (starts with %) vs Snowflake/Java
33148        let is_strftime = e.format.contains('%');
33149        // Helper: get strftime format from whatever style is stored
33150        let to_strftime = |f: &str| -> String {
33151            if is_strftime {
33152                f.to_string()
33153            } else {
33154                Self::snowflake_format_to_strftime(f)
33155            }
33156        };
33157        // Helper: get Java format
33158        let to_java = |f: &str| -> String {
33159            if is_strftime {
33160                Self::strftime_to_java_format(f)
33161            } else {
33162                Self::snowflake_format_to_spark(f)
33163            }
33164        };
33165        // Helper: get PG format
33166        let to_pg = |f: &str| -> String {
33167            if is_strftime {
33168                Self::strftime_to_postgres_format(f)
33169            } else {
33170                Self::convert_strptime_to_postgres_format(f)
33171            }
33172        };
33173
33174        match self.config.dialect {
33175            Some(DialectType::Exasol) => {
33176                self.write_keyword("TO_DATE");
33177                self.write("(");
33178                self.generate_expression(&e.this)?;
33179                self.write(", '");
33180                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
33181                self.write("'");
33182                self.write(")");
33183            }
33184            Some(DialectType::BigQuery) => {
33185                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
33186                let fmt = to_strftime(&e.format);
33187                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
33188                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
33189                self.write_keyword("PARSE_TIMESTAMP");
33190                self.write("('");
33191                self.write(&fmt);
33192                self.write("', ");
33193                self.generate_expression(&e.this)?;
33194                self.write(")");
33195            }
33196            Some(DialectType::Hive) => {
33197                // Hive: CAST(x AS TIMESTAMP) for simple date formats
33198                // Check both the raw format and the converted format (in case it's already Java)
33199                let java_fmt = to_java(&e.format);
33200                if java_fmt == "yyyy-MM-dd HH:mm:ss"
33201                    || java_fmt == "yyyy-MM-dd"
33202                    || e.format == "yyyy-MM-dd HH:mm:ss"
33203                    || e.format == "yyyy-MM-dd"
33204                {
33205                    self.write_keyword("CAST");
33206                    self.write("(");
33207                    self.generate_expression(&e.this)?;
33208                    self.write(" ");
33209                    self.write_keyword("AS TIMESTAMP");
33210                    self.write(")");
33211                } else {
33212                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
33213                    self.write_keyword("CAST");
33214                    self.write("(");
33215                    self.write_keyword("FROM_UNIXTIME");
33216                    self.write("(");
33217                    self.write_keyword("UNIX_TIMESTAMP");
33218                    self.write("(");
33219                    self.generate_expression(&e.this)?;
33220                    self.write(", '");
33221                    self.write(&java_fmt);
33222                    self.write("')");
33223                    self.write(") ");
33224                    self.write_keyword("AS TIMESTAMP");
33225                    self.write(")");
33226                }
33227            }
33228            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
33229                // Spark: TO_TIMESTAMP(value, java_format)
33230                let java_fmt = to_java(&e.format);
33231                self.write_keyword("TO_TIMESTAMP");
33232                self.write("(");
33233                self.generate_expression(&e.this)?;
33234                self.write(", '");
33235                self.write(&java_fmt);
33236                self.write("')");
33237            }
33238            Some(DialectType::MySQL) => {
33239                // MySQL: STR_TO_DATE(value, format)
33240                let mut fmt = to_strftime(&e.format);
33241                // MySQL uses %e for non-padded day, %T for %H:%M:%S
33242                fmt = fmt.replace("%-d", "%e");
33243                fmt = fmt.replace("%-m", "%c");
33244                fmt = fmt.replace("%H:%M:%S", "%T");
33245                self.write_keyword("STR_TO_DATE");
33246                self.write("(");
33247                self.generate_expression(&e.this)?;
33248                self.write(", '");
33249                self.write(&fmt);
33250                self.write("')");
33251            }
33252            Some(DialectType::Drill) => {
33253                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
33254                let java_fmt = to_java(&e.format);
33255                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
33256                let java_fmt = java_fmt.replace('T', "''T''");
33257                self.write_keyword("TO_TIMESTAMP");
33258                self.write("(");
33259                self.generate_expression(&e.this)?;
33260                self.write(", '");
33261                self.write(&java_fmt);
33262                self.write("')");
33263            }
33264            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
33265                // Presto: DATE_PARSE(value, strftime_format)
33266                let mut fmt = to_strftime(&e.format);
33267                // Presto uses %e for non-padded day, %T for %H:%M:%S
33268                fmt = fmt.replace("%-d", "%e");
33269                fmt = fmt.replace("%-m", "%c");
33270                fmt = fmt.replace("%H:%M:%S", "%T");
33271                self.write_keyword("DATE_PARSE");
33272                self.write("(");
33273                self.generate_expression(&e.this)?;
33274                self.write(", '");
33275                self.write(&fmt);
33276                self.write("')");
33277            }
33278            Some(DialectType::DuckDB) => {
33279                // DuckDB: STRPTIME(value, strftime_format)
33280                let fmt = to_strftime(&e.format);
33281                self.write_keyword("STRPTIME");
33282                self.write("(");
33283                self.generate_expression(&e.this)?;
33284                self.write(", '");
33285                self.write(&fmt);
33286                self.write("')");
33287            }
33288            Some(DialectType::PostgreSQL)
33289            | Some(DialectType::Redshift)
33290            | Some(DialectType::Materialize) => {
33291                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
33292                let pg_fmt = to_pg(&e.format);
33293                self.write_keyword("TO_TIMESTAMP");
33294                self.write("(");
33295                self.generate_expression(&e.this)?;
33296                self.write(", '");
33297                self.write(&pg_fmt);
33298                self.write("')");
33299            }
33300            Some(DialectType::Oracle) => {
33301                // Oracle: TO_TIMESTAMP(value, pg_format)
33302                let pg_fmt = to_pg(&e.format);
33303                self.write_keyword("TO_TIMESTAMP");
33304                self.write("(");
33305                self.generate_expression(&e.this)?;
33306                self.write(", '");
33307                self.write(&pg_fmt);
33308                self.write("')");
33309            }
33310            Some(DialectType::Snowflake) => {
33311                // Snowflake: TO_TIMESTAMP(value, format) - native format
33312                self.write_keyword("TO_TIMESTAMP");
33313                self.write("(");
33314                self.generate_expression(&e.this)?;
33315                self.write(", '");
33316                self.write(&e.format);
33317                self.write("')");
33318            }
33319            _ => {
33320                // Default: STR_TO_TIME(this, format)
33321                self.write_keyword("STR_TO_TIME");
33322                self.write("(");
33323                self.generate_expression(&e.this)?;
33324                self.write(", '");
33325                self.write(&e.format);
33326                self.write("'");
33327                self.write(")");
33328            }
33329        }
33330        Ok(())
33331    }
33332
33333    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
33334    fn snowflake_format_to_strftime(format: &str) -> String {
33335        let mut result = String::new();
33336        let chars: Vec<char> = format.chars().collect();
33337        let mut i = 0;
33338        while i < chars.len() {
33339            let remaining = &format[i..];
33340            if remaining.starts_with("yyyy") {
33341                result.push_str("%Y");
33342                i += 4;
33343            } else if remaining.starts_with("yy") {
33344                result.push_str("%y");
33345                i += 2;
33346            } else if remaining.starts_with("mmmm") {
33347                result.push_str("%B"); // full month name
33348                i += 4;
33349            } else if remaining.starts_with("mon") {
33350                result.push_str("%b"); // abbreviated month
33351                i += 3;
33352            } else if remaining.starts_with("mm") {
33353                result.push_str("%m");
33354                i += 2;
33355            } else if remaining.starts_with("DD") {
33356                result.push_str("%d");
33357                i += 2;
33358            } else if remaining.starts_with("dy") {
33359                result.push_str("%a"); // abbreviated day name
33360                i += 2;
33361            } else if remaining.starts_with("hh24") {
33362                result.push_str("%H");
33363                i += 4;
33364            } else if remaining.starts_with("hh12") {
33365                result.push_str("%I");
33366                i += 4;
33367            } else if remaining.starts_with("hh") {
33368                result.push_str("%H");
33369                i += 2;
33370            } else if remaining.starts_with("mi") {
33371                result.push_str("%M");
33372                i += 2;
33373            } else if remaining.starts_with("ss") {
33374                result.push_str("%S");
33375                i += 2;
33376            } else if remaining.starts_with("ff") {
33377                // Fractional seconds
33378                result.push_str("%f");
33379                i += 2;
33380                // Skip digits after ff (ff3, ff6, ff9)
33381                while i < chars.len() && chars[i].is_ascii_digit() {
33382                    i += 1;
33383                }
33384            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
33385                result.push_str("%p");
33386                i += 2;
33387            } else if remaining.starts_with("tz") {
33388                result.push_str("%Z");
33389                i += 2;
33390            } else {
33391                result.push(chars[i]);
33392                i += 1;
33393            }
33394        }
33395        result
33396    }
33397
33398    /// Convert Snowflake normalized format to Spark format (Java-style)
33399    fn snowflake_format_to_spark(format: &str) -> String {
33400        let mut result = String::new();
33401        let chars: Vec<char> = format.chars().collect();
33402        let mut i = 0;
33403        while i < chars.len() {
33404            let remaining = &format[i..];
33405            if remaining.starts_with("yyyy") {
33406                result.push_str("yyyy");
33407                i += 4;
33408            } else if remaining.starts_with("yy") {
33409                result.push_str("yy");
33410                i += 2;
33411            } else if remaining.starts_with("mmmm") {
33412                result.push_str("MMMM"); // full month name
33413                i += 4;
33414            } else if remaining.starts_with("mon") {
33415                result.push_str("MMM"); // abbreviated month
33416                i += 3;
33417            } else if remaining.starts_with("mm") {
33418                result.push_str("MM");
33419                i += 2;
33420            } else if remaining.starts_with("DD") {
33421                result.push_str("dd");
33422                i += 2;
33423            } else if remaining.starts_with("dy") {
33424                result.push_str("EEE"); // abbreviated day name
33425                i += 2;
33426            } else if remaining.starts_with("hh24") {
33427                result.push_str("HH");
33428                i += 4;
33429            } else if remaining.starts_with("hh12") {
33430                result.push_str("hh");
33431                i += 4;
33432            } else if remaining.starts_with("hh") {
33433                result.push_str("HH");
33434                i += 2;
33435            } else if remaining.starts_with("mi") {
33436                result.push_str("mm");
33437                i += 2;
33438            } else if remaining.starts_with("ss") {
33439                result.push_str("ss");
33440                i += 2;
33441            } else if remaining.starts_with("ff") {
33442                result.push_str("SSS"); // milliseconds
33443                i += 2;
33444                // Skip digits after ff
33445                while i < chars.len() && chars[i].is_ascii_digit() {
33446                    i += 1;
33447                }
33448            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
33449                result.push_str("a");
33450                i += 2;
33451            } else if remaining.starts_with("tz") {
33452                result.push_str("z");
33453                i += 2;
33454            } else {
33455                result.push(chars[i]);
33456                i += 1;
33457            }
33458        }
33459        result
33460    }
33461
33462    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
33463        match self.config.dialect {
33464            Some(DialectType::DuckDB) => {
33465                // DuckDB: EPOCH(STRPTIME(value, format))
33466                self.write_keyword("EPOCH");
33467                self.write("(");
33468                self.write_keyword("STRPTIME");
33469                self.write("(");
33470                if let Some(this) = &e.this {
33471                    self.generate_expression(this)?;
33472                }
33473                if let Some(format) = &e.format {
33474                    self.write(", '");
33475                    self.write(format);
33476                    self.write("'");
33477                }
33478                self.write("))");
33479            }
33480            Some(DialectType::Hive) => {
33481                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
33482                self.write_keyword("UNIX_TIMESTAMP");
33483                self.write("(");
33484                if let Some(this) = &e.this {
33485                    self.generate_expression(this)?;
33486                }
33487                if let Some(format) = &e.format {
33488                    let java_fmt = Self::strftime_to_java_format(format);
33489                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
33490                        self.write(", '");
33491                        self.write(&java_fmt);
33492                        self.write("'");
33493                    }
33494                }
33495                self.write(")");
33496            }
33497            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
33498                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
33499                self.write_keyword("UNIX_TIMESTAMP");
33500                self.write("(");
33501                if let Some(this) = &e.this {
33502                    self.generate_expression(this)?;
33503                }
33504                if let Some(format) = &e.format {
33505                    self.write(", '");
33506                    self.write(format);
33507                    self.write("'");
33508                }
33509                self.write(")");
33510            }
33511            Some(DialectType::Presto) | Some(DialectType::Trino) => {
33512                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
33513                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
33514                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
33515                let java_fmt = Self::strftime_to_java_format(c_fmt);
33516                self.write_keyword("TO_UNIXTIME");
33517                self.write("(");
33518                self.write_keyword("COALESCE");
33519                self.write("(");
33520                self.write_keyword("TRY");
33521                self.write("(");
33522                self.write_keyword("DATE_PARSE");
33523                self.write("(");
33524                self.write_keyword("CAST");
33525                self.write("(");
33526                if let Some(this) = &e.this {
33527                    self.generate_expression(this)?;
33528                }
33529                self.write(" ");
33530                self.write_keyword("AS VARCHAR");
33531                self.write("), '");
33532                self.write(c_fmt);
33533                self.write("')), ");
33534                self.write_keyword("PARSE_DATETIME");
33535                self.write("(");
33536                self.write_keyword("DATE_FORMAT");
33537                self.write("(");
33538                self.write_keyword("CAST");
33539                self.write("(");
33540                if let Some(this) = &e.this {
33541                    self.generate_expression(this)?;
33542                }
33543                self.write(" ");
33544                self.write_keyword("AS TIMESTAMP");
33545                self.write("), '");
33546                self.write(c_fmt);
33547                self.write("'), '");
33548                self.write(&java_fmt);
33549                self.write("')))");
33550            }
33551            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
33552                // Spark: UNIX_TIMESTAMP(value, java_format)
33553                self.write_keyword("UNIX_TIMESTAMP");
33554                self.write("(");
33555                if let Some(this) = &e.this {
33556                    self.generate_expression(this)?;
33557                }
33558                if let Some(format) = &e.format {
33559                    let java_fmt = Self::strftime_to_java_format(format);
33560                    self.write(", '");
33561                    self.write(&java_fmt);
33562                    self.write("'");
33563                }
33564                self.write(")");
33565            }
33566            _ => {
33567                // Default: STR_TO_UNIX(this, format)
33568                self.write_keyword("STR_TO_UNIX");
33569                self.write("(");
33570                if let Some(this) = &e.this {
33571                    self.generate_expression(this)?;
33572                }
33573                if let Some(format) = &e.format {
33574                    self.write(", '");
33575                    self.write(format);
33576                    self.write("'");
33577                }
33578                self.write(")");
33579            }
33580        }
33581        Ok(())
33582    }
33583
33584    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
33585        // STRING_TO_ARRAY(this, delimiter, null_string)
33586        self.write_keyword("STRING_TO_ARRAY");
33587        self.write("(");
33588        self.generate_expression(&e.this)?;
33589        if let Some(expression) = &e.expression {
33590            self.write(", ");
33591            self.generate_expression(expression)?;
33592        }
33593        if let Some(null_val) = &e.null {
33594            self.write(", ");
33595            self.generate_expression(null_val)?;
33596        }
33597        self.write(")");
33598        Ok(())
33599    }
33600
33601    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
33602        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
33603            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
33604            self.write_keyword("OBJECT_CONSTRUCT");
33605            self.write("(");
33606            for (i, (name, expr)) in e.fields.iter().enumerate() {
33607                if i > 0 {
33608                    self.write(", ");
33609                }
33610                if let Some(name) = name {
33611                    self.write("'");
33612                    self.write(name);
33613                    self.write("'");
33614                    self.write(", ");
33615                } else {
33616                    self.write("'_");
33617                    self.write(&i.to_string());
33618                    self.write("'");
33619                    self.write(", ");
33620                }
33621                self.generate_expression(expr)?;
33622            }
33623            self.write(")");
33624        } else if self.config.struct_curly_brace_notation {
33625            // DuckDB-style: {'key': value, ...}
33626            self.write("{");
33627            for (i, (name, expr)) in e.fields.iter().enumerate() {
33628                if i > 0 {
33629                    self.write(", ");
33630                }
33631                if let Some(name) = name {
33632                    // Quote the key as a string literal
33633                    self.write("'");
33634                    self.write(name);
33635                    self.write("'");
33636                    self.write(": ");
33637                } else {
33638                    // Unnamed field: use positional key
33639                    self.write("'_");
33640                    self.write(&i.to_string());
33641                    self.write("'");
33642                    self.write(": ");
33643                }
33644                self.generate_expression(expr)?;
33645            }
33646            self.write("}");
33647        } else {
33648            // Standard SQL struct notation
33649            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
33650            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
33651            let value_as_name = matches!(
33652                self.config.dialect,
33653                Some(DialectType::BigQuery)
33654                    | Some(DialectType::Spark)
33655                    | Some(DialectType::Databricks)
33656                    | Some(DialectType::Hive)
33657            );
33658            self.write_keyword("STRUCT");
33659            self.write("(");
33660            for (i, (name, expr)) in e.fields.iter().enumerate() {
33661                if i > 0 {
33662                    self.write(", ");
33663                }
33664                if let Some(name) = name {
33665                    if value_as_name {
33666                        // STRUCT(value AS name)
33667                        self.generate_expression(expr)?;
33668                        self.write_space();
33669                        self.write_keyword("AS");
33670                        self.write_space();
33671                        // Quote name if it contains spaces or special chars
33672                        let needs_quoting = name.contains(' ') || name.contains('-');
33673                        if needs_quoting {
33674                            if matches!(
33675                                self.config.dialect,
33676                                Some(DialectType::Spark)
33677                                    | Some(DialectType::Databricks)
33678                                    | Some(DialectType::Hive)
33679                            ) {
33680                                self.write("`");
33681                                self.write(name);
33682                                self.write("`");
33683                            } else {
33684                                self.write(name);
33685                            }
33686                        } else {
33687                            self.write(name);
33688                        }
33689                    } else {
33690                        // STRUCT(name AS value)
33691                        self.write(name);
33692                        self.write_space();
33693                        self.write_keyword("AS");
33694                        self.write_space();
33695                        self.generate_expression(expr)?;
33696                    }
33697                } else {
33698                    self.generate_expression(expr)?;
33699                }
33700            }
33701            self.write(")");
33702        }
33703        Ok(())
33704    }
33705
33706    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
33707        // STUFF(this, start, length, expression)
33708        self.write_keyword("STUFF");
33709        self.write("(");
33710        self.generate_expression(&e.this)?;
33711        if let Some(start) = &e.start {
33712            self.write(", ");
33713            self.generate_expression(start)?;
33714        }
33715        if let Some(length) = e.length {
33716            self.write(", ");
33717            self.write(&length.to_string());
33718        }
33719        self.write(", ");
33720        self.generate_expression(&e.expression)?;
33721        self.write(")");
33722        Ok(())
33723    }
33724
33725    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
33726        // SUBSTRING_INDEX(this, delimiter, count)
33727        self.write_keyword("SUBSTRING_INDEX");
33728        self.write("(");
33729        self.generate_expression(&e.this)?;
33730        if let Some(delimiter) = &e.delimiter {
33731            self.write(", ");
33732            self.generate_expression(delimiter)?;
33733        }
33734        if let Some(count) = &e.count {
33735            self.write(", ");
33736            self.generate_expression(count)?;
33737        }
33738        self.write(")");
33739        Ok(())
33740    }
33741
33742    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
33743        // SUMMARIZE [TABLE] this
33744        self.write_keyword("SUMMARIZE");
33745        if e.table.is_some() {
33746            self.write_space();
33747            self.write_keyword("TABLE");
33748        }
33749        self.write_space();
33750        self.generate_expression(&e.this)?;
33751        Ok(())
33752    }
33753
33754    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
33755        // SYSTIMESTAMP
33756        self.write_keyword("SYSTIMESTAMP");
33757        Ok(())
33758    }
33759
33760    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
33761        // alias (columns...)
33762        if let Some(this) = &e.this {
33763            self.generate_expression(this)?;
33764        }
33765        if !e.columns.is_empty() {
33766            self.write("(");
33767            for (i, col) in e.columns.iter().enumerate() {
33768                if i > 0 {
33769                    self.write(", ");
33770                }
33771                self.generate_expression(col)?;
33772            }
33773            self.write(")");
33774        }
33775        Ok(())
33776    }
33777
33778    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
33779        // TABLE(this) [AS alias]
33780        self.write_keyword("TABLE");
33781        self.write("(");
33782        self.generate_expression(&e.this)?;
33783        self.write(")");
33784        if let Some(alias) = &e.alias {
33785            self.write_space();
33786            self.write_keyword("AS");
33787            self.write_space();
33788            self.write(alias);
33789        }
33790        Ok(())
33791    }
33792
33793    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
33794        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
33795        self.write_keyword("ROWS FROM");
33796        self.write(" (");
33797        for (i, expr) in e.expressions.iter().enumerate() {
33798            if i > 0 {
33799                self.write(", ");
33800            }
33801            // Each expression is either:
33802            // - A plain function (no alias)
33803            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
33804            match expr {
33805                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
33806                    // First element is the function, second is the TableAlias
33807                    self.generate_expression(&tuple.expressions[0])?;
33808                    self.write_space();
33809                    self.write_keyword("AS");
33810                    self.write_space();
33811                    self.generate_expression(&tuple.expressions[1])?;
33812                }
33813                _ => {
33814                    self.generate_expression(expr)?;
33815                }
33816            }
33817        }
33818        self.write(")");
33819        if e.ordinality {
33820            self.write_space();
33821            self.write_keyword("WITH ORDINALITY");
33822        }
33823        if let Some(alias) = &e.alias {
33824            self.write_space();
33825            self.write_keyword("AS");
33826            self.write_space();
33827            self.generate_expression(alias)?;
33828        }
33829        Ok(())
33830    }
33831
33832    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
33833        use crate::dialects::DialectType;
33834
33835        // New wrapper pattern: expression + Sample struct
33836        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
33837            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
33838            if self.config.alias_post_tablesample {
33839                // Handle Subquery with alias and Alias wrapper
33840                if let Expression::Subquery(ref s) = **this {
33841                    if let Some(ref alias) = s.alias {
33842                        // Create a clone without alias for output
33843                        let mut subquery_no_alias = (**s).clone();
33844                        subquery_no_alias.alias = None;
33845                        subquery_no_alias.column_aliases = Vec::new();
33846                        self.generate_expression(&Expression::Subquery(Box::new(
33847                            subquery_no_alias,
33848                        )))?;
33849                        self.write_space();
33850                        self.write_keyword("TABLESAMPLE");
33851                        self.generate_sample_body(sample)?;
33852                        if let Some(ref seed) = sample.seed {
33853                            self.write_space();
33854                            let use_seed = sample.use_seed_keyword
33855                                && !matches!(
33856                                    self.config.dialect,
33857                                    Some(crate::dialects::DialectType::Databricks)
33858                                        | Some(crate::dialects::DialectType::Spark)
33859                                );
33860                            if use_seed {
33861                                self.write_keyword("SEED");
33862                            } else {
33863                                self.write_keyword("REPEATABLE");
33864                            }
33865                            self.write(" (");
33866                            self.generate_expression(seed)?;
33867                            self.write(")");
33868                        }
33869                        self.write_space();
33870                        self.write_keyword("AS");
33871                        self.write_space();
33872                        self.generate_identifier(alias)?;
33873                        return Ok(());
33874                    }
33875                } else if let Expression::Alias(ref a) = **this {
33876                    // Output the base expression without alias
33877                    self.generate_expression(&a.this)?;
33878                    self.write_space();
33879                    self.write_keyword("TABLESAMPLE");
33880                    self.generate_sample_body(sample)?;
33881                    if let Some(ref seed) = sample.seed {
33882                        self.write_space();
33883                        let use_seed = sample.use_seed_keyword
33884                            && !matches!(
33885                                self.config.dialect,
33886                                Some(crate::dialects::DialectType::Databricks)
33887                                    | Some(crate::dialects::DialectType::Spark)
33888                            );
33889                        if use_seed {
33890                            self.write_keyword("SEED");
33891                        } else {
33892                            self.write_keyword("REPEATABLE");
33893                        }
33894                        self.write(" (");
33895                        self.generate_expression(seed)?;
33896                        self.write(")");
33897                    }
33898                    // Output alias after TABLESAMPLE
33899                    self.write_space();
33900                    self.write_keyword("AS");
33901                    self.write_space();
33902                    self.generate_identifier(&a.alias)?;
33903                    return Ok(());
33904                }
33905            }
33906            // Default: generate wrapped expression first, then TABLESAMPLE
33907            self.generate_expression(this)?;
33908            self.write_space();
33909            self.write_keyword("TABLESAMPLE");
33910            self.generate_sample_body(sample)?;
33911            // Seed for table-level sample
33912            if let Some(ref seed) = sample.seed {
33913                self.write_space();
33914                // Databricks uses REPEATABLE, not SEED
33915                let use_seed = sample.use_seed_keyword
33916                    && !matches!(
33917                        self.config.dialect,
33918                        Some(crate::dialects::DialectType::Databricks)
33919                            | Some(crate::dialects::DialectType::Spark)
33920                    );
33921                if use_seed {
33922                    self.write_keyword("SEED");
33923                } else {
33924                    self.write_keyword("REPEATABLE");
33925                }
33926                self.write(" (");
33927                self.generate_expression(seed)?;
33928                self.write(")");
33929            }
33930            return Ok(());
33931        }
33932
33933        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
33934        self.write_keyword("TABLESAMPLE");
33935        if let Some(method) = &e.method {
33936            self.write_space();
33937            self.write_keyword(method);
33938        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
33939            // Snowflake defaults to BERNOULLI when no method is specified
33940            self.write_space();
33941            self.write_keyword("BERNOULLI");
33942        }
33943        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
33944            self.write_space();
33945            self.write_keyword("BUCKET");
33946            self.write_space();
33947            self.generate_expression(numerator)?;
33948            self.write_space();
33949            self.write_keyword("OUT OF");
33950            self.write_space();
33951            self.generate_expression(denominator)?;
33952            if let Some(field) = &e.bucket_field {
33953                self.write_space();
33954                self.write_keyword("ON");
33955                self.write_space();
33956                self.generate_expression(field)?;
33957            }
33958        } else if !e.expressions.is_empty() {
33959            self.write(" (");
33960            for (i, expr) in e.expressions.iter().enumerate() {
33961                if i > 0 {
33962                    self.write(", ");
33963                }
33964                self.generate_expression(expr)?;
33965            }
33966            self.write(")");
33967        } else if let Some(percent) = &e.percent {
33968            self.write(" (");
33969            self.generate_expression(percent)?;
33970            self.write_space();
33971            self.write_keyword("PERCENT");
33972            self.write(")");
33973        }
33974        Ok(())
33975    }
33976
33977    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
33978        // [prefix]this[postfix]
33979        if let Some(prefix) = &e.prefix {
33980            self.generate_expression(prefix)?;
33981        }
33982        if let Some(this) = &e.this {
33983            self.generate_expression(this)?;
33984        }
33985        if let Some(postfix) = &e.postfix {
33986            self.generate_expression(postfix)?;
33987        }
33988        Ok(())
33989    }
33990
33991    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
33992        // TAG (expressions)
33993        self.write_keyword("TAG");
33994        self.write(" (");
33995        for (i, expr) in e.expressions.iter().enumerate() {
33996            if i > 0 {
33997                self.write(", ");
33998            }
33999            self.generate_expression(expr)?;
34000        }
34001        self.write(")");
34002        Ok(())
34003    }
34004
34005    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
34006        // TEMPORARY or TEMP or [this] TEMPORARY
34007        if let Some(this) = &e.this {
34008            self.generate_expression(this)?;
34009            self.write_space();
34010        }
34011        self.write_keyword("TEMPORARY");
34012        Ok(())
34013    }
34014
34015    /// Generate a Time function expression
34016    /// For most dialects: TIME('value')
34017    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
34018        // Standard: TIME(value)
34019        self.write_keyword("TIME");
34020        self.write("(");
34021        self.generate_expression(&e.this)?;
34022        self.write(")");
34023        Ok(())
34024    }
34025
34026    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
34027        // TIME_ADD(this, expression, unit)
34028        self.write_keyword("TIME_ADD");
34029        self.write("(");
34030        self.generate_expression(&e.this)?;
34031        self.write(", ");
34032        self.generate_expression(&e.expression)?;
34033        if let Some(unit) = &e.unit {
34034            self.write(", ");
34035            self.write_keyword(unit);
34036        }
34037        self.write(")");
34038        Ok(())
34039    }
34040
34041    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
34042        // TIME_DIFF(this, expression, unit)
34043        self.write_keyword("TIME_DIFF");
34044        self.write("(");
34045        self.generate_expression(&e.this)?;
34046        self.write(", ");
34047        self.generate_expression(&e.expression)?;
34048        if let Some(unit) = &e.unit {
34049            self.write(", ");
34050            self.write_keyword(unit);
34051        }
34052        self.write(")");
34053        Ok(())
34054    }
34055
34056    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
34057        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
34058        self.write_keyword("TIME_FROM_PARTS");
34059        self.write("(");
34060        let mut first = true;
34061        if let Some(hour) = &e.hour {
34062            self.generate_expression(hour)?;
34063            first = false;
34064        }
34065        if let Some(minute) = &e.min {
34066            if !first {
34067                self.write(", ");
34068            }
34069            self.generate_expression(minute)?;
34070            first = false;
34071        }
34072        if let Some(second) = &e.sec {
34073            if !first {
34074                self.write(", ");
34075            }
34076            self.generate_expression(second)?;
34077            first = false;
34078        }
34079        if let Some(ns) = &e.nano {
34080            if !first {
34081                self.write(", ");
34082            }
34083            self.generate_expression(ns)?;
34084        }
34085        self.write(")");
34086        Ok(())
34087    }
34088
34089    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
34090        // TIME_SLICE(this, expression, unit)
34091        self.write_keyword("TIME_SLICE");
34092        self.write("(");
34093        self.generate_expression(&e.this)?;
34094        self.write(", ");
34095        self.generate_expression(&e.expression)?;
34096        self.write(", ");
34097        self.write_keyword(&e.unit);
34098        self.write(")");
34099        Ok(())
34100    }
34101
34102    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
34103        // TIME_STR_TO_TIME(this)
34104        self.write_keyword("TIME_STR_TO_TIME");
34105        self.write("(");
34106        self.generate_expression(&e.this)?;
34107        self.write(")");
34108        Ok(())
34109    }
34110
34111    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
34112        // TIME_SUB(this, expression, unit)
34113        self.write_keyword("TIME_SUB");
34114        self.write("(");
34115        self.generate_expression(&e.this)?;
34116        self.write(", ");
34117        self.generate_expression(&e.expression)?;
34118        if let Some(unit) = &e.unit {
34119            self.write(", ");
34120            self.write_keyword(unit);
34121        }
34122        self.write(")");
34123        Ok(())
34124    }
34125
34126    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
34127        match self.config.dialect {
34128            Some(DialectType::Exasol) => {
34129                // Exasol uses TO_CHAR with Exasol-specific format
34130                self.write_keyword("TO_CHAR");
34131                self.write("(");
34132                self.generate_expression(&e.this)?;
34133                self.write(", '");
34134                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
34135                self.write("'");
34136                self.write(")");
34137            }
34138            Some(DialectType::PostgreSQL)
34139            | Some(DialectType::Redshift)
34140            | Some(DialectType::Materialize) => {
34141                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
34142                self.write_keyword("TO_CHAR");
34143                self.write("(");
34144                self.generate_expression(&e.this)?;
34145                self.write(", '");
34146                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
34147                self.write("'");
34148                self.write(")");
34149            }
34150            Some(DialectType::Oracle) => {
34151                // Oracle uses TO_CHAR with PG-like format
34152                self.write_keyword("TO_CHAR");
34153                self.write("(");
34154                self.generate_expression(&e.this)?;
34155                self.write(", '");
34156                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
34157                self.write("'");
34158                self.write(")");
34159            }
34160            Some(DialectType::Drill) => {
34161                // Drill: TO_CHAR with Java format
34162                self.write_keyword("TO_CHAR");
34163                self.write("(");
34164                self.generate_expression(&e.this)?;
34165                self.write(", '");
34166                self.write(&Self::strftime_to_java_format(&e.format));
34167                self.write("'");
34168                self.write(")");
34169            }
34170            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
34171                // TSQL: FORMAT(value, format) with .NET-style format
34172                self.write_keyword("FORMAT");
34173                self.write("(");
34174                self.generate_expression(&e.this)?;
34175                self.write(", '");
34176                self.write(&Self::strftime_to_tsql_format(&e.format));
34177                self.write("'");
34178                self.write(")");
34179            }
34180            Some(DialectType::DuckDB) => {
34181                // DuckDB: STRFTIME(value, format) - keeps C format
34182                self.write_keyword("STRFTIME");
34183                self.write("(");
34184                self.generate_expression(&e.this)?;
34185                self.write(", '");
34186                self.write(&e.format);
34187                self.write("'");
34188                self.write(")");
34189            }
34190            Some(DialectType::BigQuery) => {
34191                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
34192                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
34193                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
34194                self.write_keyword("FORMAT_DATE");
34195                self.write("('");
34196                self.write(&fmt);
34197                self.write("', ");
34198                self.generate_expression(&e.this)?;
34199                self.write(")");
34200            }
34201            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
34202                // Hive/Spark: DATE_FORMAT(value, java_format)
34203                self.write_keyword("DATE_FORMAT");
34204                self.write("(");
34205                self.generate_expression(&e.this)?;
34206                self.write(", '");
34207                self.write(&Self::strftime_to_java_format(&e.format));
34208                self.write("'");
34209                self.write(")");
34210            }
34211            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
34212                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
34213                self.write_keyword("DATE_FORMAT");
34214                self.write("(");
34215                self.generate_expression(&e.this)?;
34216                self.write(", '");
34217                self.write(&e.format);
34218                self.write("'");
34219                self.write(")");
34220            }
34221            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
34222                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
34223                self.write_keyword("DATE_FORMAT");
34224                self.write("(");
34225                self.generate_expression(&e.this)?;
34226                self.write(", '");
34227                self.write(&e.format);
34228                self.write("'");
34229                self.write(")");
34230            }
34231            _ => {
34232                // Default: TIME_TO_STR(this, format)
34233                self.write_keyword("TIME_TO_STR");
34234                self.write("(");
34235                self.generate_expression(&e.this)?;
34236                self.write(", '");
34237                self.write(&e.format);
34238                self.write("'");
34239                self.write(")");
34240            }
34241        }
34242        Ok(())
34243    }
34244
34245    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
34246        match self.config.dialect {
34247            Some(DialectType::DuckDB) => {
34248                // DuckDB: EPOCH(x)
34249                self.write_keyword("EPOCH");
34250                self.write("(");
34251                self.generate_expression(&e.this)?;
34252                self.write(")");
34253            }
34254            Some(DialectType::Hive)
34255            | Some(DialectType::Spark)
34256            | Some(DialectType::Databricks)
34257            | Some(DialectType::Doris)
34258            | Some(DialectType::StarRocks)
34259            | Some(DialectType::Drill) => {
34260                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
34261                self.write_keyword("UNIX_TIMESTAMP");
34262                self.write("(");
34263                self.generate_expression(&e.this)?;
34264                self.write(")");
34265            }
34266            Some(DialectType::Presto) | Some(DialectType::Trino) => {
34267                // Presto: TO_UNIXTIME(x)
34268                self.write_keyword("TO_UNIXTIME");
34269                self.write("(");
34270                self.generate_expression(&e.this)?;
34271                self.write(")");
34272            }
34273            _ => {
34274                // Default: TIME_TO_UNIX(x)
34275                self.write_keyword("TIME_TO_UNIX");
34276                self.write("(");
34277                self.generate_expression(&e.this)?;
34278                self.write(")");
34279            }
34280        }
34281        Ok(())
34282    }
34283
34284    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
34285        match self.config.dialect {
34286            Some(DialectType::Hive) => {
34287                // Hive: TO_DATE(x)
34288                self.write_keyword("TO_DATE");
34289                self.write("(");
34290                self.generate_expression(&e.this)?;
34291                self.write(")");
34292            }
34293            _ => {
34294                // Default: TIME_STR_TO_DATE(x)
34295                self.write_keyword("TIME_STR_TO_DATE");
34296                self.write("(");
34297                self.generate_expression(&e.this)?;
34298                self.write(")");
34299            }
34300        }
34301        Ok(())
34302    }
34303
34304    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
34305        // TIME_TRUNC(this, unit)
34306        self.write_keyword("TIME_TRUNC");
34307        self.write("(");
34308        self.generate_expression(&e.this)?;
34309        self.write(", ");
34310        self.write_keyword(&e.unit);
34311        self.write(")");
34312        Ok(())
34313    }
34314
34315    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
34316        // Just output the unit name
34317        if let Some(unit) = &e.unit {
34318            self.write_keyword(unit);
34319        }
34320        Ok(())
34321    }
34322
34323    /// Generate a Timestamp function expression
34324    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
34325    /// For other dialects: TIMESTAMP('value')
34326    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
34327        use crate::dialects::DialectType;
34328        use crate::expressions::Literal;
34329
34330        match self.config.dialect {
34331            // Exasol uses TO_TIMESTAMP for Timestamp expressions
34332            Some(DialectType::Exasol) => {
34333                self.write_keyword("TO_TIMESTAMP");
34334                self.write("(");
34335                // Extract the string value from the expression if it's a string literal
34336                if let Some(this) = &e.this {
34337                    match this.as_ref() {
34338                        Expression::Literal(Literal::String(s)) => {
34339                            self.write("'");
34340                            self.write(s);
34341                            self.write("'");
34342                        }
34343                        _ => {
34344                            self.generate_expression(this)?;
34345                        }
34346                    }
34347                }
34348                self.write(")");
34349            }
34350            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
34351            _ => {
34352                self.write_keyword("TIMESTAMP");
34353                self.write("(");
34354                if let Some(this) = &e.this {
34355                    self.generate_expression(this)?;
34356                }
34357                if let Some(zone) = &e.zone {
34358                    self.write(", ");
34359                    self.generate_expression(zone)?;
34360                }
34361                self.write(")");
34362            }
34363        }
34364        Ok(())
34365    }
34366
34367    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
34368        // TIMESTAMP_ADD(this, expression, unit)
34369        self.write_keyword("TIMESTAMP_ADD");
34370        self.write("(");
34371        self.generate_expression(&e.this)?;
34372        self.write(", ");
34373        self.generate_expression(&e.expression)?;
34374        if let Some(unit) = &e.unit {
34375            self.write(", ");
34376            self.write_keyword(unit);
34377        }
34378        self.write(")");
34379        Ok(())
34380    }
34381
34382    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
34383        // TIMESTAMP_DIFF(this, expression, unit)
34384        self.write_keyword("TIMESTAMP_DIFF");
34385        self.write("(");
34386        self.generate_expression(&e.this)?;
34387        self.write(", ");
34388        self.generate_expression(&e.expression)?;
34389        if let Some(unit) = &e.unit {
34390            self.write(", ");
34391            self.write_keyword(unit);
34392        }
34393        self.write(")");
34394        Ok(())
34395    }
34396
34397    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
34398        // TIMESTAMP_FROM_PARTS(this, expression)
34399        self.write_keyword("TIMESTAMP_FROM_PARTS");
34400        self.write("(");
34401        if let Some(this) = &e.this {
34402            self.generate_expression(this)?;
34403        }
34404        if let Some(expression) = &e.expression {
34405            self.write(", ");
34406            self.generate_expression(expression)?;
34407        }
34408        if let Some(zone) = &e.zone {
34409            self.write(", ");
34410            self.generate_expression(zone)?;
34411        }
34412        if let Some(milli) = &e.milli {
34413            self.write(", ");
34414            self.generate_expression(milli)?;
34415        }
34416        self.write(")");
34417        Ok(())
34418    }
34419
34420    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
34421        // TIMESTAMP_SUB(this, INTERVAL expression unit)
34422        self.write_keyword("TIMESTAMP_SUB");
34423        self.write("(");
34424        self.generate_expression(&e.this)?;
34425        self.write(", ");
34426        self.write_keyword("INTERVAL");
34427        self.write_space();
34428        self.generate_expression(&e.expression)?;
34429        if let Some(unit) = &e.unit {
34430            self.write_space();
34431            self.write_keyword(unit);
34432        }
34433        self.write(")");
34434        Ok(())
34435    }
34436
34437    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
34438        // TIMESTAMP_TZ_FROM_PARTS(...)
34439        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
34440        self.write("(");
34441        if let Some(zone) = &e.zone {
34442            self.generate_expression(zone)?;
34443        }
34444        self.write(")");
34445        Ok(())
34446    }
34447
34448    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
34449        // TO_BINARY(this, [format])
34450        self.write_keyword("TO_BINARY");
34451        self.write("(");
34452        self.generate_expression(&e.this)?;
34453        if let Some(format) = &e.format {
34454            self.write(", '");
34455            self.write(format);
34456            self.write("'");
34457        }
34458        self.write(")");
34459        Ok(())
34460    }
34461
34462    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
34463        // TO_BOOLEAN(this)
34464        self.write_keyword("TO_BOOLEAN");
34465        self.write("(");
34466        self.generate_expression(&e.this)?;
34467        self.write(")");
34468        Ok(())
34469    }
34470
34471    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
34472        // TO_CHAR(this, [format], [nlsparam])
34473        self.write_keyword("TO_CHAR");
34474        self.write("(");
34475        self.generate_expression(&e.this)?;
34476        if let Some(format) = &e.format {
34477            self.write(", '");
34478            self.write(format);
34479            self.write("'");
34480        }
34481        if let Some(nlsparam) = &e.nlsparam {
34482            self.write(", ");
34483            self.generate_expression(nlsparam)?;
34484        }
34485        self.write(")");
34486        Ok(())
34487    }
34488
34489    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
34490        // TO_DECFLOAT(this, [format])
34491        self.write_keyword("TO_DECFLOAT");
34492        self.write("(");
34493        self.generate_expression(&e.this)?;
34494        if let Some(format) = &e.format {
34495            self.write(", '");
34496            self.write(format);
34497            self.write("'");
34498        }
34499        self.write(")");
34500        Ok(())
34501    }
34502
34503    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
34504        // TO_DOUBLE(this, [format])
34505        self.write_keyword("TO_DOUBLE");
34506        self.write("(");
34507        self.generate_expression(&e.this)?;
34508        if let Some(format) = &e.format {
34509            self.write(", '");
34510            self.write(format);
34511            self.write("'");
34512        }
34513        self.write(")");
34514        Ok(())
34515    }
34516
34517    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
34518        // TO_FILE(this, path)
34519        self.write_keyword("TO_FILE");
34520        self.write("(");
34521        self.generate_expression(&e.this)?;
34522        if let Some(path) = &e.path {
34523            self.write(", ");
34524            self.generate_expression(path)?;
34525        }
34526        self.write(")");
34527        Ok(())
34528    }
34529
34530    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
34531        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
34532        // If safe flag is set, output TRY_TO_NUMBER
34533        let is_safe = e.safe.is_some();
34534        if is_safe {
34535            self.write_keyword("TRY_TO_NUMBER");
34536        } else {
34537            self.write_keyword("TO_NUMBER");
34538        }
34539        self.write("(");
34540        self.generate_expression(&e.this)?;
34541        if let Some(format) = &e.format {
34542            self.write(", ");
34543            self.generate_expression(format)?;
34544        }
34545        if let Some(nlsparam) = &e.nlsparam {
34546            self.write(", ");
34547            self.generate_expression(nlsparam)?;
34548        }
34549        if let Some(precision) = &e.precision {
34550            self.write(", ");
34551            self.generate_expression(precision)?;
34552        }
34553        if let Some(scale) = &e.scale {
34554            self.write(", ");
34555            self.generate_expression(scale)?;
34556        }
34557        self.write(")");
34558        Ok(())
34559    }
34560
34561    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
34562        // TO_TABLE this
34563        self.write_keyword("TO_TABLE");
34564        self.write_space();
34565        self.generate_expression(&e.this)?;
34566        Ok(())
34567    }
34568
34569    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
34570        // Check mark to determine the format
34571        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
34572            Expression::Identifier(id) => id.name.clone(),
34573            Expression::Literal(Literal::String(s)) => s.clone(),
34574            _ => String::new(),
34575        });
34576
34577        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
34578        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
34579        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
34580            matches!(m.as_ref(), Expression::Literal(Literal::String(_)))
34581        });
34582
34583        // For Presto/Trino: always use START TRANSACTION
34584        let use_start_transaction = matches!(
34585            self.config.dialect,
34586            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
34587        );
34588        // For most dialects: strip TRANSACTION keyword
34589        let strip_transaction = matches!(
34590            self.config.dialect,
34591            Some(DialectType::Snowflake)
34592                | Some(DialectType::PostgreSQL)
34593                | Some(DialectType::Redshift)
34594                | Some(DialectType::MySQL)
34595                | Some(DialectType::Hive)
34596                | Some(DialectType::Spark)
34597                | Some(DialectType::Databricks)
34598                | Some(DialectType::DuckDB)
34599                | Some(DialectType::Oracle)
34600                | Some(DialectType::Doris)
34601                | Some(DialectType::StarRocks)
34602                | Some(DialectType::Materialize)
34603                | Some(DialectType::ClickHouse)
34604        );
34605
34606        if is_start || use_start_transaction {
34607            // START TRANSACTION [modes]
34608            self.write_keyword("START TRANSACTION");
34609            if let Some(modes) = &e.modes {
34610                self.write_space();
34611                self.generate_expression(modes)?;
34612            }
34613        } else {
34614            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
34615            self.write_keyword("BEGIN");
34616
34617            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
34618            let is_kind = e.this.as_ref().map_or(false, |t| {
34619                if let Expression::Identifier(id) = t.as_ref() {
34620                    matches!(
34621                        id.name.to_uppercase().as_str(),
34622                        "DEFERRED" | "IMMEDIATE" | "EXCLUSIVE"
34623                    )
34624                } else {
34625                    false
34626                }
34627            });
34628
34629            // Output kind before TRANSACTION keyword
34630            if is_kind {
34631                if let Some(this) = &e.this {
34632                    self.write_space();
34633                    if let Expression::Identifier(id) = this.as_ref() {
34634                        self.write_keyword(&id.name);
34635                    }
34636                }
34637            }
34638
34639            // Output TRANSACTION keyword if it was present and target supports it
34640            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
34641                self.write_space();
34642                self.write_keyword("TRANSACTION");
34643            }
34644
34645            // Output transaction name (not kind)
34646            if !is_kind {
34647                if let Some(this) = &e.this {
34648                    self.write_space();
34649                    self.generate_expression(this)?;
34650                }
34651            }
34652
34653            // Output WITH MARK 'description' for TSQL
34654            if has_with_mark {
34655                self.write_space();
34656                self.write_keyword("WITH MARK");
34657                if let Some(Expression::Literal(Literal::String(desc))) = e.mark.as_deref() {
34658                    if !desc.is_empty() {
34659                        self.write_space();
34660                        self.write(&format!("'{}'", desc));
34661                    }
34662                }
34663            }
34664
34665            // Output modes (isolation levels, etc.)
34666            if let Some(modes) = &e.modes {
34667                self.write_space();
34668                self.generate_expression(modes)?;
34669            }
34670        }
34671        Ok(())
34672    }
34673
34674    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
34675        // TRANSFORM(this, expression)
34676        self.write_keyword("TRANSFORM");
34677        self.write("(");
34678        self.generate_expression(&e.this)?;
34679        self.write(", ");
34680        self.generate_expression(&e.expression)?;
34681        self.write(")");
34682        Ok(())
34683    }
34684
34685    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
34686        // TRANSFORM(expressions)
34687        self.write_keyword("TRANSFORM");
34688        self.write("(");
34689        if self.config.pretty && !e.expressions.is_empty() {
34690            self.indent_level += 1;
34691            for (i, expr) in e.expressions.iter().enumerate() {
34692                if i > 0 {
34693                    self.write(",");
34694                }
34695                self.write_newline();
34696                self.write_indent();
34697                self.generate_expression(expr)?;
34698            }
34699            self.indent_level -= 1;
34700            self.write_newline();
34701            self.write(")");
34702        } else {
34703            for (i, expr) in e.expressions.iter().enumerate() {
34704                if i > 0 {
34705                    self.write(", ");
34706                }
34707                self.generate_expression(expr)?;
34708            }
34709            self.write(")");
34710        }
34711        Ok(())
34712    }
34713
34714    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
34715        use crate::dialects::DialectType;
34716        // TRANSIENT is Snowflake-specific; skip for other dialects
34717        if let Some(this) = &e.this {
34718            self.generate_expression(this)?;
34719            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
34720                self.write_space();
34721            }
34722        }
34723        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
34724            self.write_keyword("TRANSIENT");
34725        }
34726        Ok(())
34727    }
34728
34729    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
34730        // TRANSLATE(this, from_, to)
34731        self.write_keyword("TRANSLATE");
34732        self.write("(");
34733        self.generate_expression(&e.this)?;
34734        if let Some(from) = &e.from_ {
34735            self.write(", ");
34736            self.generate_expression(from)?;
34737        }
34738        if let Some(to) = &e.to {
34739            self.write(", ");
34740            self.generate_expression(to)?;
34741        }
34742        self.write(")");
34743        Ok(())
34744    }
34745
34746    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
34747        // TRANSLATE(this USING expression)
34748        self.write_keyword("TRANSLATE");
34749        self.write("(");
34750        self.generate_expression(&e.this)?;
34751        self.write_space();
34752        self.write_keyword("USING");
34753        self.write_space();
34754        self.generate_expression(&e.expression)?;
34755        if e.with_error.is_some() {
34756            self.write_space();
34757            self.write_keyword("WITH ERROR");
34758        }
34759        self.write(")");
34760        Ok(())
34761    }
34762
34763    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
34764        // TRUNCATE TABLE table1, table2, ...
34765        self.write_keyword("TRUNCATE TABLE");
34766        self.write_space();
34767        for (i, expr) in e.expressions.iter().enumerate() {
34768            if i > 0 {
34769                self.write(", ");
34770            }
34771            self.generate_expression(expr)?;
34772        }
34773        Ok(())
34774    }
34775
34776    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
34777        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
34778        self.write_keyword("TRY_BASE64_DECODE_BINARY");
34779        self.write("(");
34780        self.generate_expression(&e.this)?;
34781        if let Some(alphabet) = &e.alphabet {
34782            self.write(", ");
34783            self.generate_expression(alphabet)?;
34784        }
34785        self.write(")");
34786        Ok(())
34787    }
34788
34789    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
34790        // TRY_BASE64_DECODE_STRING(this, [alphabet])
34791        self.write_keyword("TRY_BASE64_DECODE_STRING");
34792        self.write("(");
34793        self.generate_expression(&e.this)?;
34794        if let Some(alphabet) = &e.alphabet {
34795            self.write(", ");
34796            self.generate_expression(alphabet)?;
34797        }
34798        self.write(")");
34799        Ok(())
34800    }
34801
34802    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
34803        // TRY_TO_DECFLOAT(this, [format])
34804        self.write_keyword("TRY_TO_DECFLOAT");
34805        self.write("(");
34806        self.generate_expression(&e.this)?;
34807        if let Some(format) = &e.format {
34808            self.write(", '");
34809            self.write(format);
34810            self.write("'");
34811        }
34812        self.write(")");
34813        Ok(())
34814    }
34815
34816    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
34817        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
34818        self.write_keyword("TS_OR_DS_ADD");
34819        self.write("(");
34820        self.generate_expression(&e.this)?;
34821        self.write(", ");
34822        self.generate_expression(&e.expression)?;
34823        if let Some(unit) = &e.unit {
34824            self.write(", ");
34825            self.write_keyword(unit);
34826        }
34827        if let Some(return_type) = &e.return_type {
34828            self.write(", ");
34829            self.generate_expression(return_type)?;
34830        }
34831        self.write(")");
34832        Ok(())
34833    }
34834
34835    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
34836        // TS_OR_DS_DIFF(this, expression, [unit])
34837        self.write_keyword("TS_OR_DS_DIFF");
34838        self.write("(");
34839        self.generate_expression(&e.this)?;
34840        self.write(", ");
34841        self.generate_expression(&e.expression)?;
34842        if let Some(unit) = &e.unit {
34843            self.write(", ");
34844            self.write_keyword(unit);
34845        }
34846        self.write(")");
34847        Ok(())
34848    }
34849
34850    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
34851        let default_time_format = "%Y-%m-%d %H:%M:%S";
34852        let default_date_format = "%Y-%m-%d";
34853        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
34854            f != default_time_format && f != default_date_format
34855        });
34856
34857        if has_non_default_format {
34858            // With non-default format: dialect-specific handling
34859            let fmt = e.format.as_ref().unwrap();
34860            match self.config.dialect {
34861                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
34862                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
34863                    // STR_TO_DATE is the MySQL-native form of StrToTime
34864                    let str_to_time = crate::expressions::StrToTime {
34865                        this: Box::new((*e.this).clone()),
34866                        format: fmt.clone(),
34867                        zone: None,
34868                        safe: None,
34869                        target_type: None,
34870                    };
34871                    self.generate_str_to_time(&str_to_time)?;
34872                }
34873                Some(DialectType::Hive)
34874                | Some(DialectType::Spark)
34875                | Some(DialectType::Databricks) => {
34876                    // Hive/Spark: TO_DATE(x, java_fmt)
34877                    self.write_keyword("TO_DATE");
34878                    self.write("(");
34879                    self.generate_expression(&e.this)?;
34880                    self.write(", '");
34881                    self.write(&Self::strftime_to_java_format(fmt));
34882                    self.write("')");
34883                }
34884                Some(DialectType::Snowflake) => {
34885                    // Snowflake: TO_DATE(x, snowflake_fmt)
34886                    self.write_keyword("TO_DATE");
34887                    self.write("(");
34888                    self.generate_expression(&e.this)?;
34889                    self.write(", '");
34890                    self.write(&Self::strftime_to_snowflake_format(fmt));
34891                    self.write("')");
34892                }
34893                Some(DialectType::Doris) => {
34894                    // Doris: TO_DATE(x) - ignores format
34895                    self.write_keyword("TO_DATE");
34896                    self.write("(");
34897                    self.generate_expression(&e.this)?;
34898                    self.write(")");
34899                }
34900                _ => {
34901                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
34902                    self.write_keyword("CAST");
34903                    self.write("(");
34904                    let str_to_time = crate::expressions::StrToTime {
34905                        this: Box::new((*e.this).clone()),
34906                        format: fmt.clone(),
34907                        zone: None,
34908                        safe: None,
34909                        target_type: None,
34910                    };
34911                    self.generate_str_to_time(&str_to_time)?;
34912                    self.write_keyword(" AS ");
34913                    self.write_keyword("DATE");
34914                    self.write(")");
34915                }
34916            }
34917        } else {
34918            // Without format (or default format): simple date conversion
34919            match self.config.dialect {
34920                Some(DialectType::MySQL)
34921                | Some(DialectType::SQLite)
34922                | Some(DialectType::StarRocks) => {
34923                    // MySQL/SQLite/StarRocks: DATE(x)
34924                    self.write_keyword("DATE");
34925                    self.write("(");
34926                    self.generate_expression(&e.this)?;
34927                    self.write(")");
34928                }
34929                Some(DialectType::Hive)
34930                | Some(DialectType::Spark)
34931                | Some(DialectType::Databricks)
34932                | Some(DialectType::Snowflake)
34933                | Some(DialectType::Doris) => {
34934                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
34935                    self.write_keyword("TO_DATE");
34936                    self.write("(");
34937                    self.generate_expression(&e.this)?;
34938                    self.write(")");
34939                }
34940                Some(DialectType::Presto)
34941                | Some(DialectType::Trino)
34942                | Some(DialectType::Athena) => {
34943                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
34944                    self.write_keyword("CAST");
34945                    self.write("(");
34946                    self.write_keyword("CAST");
34947                    self.write("(");
34948                    self.generate_expression(&e.this)?;
34949                    self.write_keyword(" AS ");
34950                    self.write_keyword("TIMESTAMP");
34951                    self.write(")");
34952                    self.write_keyword(" AS ");
34953                    self.write_keyword("DATE");
34954                    self.write(")");
34955                }
34956                Some(DialectType::ClickHouse) => {
34957                    // ClickHouse: CAST(x AS Nullable(DATE))
34958                    self.write_keyword("CAST");
34959                    self.write("(");
34960                    self.generate_expression(&e.this)?;
34961                    self.write_keyword(" AS ");
34962                    self.write("Nullable(DATE)");
34963                    self.write(")");
34964                }
34965                _ => {
34966                    // Default: CAST(x AS DATE)
34967                    self.write_keyword("CAST");
34968                    self.write("(");
34969                    self.generate_expression(&e.this)?;
34970                    self.write_keyword(" AS ");
34971                    self.write_keyword("DATE");
34972                    self.write(")");
34973                }
34974            }
34975        }
34976        Ok(())
34977    }
34978
34979    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
34980        // TS_OR_DS_TO_TIME(this, [format])
34981        self.write_keyword("TS_OR_DS_TO_TIME");
34982        self.write("(");
34983        self.generate_expression(&e.this)?;
34984        if let Some(format) = &e.format {
34985            self.write(", '");
34986            self.write(format);
34987            self.write("'");
34988        }
34989        self.write(")");
34990        Ok(())
34991    }
34992
34993    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
34994        // UNHEX(this, [expression])
34995        self.write_keyword("UNHEX");
34996        self.write("(");
34997        self.generate_expression(&e.this)?;
34998        if let Some(expression) = &e.expression {
34999            self.write(", ");
35000            self.generate_expression(expression)?;
35001        }
35002        self.write(")");
35003        Ok(())
35004    }
35005
35006    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
35007        // U&this [UESCAPE escape]
35008        self.write("U&");
35009        self.generate_expression(&e.this)?;
35010        if let Some(escape) = &e.escape {
35011            self.write_space();
35012            self.write_keyword("UESCAPE");
35013            self.write_space();
35014            self.generate_expression(escape)?;
35015        }
35016        Ok(())
35017    }
35018
35019    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
35020        // UNIFORM(this, expression, [gen], [seed])
35021        self.write_keyword("UNIFORM");
35022        self.write("(");
35023        self.generate_expression(&e.this)?;
35024        self.write(", ");
35025        self.generate_expression(&e.expression)?;
35026        if let Some(gen) = &e.gen {
35027            self.write(", ");
35028            self.generate_expression(gen)?;
35029        }
35030        if let Some(seed) = &e.seed {
35031            self.write(", ");
35032            self.generate_expression(seed)?;
35033        }
35034        self.write(")");
35035        Ok(())
35036    }
35037
35038    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
35039        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
35040        self.write_keyword("UNIQUE");
35041        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
35042        if e.nulls.is_some() {
35043            self.write(" NULLS NOT DISTINCT");
35044        }
35045        if let Some(this) = &e.this {
35046            self.write_space();
35047            self.generate_expression(this)?;
35048        }
35049        if let Some(index_type) = &e.index_type {
35050            self.write(" USING ");
35051            self.generate_expression(index_type)?;
35052        }
35053        if let Some(on_conflict) = &e.on_conflict {
35054            self.write_space();
35055            self.generate_expression(on_conflict)?;
35056        }
35057        for opt in &e.options {
35058            self.write_space();
35059            self.generate_expression(opt)?;
35060        }
35061        Ok(())
35062    }
35063
35064    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
35065        // UNIQUE KEY (expressions)
35066        self.write_keyword("UNIQUE KEY");
35067        self.write(" (");
35068        for (i, expr) in e.expressions.iter().enumerate() {
35069            if i > 0 {
35070                self.write(", ");
35071            }
35072            self.generate_expression(expr)?;
35073        }
35074        self.write(")");
35075        Ok(())
35076    }
35077
35078    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
35079        // ROLLUP (r1(col1, col2), r2(col1))
35080        self.write_keyword("ROLLUP");
35081        self.write(" (");
35082        for (i, index) in e.expressions.iter().enumerate() {
35083            if i > 0 {
35084                self.write(", ");
35085            }
35086            self.generate_identifier(&index.name)?;
35087            self.write("(");
35088            for (j, col) in index.expressions.iter().enumerate() {
35089                if j > 0 {
35090                    self.write(", ");
35091                }
35092                self.generate_identifier(col)?;
35093            }
35094            self.write(")");
35095        }
35096        self.write(")");
35097        Ok(())
35098    }
35099
35100    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
35101        match self.config.dialect {
35102            Some(DialectType::DuckDB) => {
35103                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
35104                self.write_keyword("STRFTIME");
35105                self.write("(");
35106                self.write_keyword("TO_TIMESTAMP");
35107                self.write("(");
35108                self.generate_expression(&e.this)?;
35109                self.write("), '");
35110                if let Some(format) = &e.format {
35111                    self.write(format);
35112                }
35113                self.write("')");
35114            }
35115            Some(DialectType::Hive) => {
35116                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
35117                self.write_keyword("FROM_UNIXTIME");
35118                self.write("(");
35119                self.generate_expression(&e.this)?;
35120                if let Some(format) = &e.format {
35121                    if format != "yyyy-MM-dd HH:mm:ss" {
35122                        self.write(", '");
35123                        self.write(format);
35124                        self.write("'");
35125                    }
35126                }
35127                self.write(")");
35128            }
35129            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35130                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
35131                self.write_keyword("DATE_FORMAT");
35132                self.write("(");
35133                self.write_keyword("FROM_UNIXTIME");
35134                self.write("(");
35135                self.generate_expression(&e.this)?;
35136                self.write("), '");
35137                if let Some(format) = &e.format {
35138                    self.write(format);
35139                }
35140                self.write("')");
35141            }
35142            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35143                // Spark: FROM_UNIXTIME(value, format)
35144                self.write_keyword("FROM_UNIXTIME");
35145                self.write("(");
35146                self.generate_expression(&e.this)?;
35147                if let Some(format) = &e.format {
35148                    self.write(", '");
35149                    self.write(format);
35150                    self.write("'");
35151                }
35152                self.write(")");
35153            }
35154            _ => {
35155                // Default: UNIX_TO_STR(this, [format])
35156                self.write_keyword("UNIX_TO_STR");
35157                self.write("(");
35158                self.generate_expression(&e.this)?;
35159                if let Some(format) = &e.format {
35160                    self.write(", '");
35161                    self.write(format);
35162                    self.write("'");
35163                }
35164                self.write(")");
35165            }
35166        }
35167        Ok(())
35168    }
35169
35170    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
35171        use crate::dialects::DialectType;
35172        let scale = e.scale.unwrap_or(0); // 0 = seconds
35173
35174        match self.config.dialect {
35175            Some(DialectType::Snowflake) => {
35176                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
35177                self.write_keyword("TO_TIMESTAMP");
35178                self.write("(");
35179                self.generate_expression(&e.this)?;
35180                if let Some(s) = e.scale {
35181                    if s > 0 {
35182                        self.write(", ");
35183                        self.write(&s.to_string());
35184                    }
35185                }
35186                self.write(")");
35187            }
35188            Some(DialectType::BigQuery) => {
35189                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
35190                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
35191                match scale {
35192                    0 => {
35193                        self.write_keyword("TIMESTAMP_SECONDS");
35194                        self.write("(");
35195                        self.generate_expression(&e.this)?;
35196                        self.write(")");
35197                    }
35198                    3 => {
35199                        self.write_keyword("TIMESTAMP_MILLIS");
35200                        self.write("(");
35201                        self.generate_expression(&e.this)?;
35202                        self.write(")");
35203                    }
35204                    6 => {
35205                        self.write_keyword("TIMESTAMP_MICROS");
35206                        self.write("(");
35207                        self.generate_expression(&e.this)?;
35208                        self.write(")");
35209                    }
35210                    _ => {
35211                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
35212                        self.write_keyword("TIMESTAMP_SECONDS");
35213                        self.write("(CAST(");
35214                        self.generate_expression(&e.this)?;
35215                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
35216                    }
35217                }
35218            }
35219            Some(DialectType::Spark) => {
35220                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
35221                // TIMESTAMP_MILLIS(value) for scale=3
35222                // TIMESTAMP_MICROS(value) for scale=6
35223                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
35224                match scale {
35225                    0 => {
35226                        self.write_keyword("CAST");
35227                        self.write("(");
35228                        self.write_keyword("FROM_UNIXTIME");
35229                        self.write("(");
35230                        self.generate_expression(&e.this)?;
35231                        self.write(") ");
35232                        self.write_keyword("AS TIMESTAMP");
35233                        self.write(")");
35234                    }
35235                    3 => {
35236                        self.write_keyword("TIMESTAMP_MILLIS");
35237                        self.write("(");
35238                        self.generate_expression(&e.this)?;
35239                        self.write(")");
35240                    }
35241                    6 => {
35242                        self.write_keyword("TIMESTAMP_MICROS");
35243                        self.write("(");
35244                        self.generate_expression(&e.this)?;
35245                        self.write(")");
35246                    }
35247                    _ => {
35248                        self.write_keyword("TIMESTAMP_SECONDS");
35249                        self.write("(");
35250                        self.generate_expression(&e.this)?;
35251                        self.write(&format!(" / POWER(10, {}))", scale));
35252                    }
35253                }
35254            }
35255            Some(DialectType::Databricks) => {
35256                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
35257                // TIMESTAMP_MILLIS(value) for scale=3
35258                // TIMESTAMP_MICROS(value) for scale=6
35259                match scale {
35260                    0 => {
35261                        self.write_keyword("CAST");
35262                        self.write("(");
35263                        self.write_keyword("FROM_UNIXTIME");
35264                        self.write("(");
35265                        self.generate_expression(&e.this)?;
35266                        self.write(") ");
35267                        self.write_keyword("AS TIMESTAMP");
35268                        self.write(")");
35269                    }
35270                    3 => {
35271                        self.write_keyword("TIMESTAMP_MILLIS");
35272                        self.write("(");
35273                        self.generate_expression(&e.this)?;
35274                        self.write(")");
35275                    }
35276                    6 => {
35277                        self.write_keyword("TIMESTAMP_MICROS");
35278                        self.write("(");
35279                        self.generate_expression(&e.this)?;
35280                        self.write(")");
35281                    }
35282                    _ => {
35283                        self.write_keyword("TIMESTAMP_SECONDS");
35284                        self.write("(");
35285                        self.generate_expression(&e.this)?;
35286                        self.write(&format!(" / POWER(10, {}))", scale));
35287                    }
35288                }
35289            }
35290            Some(DialectType::Hive) => {
35291                // Hive: FROM_UNIXTIME(value)
35292                if scale == 0 {
35293                    self.write_keyword("FROM_UNIXTIME");
35294                    self.write("(");
35295                    self.generate_expression(&e.this)?;
35296                    self.write(")");
35297                } else {
35298                    self.write_keyword("FROM_UNIXTIME");
35299                    self.write("(");
35300                    self.generate_expression(&e.this)?;
35301                    self.write(&format!(" / POWER(10, {})", scale));
35302                    self.write(")");
35303                }
35304            }
35305            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35306                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
35307                // FROM_UNIXTIME(value) for scale=0
35308                if scale == 0 {
35309                    self.write_keyword("FROM_UNIXTIME");
35310                    self.write("(");
35311                    self.generate_expression(&e.this)?;
35312                    self.write(")");
35313                } else {
35314                    self.write_keyword("FROM_UNIXTIME");
35315                    self.write("(CAST(");
35316                    self.generate_expression(&e.this)?;
35317                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
35318                }
35319            }
35320            Some(DialectType::DuckDB) => {
35321                // DuckDB: TO_TIMESTAMP(value) for scale=0
35322                // EPOCH_MS(value) for scale=3
35323                // MAKE_TIMESTAMP(value) for scale=6
35324                match scale {
35325                    0 => {
35326                        self.write_keyword("TO_TIMESTAMP");
35327                        self.write("(");
35328                        self.generate_expression(&e.this)?;
35329                        self.write(")");
35330                    }
35331                    3 => {
35332                        self.write_keyword("EPOCH_MS");
35333                        self.write("(");
35334                        self.generate_expression(&e.this)?;
35335                        self.write(")");
35336                    }
35337                    6 => {
35338                        self.write_keyword("MAKE_TIMESTAMP");
35339                        self.write("(");
35340                        self.generate_expression(&e.this)?;
35341                        self.write(")");
35342                    }
35343                    _ => {
35344                        self.write_keyword("TO_TIMESTAMP");
35345                        self.write("(");
35346                        self.generate_expression(&e.this)?;
35347                        self.write(&format!(" / POWER(10, {}))", scale));
35348                        self.write_keyword(" AT TIME ZONE");
35349                        self.write(" 'UTC'");
35350                    }
35351                }
35352            }
35353            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
35354                // Doris/StarRocks: FROM_UNIXTIME(value)
35355                self.write_keyword("FROM_UNIXTIME");
35356                self.write("(");
35357                self.generate_expression(&e.this)?;
35358                self.write(")");
35359            }
35360            Some(DialectType::Oracle) => {
35361                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
35362                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
35363                self.generate_expression(&e.this)?;
35364                self.write(" / 86400)");
35365            }
35366            Some(DialectType::Redshift) => {
35367                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
35368                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
35369                self.write("(TIMESTAMP 'epoch' + ");
35370                if scale == 0 {
35371                    self.generate_expression(&e.this)?;
35372                } else {
35373                    self.write("(");
35374                    self.generate_expression(&e.this)?;
35375                    self.write(&format!(" / POWER(10, {}))", scale));
35376                }
35377                self.write(" * INTERVAL '1 SECOND')");
35378            }
35379            _ => {
35380                // Default: TO_TIMESTAMP(value[, scale])
35381                self.write_keyword("TO_TIMESTAMP");
35382                self.write("(");
35383                self.generate_expression(&e.this)?;
35384                if let Some(s) = e.scale {
35385                    self.write(", ");
35386                    self.write(&s.to_string());
35387                }
35388                self.write(")");
35389            }
35390        }
35391        Ok(())
35392    }
35393
35394    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
35395        // NAME col VALUE col1, col2, ...
35396        if !matches!(&*e.this, Expression::Null(_)) {
35397            self.write_keyword("NAME");
35398            self.write_space();
35399            self.generate_expression(&e.this)?;
35400        }
35401        if !e.expressions.is_empty() {
35402            self.write_space();
35403            self.write_keyword("VALUE");
35404            self.write_space();
35405            for (i, expr) in e.expressions.iter().enumerate() {
35406                if i > 0 {
35407                    self.write(", ");
35408                }
35409                self.generate_expression(expr)?;
35410            }
35411        }
35412        Ok(())
35413    }
35414
35415    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
35416        // this(expressions) or (this)(expressions)
35417        if e.wrapped.is_some() {
35418            self.write("(");
35419        }
35420        self.generate_expression(&e.this)?;
35421        if e.wrapped.is_some() {
35422            self.write(")");
35423        }
35424        self.write("(");
35425        for (i, expr) in e.expressions.iter().enumerate() {
35426            if i > 0 {
35427                self.write(", ");
35428            }
35429            self.generate_expression(expr)?;
35430        }
35431        self.write(")");
35432        Ok(())
35433    }
35434
35435    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
35436        // USING TEMPLATE this
35437        self.write_keyword("USING TEMPLATE");
35438        self.write_space();
35439        self.generate_expression(&e.this)?;
35440        Ok(())
35441    }
35442
35443    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
35444        // UTC_TIME
35445        self.write_keyword("UTC_TIME");
35446        Ok(())
35447    }
35448
35449    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
35450        // UTC_TIMESTAMP
35451        self.write_keyword("UTC_TIMESTAMP");
35452        Ok(())
35453    }
35454
35455    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
35456        use crate::dialects::DialectType;
35457        // Choose UUID function name based on target dialect
35458        let func_name = match self.config.dialect {
35459            Some(DialectType::Snowflake) => "UUID_STRING",
35460            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
35461            Some(DialectType::BigQuery) => "GENERATE_UUID",
35462            _ => {
35463                if let Some(name) = &e.name {
35464                    name.as_str()
35465                } else {
35466                    "UUID"
35467                }
35468            }
35469        };
35470        self.write_keyword(func_name);
35471        self.write("(");
35472        if let Some(this) = &e.this {
35473            self.generate_expression(this)?;
35474        }
35475        self.write(")");
35476        Ok(())
35477    }
35478
35479    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
35480        // MAP(key1, value1, key2, value2, ...)
35481        self.write_keyword("MAP");
35482        self.write("(");
35483        let mut first = true;
35484        for (k, v) in e.keys.iter().zip(e.values.iter()) {
35485            if !first {
35486                self.write(", ");
35487            }
35488            self.generate_expression(k)?;
35489            self.write(", ");
35490            self.generate_expression(v)?;
35491            first = false;
35492        }
35493        self.write(")");
35494        Ok(())
35495    }
35496
35497    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
35498        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
35499        self.write_keyword("VECTOR_SEARCH");
35500        self.write("(");
35501        self.generate_expression(&e.this)?;
35502        if let Some(col) = &e.column_to_search {
35503            self.write(", ");
35504            self.generate_expression(col)?;
35505        }
35506        if let Some(query_table) = &e.query_table {
35507            self.write(", ");
35508            self.generate_expression(query_table)?;
35509        }
35510        if let Some(query_col) = &e.query_column_to_search {
35511            self.write(", ");
35512            self.generate_expression(query_col)?;
35513        }
35514        if let Some(top_k) = &e.top_k {
35515            self.write(", ");
35516            self.generate_expression(top_k)?;
35517        }
35518        if let Some(dist_type) = &e.distance_type {
35519            self.write(", ");
35520            self.generate_expression(dist_type)?;
35521        }
35522        self.write(")");
35523        Ok(())
35524    }
35525
35526    fn generate_version(&mut self, e: &Version) -> Result<()> {
35527        // Python: f"FOR {expression.name} {kind} {expr}"
35528        // e.this = Identifier("TIMESTAMP" or "VERSION")
35529        // e.kind = "AS OF" (or "BETWEEN", etc.)
35530        // e.expression = the value expression
35531        // Hive does NOT use the FOR prefix for time travel
35532        use crate::dialects::DialectType;
35533        let skip_for = matches!(
35534            self.config.dialect,
35535            Some(DialectType::Hive) | Some(DialectType::Spark)
35536        );
35537        if !skip_for {
35538            self.write_keyword("FOR");
35539            self.write_space();
35540        }
35541        // Extract the name from this (which is an Identifier expression)
35542        match e.this.as_ref() {
35543            Expression::Identifier(ident) => {
35544                self.write_keyword(&ident.name);
35545            }
35546            _ => {
35547                self.generate_expression(&e.this)?;
35548            }
35549        }
35550        self.write_space();
35551        self.write_keyword(&e.kind);
35552        if let Some(expression) = &e.expression {
35553            self.write_space();
35554            self.generate_expression(expression)?;
35555        }
35556        Ok(())
35557    }
35558
35559    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
35560        // Python: return self.sql(expression, "this")
35561        self.generate_expression(&e.this)?;
35562        Ok(())
35563    }
35564
35565    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
35566        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
35567        if e.this.is_some() {
35568            self.write_keyword("NOT VOLATILE");
35569        } else {
35570            self.write_keyword("VOLATILE");
35571        }
35572        Ok(())
35573    }
35574
35575    fn generate_watermark_column_constraint(
35576        &mut self,
35577        e: &WatermarkColumnConstraint,
35578    ) -> Result<()> {
35579        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
35580        self.write_keyword("WATERMARK FOR");
35581        self.write_space();
35582        self.generate_expression(&e.this)?;
35583        self.write_space();
35584        self.write_keyword("AS");
35585        self.write_space();
35586        self.generate_expression(&e.expression)?;
35587        Ok(())
35588    }
35589
35590    fn generate_week(&mut self, e: &Week) -> Result<()> {
35591        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
35592        self.write_keyword("WEEK");
35593        self.write("(");
35594        self.generate_expression(&e.this)?;
35595        if let Some(mode) = &e.mode {
35596            self.write(", ");
35597            self.generate_expression(mode)?;
35598        }
35599        self.write(")");
35600        Ok(())
35601    }
35602
35603    fn generate_when(&mut self, e: &When) -> Result<()> {
35604        // Python: WHEN {matched}{source}{condition} THEN {then}
35605        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
35606        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
35607        self.write_keyword("WHEN");
35608        self.write_space();
35609
35610        // Check if matched
35611        if let Some(matched) = &e.matched {
35612            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
35613            match matched.as_ref() {
35614                Expression::Boolean(b) if b.value => {
35615                    self.write_keyword("MATCHED");
35616                }
35617                _ => {
35618                    self.write_keyword("NOT MATCHED");
35619                }
35620            }
35621        } else {
35622            self.write_keyword("NOT MATCHED");
35623        }
35624
35625        // BY SOURCE / BY TARGET
35626        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
35627        // BY TARGET is the default and typically omitted in output
35628        // Only emit if the dialect supports BY SOURCE syntax
35629        if self.config.matched_by_source {
35630            if let Some(source) = &e.source {
35631                if let Expression::Boolean(b) = source.as_ref() {
35632                    if b.value {
35633                        // BY SOURCE
35634                        self.write_space();
35635                        self.write_keyword("BY SOURCE");
35636                    }
35637                    // BY TARGET (b.value == false) is omitted as it's the default
35638                } else {
35639                    // For non-boolean source, output as BY SOURCE (legacy behavior)
35640                    self.write_space();
35641                    self.write_keyword("BY SOURCE");
35642                }
35643            }
35644        }
35645
35646        // Condition
35647        if let Some(condition) = &e.condition {
35648            self.write_space();
35649            self.write_keyword("AND");
35650            self.write_space();
35651            self.generate_expression(condition)?;
35652        }
35653
35654        self.write_space();
35655        self.write_keyword("THEN");
35656        self.write_space();
35657
35658        // Generate the then expression (could be INSERT, UPDATE, DELETE)
35659        // MERGE actions are stored as Tuples with the action keyword as first element
35660        self.generate_merge_action(&e.then)?;
35661
35662        Ok(())
35663    }
35664
35665    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
35666        match action {
35667            Expression::Tuple(tuple) => {
35668                let elements = &tuple.expressions;
35669                if elements.is_empty() {
35670                    return self.generate_expression(action);
35671                }
35672                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
35673                match &elements[0] {
35674                    Expression::Var(v) if v.this == "INSERT" => {
35675                        self.write_keyword("INSERT");
35676                        // Spark: INSERT * (insert all columns)
35677                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
35678                            self.write(" *");
35679                        } else {
35680                            let mut values_idx = 1;
35681                            // Check if second element is column list (Tuple)
35682                            if elements.len() > 1 {
35683                                if let Expression::Tuple(cols) = &elements[1] {
35684                                    // Could be columns or values - if there's a third element, second is columns
35685                                    if elements.len() > 2 {
35686                                        // Second is columns, third is values
35687                                        self.write(" (");
35688                                        for (i, col) in cols.expressions.iter().enumerate() {
35689                                            if i > 0 {
35690                                                self.write(", ");
35691                                            }
35692                                            // Strip MERGE target qualifiers from INSERT column list
35693                                            if !self.merge_strip_qualifiers.is_empty() {
35694                                                let stripped = self.strip_merge_qualifier(col);
35695                                                self.generate_expression(&stripped)?;
35696                                            } else {
35697                                                self.generate_expression(col)?;
35698                                            }
35699                                        }
35700                                        self.write(")");
35701                                        values_idx = 2;
35702                                    } else {
35703                                        // Only two elements: INSERT + values (no explicit columns)
35704                                        values_idx = 1;
35705                                    }
35706                                }
35707                            }
35708                            // Generate VALUES clause
35709                            if values_idx < elements.len() {
35710                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
35711                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
35712                                if !is_row {
35713                                    self.write_space();
35714                                    self.write_keyword("VALUES");
35715                                }
35716                                self.write(" ");
35717                                if let Expression::Tuple(vals) = &elements[values_idx] {
35718                                    self.write("(");
35719                                    for (i, val) in vals.expressions.iter().enumerate() {
35720                                        if i > 0 {
35721                                            self.write(", ");
35722                                        }
35723                                        self.generate_expression(val)?;
35724                                    }
35725                                    self.write(")");
35726                                } else {
35727                                    self.generate_expression(&elements[values_idx])?;
35728                                }
35729                            }
35730                        } // close else for INSERT * check
35731                    }
35732                    Expression::Var(v) if v.this == "UPDATE" => {
35733                        self.write_keyword("UPDATE");
35734                        // Spark: UPDATE * (update all columns)
35735                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
35736                            self.write(" *");
35737                        } else if elements.len() > 1 {
35738                            self.write_space();
35739                            self.write_keyword("SET");
35740                            // In pretty mode, put assignments on next line with extra indent
35741                            if self.config.pretty {
35742                                self.write_newline();
35743                                self.indent_level += 1;
35744                                self.write_indent();
35745                            } else {
35746                                self.write_space();
35747                            }
35748                            if let Expression::Tuple(assignments) = &elements[1] {
35749                                for (i, assignment) in assignments.expressions.iter().enumerate() {
35750                                    if i > 0 {
35751                                        if self.config.pretty {
35752                                            self.write(",");
35753                                            self.write_newline();
35754                                            self.write_indent();
35755                                        } else {
35756                                            self.write(", ");
35757                                        }
35758                                    }
35759                                    // Strip MERGE target qualifiers from left side of UPDATE SET
35760                                    if !self.merge_strip_qualifiers.is_empty() {
35761                                        self.generate_merge_set_assignment(assignment)?;
35762                                    } else {
35763                                        self.generate_expression(assignment)?;
35764                                    }
35765                                }
35766                            } else {
35767                                self.generate_expression(&elements[1])?;
35768                            }
35769                            if self.config.pretty {
35770                                self.indent_level -= 1;
35771                            }
35772                        }
35773                    }
35774                    _ => {
35775                        // Fallback: generic tuple generation
35776                        self.generate_expression(action)?;
35777                    }
35778                }
35779            }
35780            Expression::Var(v)
35781                if v.this == "INSERT"
35782                    || v.this == "UPDATE"
35783                    || v.this == "DELETE"
35784                    || v.this == "DO NOTHING" =>
35785            {
35786                self.write_keyword(&v.this);
35787            }
35788            _ => {
35789                self.generate_expression(action)?;
35790            }
35791        }
35792        Ok(())
35793    }
35794
35795    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
35796    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
35797        match assignment {
35798            Expression::Eq(eq) => {
35799                // Strip qualifier from the left side if it matches a MERGE target name
35800                let stripped_left = self.strip_merge_qualifier(&eq.left);
35801                self.generate_expression(&stripped_left)?;
35802                self.write(" = ");
35803                self.generate_expression(&eq.right)?;
35804                Ok(())
35805            }
35806            other => self.generate_expression(other),
35807        }
35808    }
35809
35810    /// Strip table qualifier from a column reference if it matches a MERGE target name
35811    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
35812        match expr {
35813            Expression::Column(col) => {
35814                if let Some(ref table_ident) = col.table {
35815                    if self
35816                        .merge_strip_qualifiers
35817                        .iter()
35818                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
35819                    {
35820                        // Strip the table qualifier
35821                        let mut col = col.clone();
35822                        col.table = None;
35823                        return Expression::Column(col);
35824                    }
35825                }
35826                expr.clone()
35827            }
35828            Expression::Dot(dot) => {
35829                // table.column -> column (strip qualifier)
35830                if let Expression::Identifier(id) = &dot.this {
35831                    if self
35832                        .merge_strip_qualifiers
35833                        .iter()
35834                        .any(|n| n.eq_ignore_ascii_case(&id.name))
35835                    {
35836                        return Expression::Identifier(dot.field.clone());
35837                    }
35838                }
35839                expr.clone()
35840            }
35841            _ => expr.clone(),
35842        }
35843    }
35844
35845    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
35846        // Python: return self.expressions(expression, sep=" ", indent=False)
35847        for (i, expr) in e.expressions.iter().enumerate() {
35848            if i > 0 {
35849                // In pretty mode, each WHEN clause on its own line
35850                if self.config.pretty {
35851                    self.write_newline();
35852                    self.write_indent();
35853                } else {
35854                    self.write_space();
35855                }
35856            }
35857            self.generate_expression(expr)?;
35858        }
35859        Ok(())
35860    }
35861
35862    fn generate_where(&mut self, e: &Where) -> Result<()> {
35863        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
35864        self.write_keyword("WHERE");
35865        self.write_space();
35866        self.generate_expression(&e.this)?;
35867        Ok(())
35868    }
35869
35870    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
35871        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
35872        self.write_keyword("WIDTH_BUCKET");
35873        self.write("(");
35874        self.generate_expression(&e.this)?;
35875        if let Some(min_value) = &e.min_value {
35876            self.write(", ");
35877            self.generate_expression(min_value)?;
35878        }
35879        if let Some(max_value) = &e.max_value {
35880            self.write(", ");
35881            self.generate_expression(max_value)?;
35882        }
35883        if let Some(num_buckets) = &e.num_buckets {
35884            self.write(", ");
35885            self.generate_expression(num_buckets)?;
35886        }
35887        self.write(")");
35888        Ok(())
35889    }
35890
35891    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
35892        // Window specification: PARTITION BY ... ORDER BY ... frame
35893        self.generate_window_spec(e)
35894    }
35895
35896    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
35897        // Window specification: PARTITION BY ... ORDER BY ... frame
35898        let mut has_content = false;
35899
35900        // PARTITION BY
35901        if !e.partition_by.is_empty() {
35902            self.write_keyword("PARTITION BY");
35903            self.write_space();
35904            for (i, expr) in e.partition_by.iter().enumerate() {
35905                if i > 0 {
35906                    self.write(", ");
35907                }
35908                self.generate_expression(expr)?;
35909            }
35910            has_content = true;
35911        }
35912
35913        // ORDER BY
35914        if !e.order_by.is_empty() {
35915            if has_content {
35916                self.write_space();
35917            }
35918            self.write_keyword("ORDER BY");
35919            self.write_space();
35920            for (i, ordered) in e.order_by.iter().enumerate() {
35921                if i > 0 {
35922                    self.write(", ");
35923                }
35924                self.generate_expression(&ordered.this)?;
35925                if ordered.desc {
35926                    self.write_space();
35927                    self.write_keyword("DESC");
35928                } else if ordered.explicit_asc {
35929                    self.write_space();
35930                    self.write_keyword("ASC");
35931                }
35932                if let Some(nulls_first) = ordered.nulls_first {
35933                    self.write_space();
35934                    self.write_keyword("NULLS");
35935                    self.write_space();
35936                    if nulls_first {
35937                        self.write_keyword("FIRST");
35938                    } else {
35939                        self.write_keyword("LAST");
35940                    }
35941                }
35942            }
35943            has_content = true;
35944        }
35945
35946        // Frame specification
35947        if let Some(frame) = &e.frame {
35948            if has_content {
35949                self.write_space();
35950            }
35951            self.generate_window_frame(frame)?;
35952        }
35953
35954        Ok(())
35955    }
35956
35957    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
35958        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
35959        self.write_keyword("WITH");
35960        self.write_space();
35961        if e.no.is_some() {
35962            self.write_keyword("NO");
35963            self.write_space();
35964        }
35965        self.write_keyword("DATA");
35966
35967        // statistics
35968        if let Some(statistics) = &e.statistics {
35969            self.write_space();
35970            self.write_keyword("AND");
35971            self.write_space();
35972            // Check if statistics is true or false
35973            match statistics.as_ref() {
35974                Expression::Boolean(b) if !b.value => {
35975                    self.write_keyword("NO");
35976                    self.write_space();
35977                }
35978                _ => {}
35979            }
35980            self.write_keyword("STATISTICS");
35981        }
35982        Ok(())
35983    }
35984
35985    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
35986        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
35987        self.write_keyword("WITH FILL");
35988
35989        if let Some(from_) = &e.from_ {
35990            self.write_space();
35991            self.write_keyword("FROM");
35992            self.write_space();
35993            self.generate_expression(from_)?;
35994        }
35995
35996        if let Some(to) = &e.to {
35997            self.write_space();
35998            self.write_keyword("TO");
35999            self.write_space();
36000            self.generate_expression(to)?;
36001        }
36002
36003        if let Some(step) = &e.step {
36004            self.write_space();
36005            self.write_keyword("STEP");
36006            self.write_space();
36007            self.generate_expression(step)?;
36008        }
36009
36010        if let Some(staleness) = &e.staleness {
36011            self.write_space();
36012            self.write_keyword("STALENESS");
36013            self.write_space();
36014            self.generate_expression(staleness)?;
36015        }
36016
36017        if let Some(interpolate) = &e.interpolate {
36018            self.write_space();
36019            self.write_keyword("INTERPOLATE");
36020            self.write(" (");
36021            // INTERPOLATE items use reversed alias format: name AS expression
36022            self.generate_interpolate_item(interpolate)?;
36023            self.write(")");
36024        }
36025
36026        Ok(())
36027    }
36028
36029    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
36030    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
36031        match expr {
36032            Expression::Alias(alias) => {
36033                // Output as: alias_name AS expression
36034                self.generate_identifier(&alias.alias)?;
36035                self.write_space();
36036                self.write_keyword("AS");
36037                self.write_space();
36038                self.generate_expression(&alias.this)?;
36039            }
36040            Expression::Tuple(tuple) => {
36041                for (i, item) in tuple.expressions.iter().enumerate() {
36042                    if i > 0 {
36043                        self.write(", ");
36044                    }
36045                    self.generate_interpolate_item(item)?;
36046                }
36047            }
36048            other => {
36049                self.generate_expression(other)?;
36050            }
36051        }
36052        Ok(())
36053    }
36054
36055    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
36056        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
36057        self.write_keyword("WITH JOURNAL TABLE");
36058        self.write("=");
36059        self.generate_expression(&e.this)?;
36060        Ok(())
36061    }
36062
36063    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
36064        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
36065        self.generate_expression(&e.this)?;
36066        self.write_space();
36067        self.write_keyword("WITH");
36068        self.write_space();
36069        self.write_keyword(&e.op);
36070        Ok(())
36071    }
36072
36073    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
36074        // Python: return f"WITH {self.expressions(expression, flat=True)}"
36075        self.write_keyword("WITH");
36076        self.write_space();
36077        for (i, expr) in e.expressions.iter().enumerate() {
36078            if i > 0 {
36079                self.write(", ");
36080            }
36081            self.generate_expression(expr)?;
36082        }
36083        Ok(())
36084    }
36085
36086    fn generate_with_schema_binding_property(
36087        &mut self,
36088        e: &WithSchemaBindingProperty,
36089    ) -> Result<()> {
36090        // Python: return f"WITH {self.sql(expression, 'this')}"
36091        self.write_keyword("WITH");
36092        self.write_space();
36093        self.generate_expression(&e.this)?;
36094        Ok(())
36095    }
36096
36097    fn generate_with_system_versioning_property(
36098        &mut self,
36099        e: &WithSystemVersioningProperty,
36100    ) -> Result<()> {
36101        // Python: complex logic for SYSTEM_VERSIONING with options
36102        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
36103        // or SYSTEM_VERSIONING=ON/OFF
36104        // with WITH(...) wrapper if with_ is set
36105
36106        let mut parts = Vec::new();
36107
36108        if let Some(this) = &e.this {
36109            // HISTORY_TABLE=...
36110            let mut s = String::from("HISTORY_TABLE=");
36111            let mut gen = Generator::new();
36112            gen.generate_expression(this)?;
36113            s.push_str(&gen.output);
36114            parts.push(s);
36115        }
36116
36117        if let Some(data_consistency) = &e.data_consistency {
36118            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
36119            let mut gen = Generator::new();
36120            gen.generate_expression(data_consistency)?;
36121            s.push_str(&gen.output);
36122            parts.push(s);
36123        }
36124
36125        if let Some(retention_period) = &e.retention_period {
36126            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
36127            let mut gen = Generator::new();
36128            gen.generate_expression(retention_period)?;
36129            s.push_str(&gen.output);
36130            parts.push(s);
36131        }
36132
36133        self.write_keyword("SYSTEM_VERSIONING");
36134        self.write("=");
36135
36136        if !parts.is_empty() {
36137            self.write_keyword("ON");
36138            self.write("(");
36139            self.write(&parts.join(", "));
36140            self.write(")");
36141        } else if e.on.is_some() {
36142            self.write_keyword("ON");
36143        } else {
36144            self.write_keyword("OFF");
36145        }
36146
36147        // Wrap in WITH(...) if with_ is set
36148        if e.with_.is_some() {
36149            let inner = self.output.clone();
36150            self.output.clear();
36151            self.write("WITH(");
36152            self.write(&inner);
36153            self.write(")");
36154        }
36155
36156        Ok(())
36157    }
36158
36159    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
36160        // Python: f"WITH ({self.expressions(expression, flat=True)})"
36161        self.write_keyword("WITH");
36162        self.write(" (");
36163        for (i, expr) in e.expressions.iter().enumerate() {
36164            if i > 0 {
36165                self.write(", ");
36166            }
36167            self.generate_expression(expr)?;
36168        }
36169        self.write(")");
36170        Ok(())
36171    }
36172
36173    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
36174        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
36175        // return self.func("XMLELEMENT", name, *expression.expressions)
36176        self.write_keyword("XMLELEMENT");
36177        self.write("(");
36178
36179        if e.evalname.is_some() {
36180            self.write_keyword("EVALNAME");
36181        } else {
36182            self.write_keyword("NAME");
36183        }
36184        self.write_space();
36185        self.generate_expression(&e.this)?;
36186
36187        for expr in &e.expressions {
36188            self.write(", ");
36189            self.generate_expression(expr)?;
36190        }
36191        self.write(")");
36192        Ok(())
36193    }
36194
36195    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
36196        // XMLGET(this, expression [, instance])
36197        self.write_keyword("XMLGET");
36198        self.write("(");
36199        self.generate_expression(&e.this)?;
36200        self.write(", ");
36201        self.generate_expression(&e.expression)?;
36202        if let Some(instance) = &e.instance {
36203            self.write(", ");
36204            self.generate_expression(instance)?;
36205        }
36206        self.write(")");
36207        Ok(())
36208    }
36209
36210    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
36211        // Python: this + optional (expr)
36212        self.generate_expression(&e.this)?;
36213        if let Some(expression) = &e.expression {
36214            self.write("(");
36215            self.generate_expression(expression)?;
36216            self.write(")");
36217        }
36218        Ok(())
36219    }
36220
36221    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
36222        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
36223        self.write_keyword("XMLTABLE");
36224        self.write("(");
36225
36226        if self.config.pretty {
36227            self.indent_level += 1;
36228            self.write_newline();
36229            self.write_indent();
36230            self.generate_expression(&e.this)?;
36231
36232            if let Some(passing) = &e.passing {
36233                self.write_newline();
36234                self.write_indent();
36235                self.write_keyword("PASSING");
36236                if let Expression::Tuple(tuple) = passing.as_ref() {
36237                    for expr in &tuple.expressions {
36238                        self.write_newline();
36239                        self.indent_level += 1;
36240                        self.write_indent();
36241                        self.generate_expression(expr)?;
36242                        self.indent_level -= 1;
36243                    }
36244                } else {
36245                    self.write_newline();
36246                    self.indent_level += 1;
36247                    self.write_indent();
36248                    self.generate_expression(passing)?;
36249                    self.indent_level -= 1;
36250                }
36251            }
36252
36253            if e.by_ref.is_some() {
36254                self.write_newline();
36255                self.write_indent();
36256                self.write_keyword("RETURNING SEQUENCE BY REF");
36257            }
36258
36259            if !e.columns.is_empty() {
36260                self.write_newline();
36261                self.write_indent();
36262                self.write_keyword("COLUMNS");
36263                for (i, col) in e.columns.iter().enumerate() {
36264                    self.write_newline();
36265                    self.indent_level += 1;
36266                    self.write_indent();
36267                    self.generate_expression(col)?;
36268                    self.indent_level -= 1;
36269                    if i < e.columns.len() - 1 {
36270                        self.write(",");
36271                    }
36272                }
36273            }
36274
36275            self.indent_level -= 1;
36276            self.write_newline();
36277            self.write_indent();
36278            self.write(")");
36279            return Ok(());
36280        }
36281
36282        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
36283        if let Some(namespaces) = &e.namespaces {
36284            self.write_keyword("XMLNAMESPACES");
36285            self.write("(");
36286            // Unwrap Tuple if present to avoid extra parentheses
36287            if let Expression::Tuple(tuple) = namespaces.as_ref() {
36288                for (i, expr) in tuple.expressions.iter().enumerate() {
36289                    if i > 0 {
36290                        self.write(", ");
36291                    }
36292                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
36293                    // See xmlnamespace_sql in generator.py
36294                    if !matches!(expr, Expression::Alias(_)) {
36295                        self.write_keyword("DEFAULT");
36296                        self.write_space();
36297                    }
36298                    self.generate_expression(expr)?;
36299                }
36300            } else {
36301                // Single namespace - check if DEFAULT
36302                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
36303                    self.write_keyword("DEFAULT");
36304                    self.write_space();
36305                }
36306                self.generate_expression(namespaces)?;
36307            }
36308            self.write("), ");
36309        }
36310
36311        // XPath expression
36312        self.generate_expression(&e.this)?;
36313
36314        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
36315        if let Some(passing) = &e.passing {
36316            self.write_space();
36317            self.write_keyword("PASSING");
36318            self.write_space();
36319            // Unwrap Tuple if present to avoid extra parentheses
36320            if let Expression::Tuple(tuple) = passing.as_ref() {
36321                for (i, expr) in tuple.expressions.iter().enumerate() {
36322                    if i > 0 {
36323                        self.write(", ");
36324                    }
36325                    self.generate_expression(expr)?;
36326                }
36327            } else {
36328                self.generate_expression(passing)?;
36329            }
36330        }
36331
36332        // RETURNING SEQUENCE BY REF
36333        if e.by_ref.is_some() {
36334            self.write_space();
36335            self.write_keyword("RETURNING SEQUENCE BY REF");
36336        }
36337
36338        // COLUMNS clause
36339        if !e.columns.is_empty() {
36340            self.write_space();
36341            self.write_keyword("COLUMNS");
36342            self.write_space();
36343            for (i, col) in e.columns.iter().enumerate() {
36344                if i > 0 {
36345                    self.write(", ");
36346                }
36347                self.generate_expression(col)?;
36348            }
36349        }
36350
36351        self.write(")");
36352        Ok(())
36353    }
36354
36355    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
36356        // Python: return self.connector_sql(expression, "XOR", stack)
36357        // Handles: this XOR expression or expressions joined by XOR
36358        if let Some(this) = &e.this {
36359            self.generate_expression(this)?;
36360            if let Some(expression) = &e.expression {
36361                self.write_space();
36362                self.write_keyword("XOR");
36363                self.write_space();
36364                self.generate_expression(expression)?;
36365            }
36366        }
36367
36368        // Handle multiple expressions
36369        for (i, expr) in e.expressions.iter().enumerate() {
36370            if i > 0 || e.this.is_some() {
36371                self.write_space();
36372                self.write_keyword("XOR");
36373                self.write_space();
36374            }
36375            self.generate_expression(expr)?;
36376        }
36377        Ok(())
36378    }
36379
36380    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
36381        // ZIPF(this, elementcount [, gen])
36382        self.write_keyword("ZIPF");
36383        self.write("(");
36384        self.generate_expression(&e.this)?;
36385        if let Some(elementcount) = &e.elementcount {
36386            self.write(", ");
36387            self.generate_expression(elementcount)?;
36388        }
36389        if let Some(gen) = &e.gen {
36390            self.write(", ");
36391            self.generate_expression(gen)?;
36392        }
36393        self.write(")");
36394        Ok(())
36395    }
36396}
36397
36398impl Default for Generator {
36399    fn default() -> Self {
36400        Self::new()
36401    }
36402}
36403
36404#[cfg(test)]
36405mod tests {
36406    use super::*;
36407    use crate::parser::Parser;
36408
36409    fn roundtrip(sql: &str) -> String {
36410        let ast = Parser::parse_sql(sql).unwrap();
36411        Generator::sql(&ast[0]).unwrap()
36412    }
36413
36414    #[test]
36415    fn test_simple_select() {
36416        let result = roundtrip("SELECT 1");
36417        assert_eq!(result, "SELECT 1");
36418    }
36419
36420    #[test]
36421    fn test_select_from() {
36422        let result = roundtrip("SELECT a, b FROM t");
36423        assert_eq!(result, "SELECT a, b FROM t");
36424    }
36425
36426    #[test]
36427    fn test_select_where() {
36428        let result = roundtrip("SELECT * FROM t WHERE x = 1");
36429        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
36430    }
36431
36432    #[test]
36433    fn test_select_join() {
36434        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
36435        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
36436    }
36437
36438    #[test]
36439    fn test_insert() {
36440        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
36441        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
36442    }
36443
36444    #[test]
36445    fn test_pretty_print() {
36446        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
36447        let result = Generator::pretty_sql(&ast[0]).unwrap();
36448        assert!(result.contains('\n'));
36449    }
36450
36451    #[test]
36452    fn test_window_function() {
36453        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
36454        assert_eq!(
36455            result,
36456            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
36457        );
36458    }
36459
36460    #[test]
36461    fn test_window_function_with_frame() {
36462        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
36463        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
36464    }
36465
36466    #[test]
36467    fn test_aggregate_with_filter() {
36468        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
36469        assert_eq!(
36470            result,
36471            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
36472        );
36473    }
36474
36475    #[test]
36476    fn test_subscript() {
36477        let result = roundtrip("SELECT arr[0]");
36478        assert_eq!(result, "SELECT arr[0]");
36479    }
36480
36481    // DDL tests
36482    #[test]
36483    fn test_create_table() {
36484        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
36485        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
36486    }
36487
36488    #[test]
36489    fn test_create_table_with_constraints() {
36490        let result = roundtrip(
36491            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
36492        );
36493        assert_eq!(
36494            result,
36495            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
36496        );
36497    }
36498
36499    #[test]
36500    fn test_create_table_if_not_exists() {
36501        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
36502        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
36503    }
36504
36505    #[test]
36506    fn test_drop_table() {
36507        let result = roundtrip("DROP TABLE users");
36508        assert_eq!(result, "DROP TABLE users");
36509    }
36510
36511    #[test]
36512    fn test_drop_table_if_exists_cascade() {
36513        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
36514        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
36515    }
36516
36517    #[test]
36518    fn test_alter_table_add_column() {
36519        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
36520        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
36521    }
36522
36523    #[test]
36524    fn test_alter_table_drop_column() {
36525        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
36526        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
36527    }
36528
36529    #[test]
36530    fn test_create_index() {
36531        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
36532        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
36533    }
36534
36535    #[test]
36536    fn test_create_unique_index() {
36537        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
36538        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
36539    }
36540
36541    #[test]
36542    fn test_drop_index() {
36543        let result = roundtrip("DROP INDEX idx_name");
36544        assert_eq!(result, "DROP INDEX idx_name");
36545    }
36546
36547    #[test]
36548    fn test_create_view() {
36549        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
36550        assert_eq!(
36551            result,
36552            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
36553        );
36554    }
36555
36556    #[test]
36557    fn test_drop_view() {
36558        let result = roundtrip("DROP VIEW active_users");
36559        assert_eq!(result, "DROP VIEW active_users");
36560    }
36561
36562    #[test]
36563    fn test_truncate() {
36564        let result = roundtrip("TRUNCATE TABLE users");
36565        assert_eq!(result, "TRUNCATE TABLE users");
36566    }
36567
36568    #[test]
36569    fn test_string_literal_escaping_default() {
36570        // Default: double single quotes
36571        let result = roundtrip("SELECT 'hello'");
36572        assert_eq!(result, "SELECT 'hello'");
36573
36574        // Single quotes are doubled
36575        let result = roundtrip("SELECT 'it''s a test'");
36576        assert_eq!(result, "SELECT 'it''s a test'");
36577    }
36578
36579    #[test]
36580    fn test_not_in_style_prefix_default_generic() {
36581        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
36582        assert_eq!(
36583            result,
36584            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
36585        );
36586    }
36587
36588    #[test]
36589    fn test_not_in_style_infix_generic_override() {
36590        let ast =
36591            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
36592                .unwrap();
36593        let config = GeneratorConfig {
36594            not_in_style: NotInStyle::Infix,
36595            ..Default::default()
36596        };
36597        let mut gen = Generator::with_config(config);
36598        let result = gen.generate(&ast[0]).unwrap();
36599        assert_eq!(
36600            result,
36601            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
36602        );
36603    }
36604
36605    #[test]
36606    fn test_string_literal_escaping_mysql() {
36607        use crate::dialects::DialectType;
36608
36609        let config = GeneratorConfig {
36610            dialect: Some(DialectType::MySQL),
36611            ..Default::default()
36612        };
36613
36614        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36615        let mut gen = Generator::with_config(config.clone());
36616        let result = gen.generate(&ast[0]).unwrap();
36617        assert_eq!(result, "SELECT 'hello'");
36618
36619        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
36620        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36621        let mut gen = Generator::with_config(config.clone());
36622        let result = gen.generate(&ast[0]).unwrap();
36623        assert_eq!(result, "SELECT 'it''s'");
36624    }
36625
36626    #[test]
36627    fn test_string_literal_escaping_postgres() {
36628        use crate::dialects::DialectType;
36629
36630        let config = GeneratorConfig {
36631            dialect: Some(DialectType::PostgreSQL),
36632            ..Default::default()
36633        };
36634
36635        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36636        let mut gen = Generator::with_config(config.clone());
36637        let result = gen.generate(&ast[0]).unwrap();
36638        assert_eq!(result, "SELECT 'hello'");
36639
36640        // PostgreSQL uses doubled quotes for regular strings
36641        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36642        let mut gen = Generator::with_config(config.clone());
36643        let result = gen.generate(&ast[0]).unwrap();
36644        assert_eq!(result, "SELECT 'it''s'");
36645    }
36646
36647    #[test]
36648    fn test_string_literal_escaping_bigquery() {
36649        use crate::dialects::DialectType;
36650
36651        let config = GeneratorConfig {
36652            dialect: Some(DialectType::BigQuery),
36653            ..Default::default()
36654        };
36655
36656        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36657        let mut gen = Generator::with_config(config.clone());
36658        let result = gen.generate(&ast[0]).unwrap();
36659        assert_eq!(result, "SELECT 'hello'");
36660
36661        // BigQuery escapes single quotes with backslash
36662        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36663        let mut gen = Generator::with_config(config.clone());
36664        let result = gen.generate(&ast[0]).unwrap();
36665        assert_eq!(result, "SELECT 'it\\'s'");
36666    }
36667
36668    #[test]
36669    fn test_generate_deep_and_chain_without_stack_growth() {
36670        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
36671            Expression::column("c0"),
36672            Expression::number(0),
36673        )));
36674
36675        for i in 1..2500 {
36676            let predicate = Expression::Eq(Box::new(BinaryOp::new(
36677                Expression::column(format!("c{i}")),
36678                Expression::number(i as i64),
36679            )));
36680            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
36681        }
36682
36683        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
36684        assert!(sql.contains("c2499 = 2499"), "{}", sql);
36685    }
36686
36687    #[test]
36688    fn test_generate_deep_or_chain_without_stack_growth() {
36689        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
36690            Expression::column("c0"),
36691            Expression::number(0),
36692        )));
36693
36694        for i in 1..2500 {
36695            let predicate = Expression::Eq(Box::new(BinaryOp::new(
36696                Expression::column(format!("c{i}")),
36697                Expression::number(i as i64),
36698            )));
36699            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
36700        }
36701
36702        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
36703        assert!(sql.contains("c2499 = 2499"), "{}", sql);
36704    }
36705}