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                    });
3814                }
3815                self.write(&v.this);
3816                Ok(())
3817            }
3818            Expression::Variadic(e) => {
3819                self.write_keyword("VARIADIC");
3820                self.write_space();
3821                self.generate_expression(&e.this)?;
3822                Ok(())
3823            }
3824            Expression::VarMap(e) => self.generate_var_map(e),
3825            Expression::VectorSearch(e) => self.generate_vector_search(e),
3826            Expression::Version(e) => self.generate_version(e),
3827            Expression::ViewAttributeProperty(e) => self.generate_view_attribute_property(e),
3828            Expression::VolatileProperty(e) => self.generate_volatile_property(e),
3829            Expression::WatermarkColumnConstraint(e) => {
3830                self.generate_watermark_column_constraint(e)
3831            }
3832            Expression::Week(e) => self.generate_week(e),
3833            Expression::When(e) => self.generate_when(e),
3834            Expression::Whens(e) => self.generate_whens(e),
3835            Expression::Where(e) => self.generate_where(e),
3836            Expression::WidthBucket(e) => self.generate_width_bucket(e),
3837            Expression::Window(e) => self.generate_window(e),
3838            Expression::WindowSpec(e) => self.generate_window_spec(e),
3839            Expression::WithDataProperty(e) => self.generate_with_data_property(e),
3840            Expression::WithFill(e) => self.generate_with_fill(e),
3841            Expression::WithJournalTableProperty(e) => self.generate_with_journal_table_property(e),
3842            Expression::WithOperator(e) => self.generate_with_operator(e),
3843            Expression::WithProcedureOptions(e) => self.generate_with_procedure_options(e),
3844            Expression::WithSchemaBindingProperty(e) => {
3845                self.generate_with_schema_binding_property(e)
3846            }
3847            Expression::WithSystemVersioningProperty(e) => {
3848                self.generate_with_system_versioning_property(e)
3849            }
3850            Expression::WithTableHint(e) => self.generate_with_table_hint(e),
3851            Expression::XMLElement(e) => self.generate_xml_element(e),
3852            Expression::XMLGet(e) => self.generate_xml_get(e),
3853            Expression::XMLKeyValueOption(e) => self.generate_xml_key_value_option(e),
3854            Expression::XMLTable(e) => self.generate_xml_table(e),
3855            Expression::Xor(e) => self.generate_xor(e),
3856            Expression::Zipf(e) => self.generate_zipf(e),
3857            _ => {
3858                // Fallback for unimplemented expressions
3859                self.write(&format!("/* unimplemented: {:?} */", expr));
3860                Ok(())
3861            }
3862        }
3863    }
3864
3865    fn generate_select(&mut self, select: &Select) -> Result<()> {
3866        use crate::dialects::DialectType;
3867
3868        // Output leading comments before SELECT
3869        for comment in &select.leading_comments {
3870            self.write_formatted_comment(comment);
3871            self.write(" ");
3872        }
3873
3874        // WITH clause
3875        if let Some(with) = &select.with {
3876            self.generate_with(with)?;
3877            if self.config.pretty {
3878                self.write_newline();
3879                self.write_indent();
3880            } else {
3881                self.write_space();
3882            }
3883        }
3884
3885        // Output post-SELECT comments (comments that appeared after SELECT keyword)
3886        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
3887        for comment in &select.post_select_comments {
3888            self.write_formatted_comment(comment);
3889            self.write(" ");
3890        }
3891
3892        self.write_keyword("SELECT");
3893
3894        // Generate query hint if present /*+ ... */
3895        if let Some(hint) = &select.hint {
3896            self.generate_hint(hint)?;
3897        }
3898
3899        // For SQL Server, convert LIMIT to TOP (structural transformation)
3900        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
3901        // TOP clause (SQL Server style - before DISTINCT)
3902        let use_top_from_limit = matches!(self.config.dialect, Some(DialectType::TSQL))
3903            && select.top.is_none()
3904            && select.limit.is_some()
3905            && select.offset.is_none(); // Don't use TOP when there's OFFSET
3906
3907        // For TOP-supporting dialects: DISTINCT before TOP
3908        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
3909        let is_top_dialect = matches!(
3910            self.config.dialect,
3911            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
3912        );
3913        let keep_top_verbatim = !is_top_dialect
3914            && select.limit.is_none()
3915            && select
3916                .top
3917                .as_ref()
3918                .map_or(false, |top| top.percent || top.with_ties);
3919
3920        if select.distinct && (is_top_dialect || select.top.is_some()) {
3921            self.write_space();
3922            self.write_keyword("DISTINCT");
3923        }
3924
3925        if is_top_dialect || keep_top_verbatim {
3926            if let Some(top) = &select.top {
3927                self.write_space();
3928                self.write_keyword("TOP");
3929                if top.parenthesized {
3930                    self.write(" (");
3931                    self.generate_expression(&top.this)?;
3932                    self.write(")");
3933                } else {
3934                    self.write_space();
3935                    self.generate_expression(&top.this)?;
3936                }
3937                if top.percent {
3938                    self.write_space();
3939                    self.write_keyword("PERCENT");
3940                }
3941                if top.with_ties {
3942                    self.write_space();
3943                    self.write_keyword("WITH TIES");
3944                }
3945            } else if use_top_from_limit {
3946                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
3947                if let Some(limit) = &select.limit {
3948                    self.write_space();
3949                    self.write_keyword("TOP");
3950                    // Use parentheses for complex expressions, but not for simple literals
3951                    let is_simple_literal =
3952                        matches!(&limit.this, Expression::Literal(Literal::Number(_)));
3953                    if is_simple_literal {
3954                        self.write_space();
3955                        self.generate_expression(&limit.this)?;
3956                    } else {
3957                        self.write(" (");
3958                        self.generate_expression(&limit.this)?;
3959                        self.write(")");
3960                    }
3961                }
3962            }
3963        }
3964
3965        if select.distinct && !is_top_dialect && select.top.is_none() {
3966            self.write_space();
3967            self.write_keyword("DISTINCT");
3968        }
3969
3970        // DISTINCT ON clause (PostgreSQL)
3971        if let Some(distinct_on) = &select.distinct_on {
3972            self.write_space();
3973            self.write_keyword("ON");
3974            self.write(" (");
3975            for (i, expr) in distinct_on.iter().enumerate() {
3976                if i > 0 {
3977                    self.write(", ");
3978                }
3979                self.generate_expression(expr)?;
3980            }
3981            self.write(")");
3982        }
3983
3984        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
3985        for modifier in &select.operation_modifiers {
3986            self.write_space();
3987            self.write_keyword(modifier);
3988        }
3989
3990        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
3991        if let Some(kind) = &select.kind {
3992            self.write_space();
3993            self.write_keyword("AS");
3994            self.write_space();
3995            self.write_keyword(kind);
3996        }
3997
3998        // Expressions (only if there are any)
3999        if !select.expressions.is_empty() {
4000            if self.config.pretty {
4001                self.write_newline();
4002                self.indent_level += 1;
4003            } else {
4004                self.write_space();
4005            }
4006        }
4007
4008        for (i, expr) in select.expressions.iter().enumerate() {
4009            if i > 0 {
4010                self.write(",");
4011                if self.config.pretty {
4012                    self.write_newline();
4013                } else {
4014                    self.write_space();
4015                }
4016            }
4017            if self.config.pretty {
4018                self.write_indent();
4019            }
4020            self.generate_expression(expr)?;
4021        }
4022
4023        if self.config.pretty && !select.expressions.is_empty() {
4024            self.indent_level -= 1;
4025        }
4026
4027        // INTO clause (SELECT ... INTO table_name)
4028        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4029        if let Some(into) = &select.into {
4030            if self.config.pretty {
4031                self.write_newline();
4032                self.write_indent();
4033            } else {
4034                self.write_space();
4035            }
4036            if into.bulk_collect {
4037                self.write_keyword("BULK COLLECT INTO");
4038            } else {
4039                self.write_keyword("INTO");
4040            }
4041            if into.temporary {
4042                self.write_space();
4043                self.write_keyword("TEMPORARY");
4044            }
4045            if into.unlogged {
4046                self.write_space();
4047                self.write_keyword("UNLOGGED");
4048            }
4049            self.write_space();
4050            // If we have multiple expressions, output them comma-separated
4051            if !into.expressions.is_empty() {
4052                for (i, expr) in into.expressions.iter().enumerate() {
4053                    if i > 0 {
4054                        self.write(", ");
4055                    }
4056                    self.generate_expression(expr)?;
4057                }
4058            } else {
4059                self.generate_expression(&into.this)?;
4060            }
4061        }
4062
4063        // FROM clause
4064        if let Some(from) = &select.from {
4065            if self.config.pretty {
4066                self.write_newline();
4067                self.write_indent();
4068            } else {
4069                self.write_space();
4070            }
4071            self.write_keyword("FROM");
4072            self.write_space();
4073
4074            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4075            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4076            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4077            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4078            let has_tablesample = from
4079                .expressions
4080                .iter()
4081                .any(|e| matches!(e, Expression::TableSample(_)));
4082            let is_cross_join_dialect = matches!(
4083                self.config.dialect,
4084                Some(DialectType::BigQuery)
4085                    | Some(DialectType::Hive)
4086                    | Some(DialectType::Spark)
4087                    | Some(DialectType::Databricks)
4088                    | Some(DialectType::SQLite)
4089                    | Some(DialectType::ClickHouse)
4090            );
4091            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4092            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4093            let source_is_same_as_target = self.config.source_dialect.is_some()
4094                && self.config.source_dialect == self.config.dialect;
4095            let source_is_cross_join_dialect = matches!(
4096                self.config.source_dialect,
4097                Some(DialectType::BigQuery)
4098                    | Some(DialectType::Hive)
4099                    | Some(DialectType::Spark)
4100                    | Some(DialectType::Databricks)
4101                    | Some(DialectType::SQLite)
4102                    | Some(DialectType::ClickHouse)
4103            );
4104            let use_cross_join = !has_tablesample
4105                && is_cross_join_dialect
4106                && (source_is_same_as_target
4107                    || source_is_cross_join_dialect
4108                    || self.config.source_dialect.is_none());
4109
4110            // Snowflake wraps standalone VALUES in FROM clause with parentheses
4111            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
4112
4113            for (i, expr) in from.expressions.iter().enumerate() {
4114                if i > 0 {
4115                    if use_cross_join {
4116                        self.write(" CROSS JOIN ");
4117                    } else {
4118                        self.write(", ");
4119                    }
4120                }
4121                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
4122                    self.write("(");
4123                    self.generate_expression(expr)?;
4124                    self.write(")");
4125                } else {
4126                    self.generate_expression(expr)?;
4127                }
4128            }
4129        }
4130
4131        // JOINs - handle nested join structure for pretty printing
4132        // Deferred-condition joins "own" the non-deferred joins that follow them
4133        // until the next deferred join or end of list
4134        if self.config.pretty {
4135            self.generate_joins_with_nesting(&select.joins)?;
4136        } else {
4137            for join in &select.joins {
4138                self.generate_join(join)?;
4139            }
4140            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4141            for join in select.joins.iter().rev() {
4142                if join.deferred_condition {
4143                    self.generate_join_condition(join)?;
4144                }
4145            }
4146        }
4147
4148        // LATERAL VIEW clauses (Hive/Spark)
4149        for lateral_view in &select.lateral_views {
4150            self.generate_lateral_view(lateral_view)?;
4151        }
4152
4153        // PREWHERE (ClickHouse)
4154        if let Some(prewhere) = &select.prewhere {
4155            self.write_clause_condition("PREWHERE", prewhere)?;
4156        }
4157
4158        // WHERE
4159        if let Some(where_clause) = &select.where_clause {
4160            self.write_clause_condition("WHERE", &where_clause.this)?;
4161        }
4162
4163        // CONNECT BY (Oracle hierarchical queries)
4164        if let Some(connect) = &select.connect {
4165            self.generate_connect(connect)?;
4166        }
4167
4168        // GROUP BY
4169        if let Some(group_by) = &select.group_by {
4170            if self.config.pretty {
4171                // Output leading comments on their own lines before GROUP BY
4172                for comment in &group_by.comments {
4173                    self.write_newline();
4174                    self.write_indent();
4175                    self.write_formatted_comment(comment);
4176                }
4177                self.write_newline();
4178                self.write_indent();
4179            } else {
4180                self.write_space();
4181                // In non-pretty mode, output comments inline
4182                for comment in &group_by.comments {
4183                    self.write_formatted_comment(comment);
4184                    self.write_space();
4185                }
4186            }
4187            self.write_keyword("GROUP BY");
4188            // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
4189            match group_by.all {
4190                Some(true) => {
4191                    self.write_space();
4192                    self.write_keyword("ALL");
4193                }
4194                Some(false) => {
4195                    self.write_space();
4196                    self.write_keyword("DISTINCT");
4197                }
4198                None => {}
4199            }
4200            if !group_by.expressions.is_empty() {
4201                // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
4202                // These are represented as Cube/Rollup expressions with empty expressions at the end
4203                let mut trailing_cube = false;
4204                let mut trailing_rollup = false;
4205                let mut plain_expressions: Vec<&Expression> = Vec::new();
4206                let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
4207                let mut cube_expressions: Vec<&Expression> = Vec::new();
4208                let mut rollup_expressions: Vec<&Expression> = Vec::new();
4209
4210                for expr in &group_by.expressions {
4211                    match expr {
4212                        Expression::Cube(c) if c.expressions.is_empty() => {
4213                            trailing_cube = true;
4214                        }
4215                        Expression::Rollup(r) if r.expressions.is_empty() => {
4216                            trailing_rollup = true;
4217                        }
4218                        Expression::Function(f) if f.name == "CUBE" => {
4219                            cube_expressions.push(expr);
4220                        }
4221                        Expression::Function(f) if f.name == "ROLLUP" => {
4222                            rollup_expressions.push(expr);
4223                        }
4224                        Expression::Function(f) if f.name == "GROUPING SETS" => {
4225                            grouping_sets_expressions.push(expr);
4226                        }
4227                        _ => {
4228                            plain_expressions.push(expr);
4229                        }
4230                    }
4231                }
4232
4233                // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
4234                let mut regular_expressions: Vec<&Expression> = Vec::new();
4235                regular_expressions.extend(plain_expressions);
4236                regular_expressions.extend(grouping_sets_expressions);
4237                regular_expressions.extend(cube_expressions);
4238                regular_expressions.extend(rollup_expressions);
4239
4240                if self.config.pretty {
4241                    self.write_newline();
4242                    self.indent_level += 1;
4243                    self.write_indent();
4244                } else {
4245                    self.write_space();
4246                }
4247
4248                for (i, expr) in regular_expressions.iter().enumerate() {
4249                    if i > 0 {
4250                        if self.config.pretty {
4251                            self.write(",");
4252                            self.write_newline();
4253                            self.write_indent();
4254                        } else {
4255                            self.write(", ");
4256                        }
4257                    }
4258                    self.generate_expression(expr)?;
4259                }
4260
4261                if self.config.pretty {
4262                    self.indent_level -= 1;
4263                }
4264
4265                // Output trailing WITH CUBE or WITH ROLLUP
4266                if trailing_cube {
4267                    self.write_space();
4268                    self.write_keyword("WITH CUBE");
4269                } else if trailing_rollup {
4270                    self.write_space();
4271                    self.write_keyword("WITH ROLLUP");
4272                }
4273            }
4274
4275            // ClickHouse: WITH TOTALS
4276            if group_by.totals {
4277                self.write_space();
4278                self.write_keyword("WITH TOTALS");
4279            }
4280        }
4281
4282        // HAVING
4283        if let Some(having) = &select.having {
4284            if self.config.pretty {
4285                // Output leading comments on their own lines before HAVING
4286                for comment in &having.comments {
4287                    self.write_newline();
4288                    self.write_indent();
4289                    self.write_formatted_comment(comment);
4290                }
4291            } else {
4292                for comment in &having.comments {
4293                    self.write_space();
4294                    self.write_formatted_comment(comment);
4295                }
4296            }
4297            self.write_clause_condition("HAVING", &having.this)?;
4298        }
4299
4300        // QUALIFY and WINDOW clause ordering depends on input SQL
4301        if select.qualify_after_window {
4302            // WINDOW before QUALIFY (DuckDB style)
4303            if let Some(windows) = &select.windows {
4304                self.write_window_clause(windows)?;
4305            }
4306            if let Some(qualify) = &select.qualify {
4307                self.write_clause_condition("QUALIFY", &qualify.this)?;
4308            }
4309        } else {
4310            // QUALIFY before WINDOW (Snowflake/BigQuery default)
4311            if let Some(qualify) = &select.qualify {
4312                self.write_clause_condition("QUALIFY", &qualify.this)?;
4313            }
4314            if let Some(windows) = &select.windows {
4315                self.write_window_clause(windows)?;
4316            }
4317        }
4318
4319        // DISTRIBUTE BY (Hive/Spark)
4320        if let Some(distribute_by) = &select.distribute_by {
4321            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
4322        }
4323
4324        // CLUSTER BY (Hive/Spark)
4325        if let Some(cluster_by) = &select.cluster_by {
4326            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
4327        }
4328
4329        // SORT BY (Hive/Spark - comes before ORDER BY)
4330        if let Some(sort_by) = &select.sort_by {
4331            self.write_order_clause("SORT BY", &sort_by.expressions)?;
4332        }
4333
4334        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
4335        if let Some(order_by) = &select.order_by {
4336            if self.config.pretty {
4337                // Output leading comments on their own lines before ORDER BY
4338                for comment in &order_by.comments {
4339                    self.write_newline();
4340                    self.write_indent();
4341                    self.write_formatted_comment(comment);
4342                }
4343            } else {
4344                for comment in &order_by.comments {
4345                    self.write_space();
4346                    self.write_formatted_comment(comment);
4347                }
4348            }
4349            let keyword = if order_by.siblings {
4350                "ORDER SIBLINGS BY"
4351            } else {
4352                "ORDER BY"
4353            };
4354            self.write_order_clause(keyword, &order_by.expressions)?;
4355        }
4356
4357        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
4358        if select.order_by.is_none()
4359            && select.fetch.is_some()
4360            && matches!(
4361                self.config.dialect,
4362                Some(DialectType::TSQL) | Some(DialectType::Fabric)
4363            )
4364        {
4365            if self.config.pretty {
4366                self.write_newline();
4367                self.write_indent();
4368            } else {
4369                self.write_space();
4370            }
4371            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
4372        }
4373
4374        // LIMIT and OFFSET
4375        // PostgreSQL and others use: LIMIT count OFFSET offset
4376        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
4377        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
4378        let is_presto_like = matches!(
4379            self.config.dialect,
4380            Some(DialectType::Presto) | Some(DialectType::Trino)
4381        );
4382
4383        if is_presto_like && select.offset.is_some() {
4384            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
4385            if let Some(offset) = &select.offset {
4386                if self.config.pretty {
4387                    self.write_newline();
4388                    self.write_indent();
4389                } else {
4390                    self.write_space();
4391                }
4392                self.write_keyword("OFFSET");
4393                self.write_space();
4394                self.write_limit_expr(&offset.this)?;
4395                if offset.rows == Some(true) {
4396                    self.write_space();
4397                    self.write_keyword("ROWS");
4398                }
4399            }
4400            if let Some(limit) = &select.limit {
4401                if self.config.pretty {
4402                    self.write_newline();
4403                    self.write_indent();
4404                } else {
4405                    self.write_space();
4406                }
4407                self.write_keyword("LIMIT");
4408                self.write_space();
4409                self.write_limit_expr(&limit.this)?;
4410                if limit.percent {
4411                    self.write_space();
4412                    self.write_keyword("PERCENT");
4413                }
4414                // Emit any comments that were captured from before the LIMIT keyword
4415                for comment in &limit.comments {
4416                    self.write(" ");
4417                    self.write_formatted_comment(comment);
4418                }
4419            }
4420        } else {
4421            // Check if FETCH will be converted to LIMIT (used for ordering)
4422            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
4423                !fetch.percent
4424                    && !fetch.with_ties
4425                    && fetch.count.is_some()
4426                    && matches!(
4427                        self.config.dialect,
4428                        Some(DialectType::Spark)
4429                            | Some(DialectType::Hive)
4430                            | Some(DialectType::DuckDB)
4431                            | Some(DialectType::SQLite)
4432                            | Some(DialectType::MySQL)
4433                            | Some(DialectType::BigQuery)
4434                            | Some(DialectType::Databricks)
4435                            | Some(DialectType::StarRocks)
4436                            | Some(DialectType::Doris)
4437                            | Some(DialectType::Athena)
4438                            | Some(DialectType::ClickHouse)
4439                            | Some(DialectType::Redshift)
4440                    )
4441            });
4442
4443            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
4444            if let Some(limit) = &select.limit {
4445                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
4446                if !matches!(self.config.dialect, Some(DialectType::TSQL)) {
4447                    if self.config.pretty {
4448                        self.write_newline();
4449                        self.write_indent();
4450                    } else {
4451                        self.write_space();
4452                    }
4453                    self.write_keyword("LIMIT");
4454                    self.write_space();
4455                    self.write_limit_expr(&limit.this)?;
4456                    if limit.percent {
4457                        self.write_space();
4458                        self.write_keyword("PERCENT");
4459                    }
4460                    // Emit any comments that were captured from before the LIMIT keyword
4461                    for comment in &limit.comments {
4462                        self.write(" ");
4463                        self.write_formatted_comment(comment);
4464                    }
4465                }
4466            }
4467
4468            // Convert TOP to LIMIT for non-TOP dialects
4469            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
4470                if let Some(top) = &select.top {
4471                    if !top.percent && !top.with_ties {
4472                        if self.config.pretty {
4473                            self.write_newline();
4474                            self.write_indent();
4475                        } else {
4476                            self.write_space();
4477                        }
4478                        self.write_keyword("LIMIT");
4479                        self.write_space();
4480                        self.generate_expression(&top.this)?;
4481                    }
4482                }
4483            }
4484
4485            // If FETCH will be converted to LIMIT and there's also OFFSET,
4486            // emit LIMIT from FETCH BEFORE the OFFSET
4487            if fetch_as_limit && select.offset.is_some() {
4488                if let Some(fetch) = &select.fetch {
4489                    if self.config.pretty {
4490                        self.write_newline();
4491                        self.write_indent();
4492                    } else {
4493                        self.write_space();
4494                    }
4495                    self.write_keyword("LIMIT");
4496                    self.write_space();
4497                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4498                }
4499            }
4500
4501            // OFFSET
4502            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
4503            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
4504            if let Some(offset) = &select.offset {
4505                if self.config.pretty {
4506                    self.write_newline();
4507                    self.write_indent();
4508                } else {
4509                    self.write_space();
4510                }
4511                if matches!(self.config.dialect, Some(DialectType::TSQL)) {
4512                    // SQL Server 2012+ OFFSET ... FETCH syntax
4513                    self.write_keyword("OFFSET");
4514                    self.write_space();
4515                    self.write_limit_expr(&offset.this)?;
4516                    self.write_space();
4517                    self.write_keyword("ROWS");
4518                    // If there was a LIMIT, use FETCH NEXT ... ROWS ONLY
4519                    if let Some(limit) = &select.limit {
4520                        self.write_space();
4521                        self.write_keyword("FETCH NEXT");
4522                        self.write_space();
4523                        self.write_limit_expr(&limit.this)?;
4524                        self.write_space();
4525                        self.write_keyword("ROWS ONLY");
4526                    }
4527                } else {
4528                    self.write_keyword("OFFSET");
4529                    self.write_space();
4530                    self.write_limit_expr(&offset.this)?;
4531                    // Output ROWS keyword if it was in the original SQL
4532                    if offset.rows == Some(true) {
4533                        self.write_space();
4534                        self.write_keyword("ROWS");
4535                    }
4536                }
4537            }
4538        }
4539
4540        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
4541        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4542            if let Some(limit_by) = &select.limit_by {
4543                if !limit_by.is_empty() {
4544                    self.write_space();
4545                    self.write_keyword("BY");
4546                    self.write_space();
4547                    for (i, expr) in limit_by.iter().enumerate() {
4548                        if i > 0 {
4549                            self.write(", ");
4550                        }
4551                        self.generate_expression(expr)?;
4552                    }
4553                }
4554            }
4555        }
4556
4557        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
4558        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4559            if let Some(settings) = &select.settings {
4560                if self.config.pretty {
4561                    self.write_newline();
4562                    self.write_indent();
4563                } else {
4564                    self.write_space();
4565                }
4566                self.write_keyword("SETTINGS");
4567                self.write_space();
4568                for (i, expr) in settings.iter().enumerate() {
4569                    if i > 0 {
4570                        self.write(", ");
4571                    }
4572                    self.generate_expression(expr)?;
4573                }
4574            }
4575
4576            if let Some(format_expr) = &select.format {
4577                if self.config.pretty {
4578                    self.write_newline();
4579                    self.write_indent();
4580                } else {
4581                    self.write_space();
4582                }
4583                self.write_keyword("FORMAT");
4584                self.write_space();
4585                self.generate_expression(format_expr)?;
4586            }
4587        }
4588
4589        // FETCH FIRST/NEXT
4590        if let Some(fetch) = &select.fetch {
4591            // Check if we already emitted LIMIT from FETCH before OFFSET
4592            let fetch_already_as_limit = select.offset.is_some()
4593                && !fetch.percent
4594                && !fetch.with_ties
4595                && fetch.count.is_some()
4596                && matches!(
4597                    self.config.dialect,
4598                    Some(DialectType::Spark)
4599                        | Some(DialectType::Hive)
4600                        | Some(DialectType::DuckDB)
4601                        | Some(DialectType::SQLite)
4602                        | Some(DialectType::MySQL)
4603                        | Some(DialectType::BigQuery)
4604                        | Some(DialectType::Databricks)
4605                        | Some(DialectType::StarRocks)
4606                        | Some(DialectType::Doris)
4607                        | Some(DialectType::Athena)
4608                        | Some(DialectType::ClickHouse)
4609                        | Some(DialectType::Redshift)
4610                );
4611
4612            if fetch_already_as_limit {
4613                // Already emitted as LIMIT before OFFSET, skip
4614            } else {
4615                if self.config.pretty {
4616                    self.write_newline();
4617                    self.write_indent();
4618                } else {
4619                    self.write_space();
4620                }
4621
4622                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
4623                let use_limit = !fetch.percent
4624                    && !fetch.with_ties
4625                    && fetch.count.is_some()
4626                    && matches!(
4627                        self.config.dialect,
4628                        Some(DialectType::Spark)
4629                            | Some(DialectType::Hive)
4630                            | Some(DialectType::DuckDB)
4631                            | Some(DialectType::SQLite)
4632                            | Some(DialectType::MySQL)
4633                            | Some(DialectType::BigQuery)
4634                            | Some(DialectType::Databricks)
4635                            | Some(DialectType::StarRocks)
4636                            | Some(DialectType::Doris)
4637                            | Some(DialectType::Athena)
4638                            | Some(DialectType::ClickHouse)
4639                            | Some(DialectType::Redshift)
4640                    );
4641
4642                if use_limit {
4643                    self.write_keyword("LIMIT");
4644                    self.write_space();
4645                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4646                } else {
4647                    self.write_keyword("FETCH");
4648                    self.write_space();
4649                    self.write_keyword(&fetch.direction);
4650                    if let Some(ref count) = fetch.count {
4651                        self.write_space();
4652                        self.generate_expression(count)?;
4653                    }
4654                    if fetch.percent {
4655                        self.write_space();
4656                        self.write_keyword("PERCENT");
4657                    }
4658                    if fetch.rows {
4659                        self.write_space();
4660                        self.write_keyword("ROWS");
4661                    }
4662                    if fetch.with_ties {
4663                        self.write_space();
4664                        self.write_keyword("WITH TIES");
4665                    } else {
4666                        self.write_space();
4667                        self.write_keyword("ONLY");
4668                    }
4669                }
4670            } // close fetch_already_as_limit else
4671        }
4672
4673        // SAMPLE / TABLESAMPLE
4674        if let Some(sample) = &select.sample {
4675            use crate::dialects::DialectType;
4676            if self.config.pretty {
4677                self.write_newline();
4678            } else {
4679                self.write_space();
4680            }
4681
4682            if sample.is_using_sample {
4683                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
4684                self.write_keyword("USING SAMPLE");
4685                self.generate_sample_body(sample)?;
4686            } else {
4687                self.write_keyword("TABLESAMPLE");
4688
4689                // Snowflake defaults to BERNOULLI when no explicit method is given
4690                let snowflake_bernoulli =
4691                    matches!(self.config.dialect, Some(DialectType::Snowflake))
4692                        && !sample.explicit_method;
4693                if snowflake_bernoulli {
4694                    self.write_space();
4695                    self.write_keyword("BERNOULLI");
4696                }
4697
4698                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
4699                if matches!(sample.method, SampleMethod::Bucket) {
4700                    self.write_space();
4701                    self.write("(");
4702                    self.write_keyword("BUCKET");
4703                    self.write_space();
4704                    if let Some(ref num) = sample.bucket_numerator {
4705                        self.generate_expression(num)?;
4706                    }
4707                    self.write_space();
4708                    self.write_keyword("OUT OF");
4709                    self.write_space();
4710                    if let Some(ref denom) = sample.bucket_denominator {
4711                        self.generate_expression(denom)?;
4712                    }
4713                    if let Some(ref field) = sample.bucket_field {
4714                        self.write_space();
4715                        self.write_keyword("ON");
4716                        self.write_space();
4717                        self.generate_expression(field)?;
4718                    }
4719                    self.write(")");
4720                } else if sample.unit_after_size {
4721                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
4722                    if sample.explicit_method && sample.method_before_size {
4723                        self.write_space();
4724                        match sample.method {
4725                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4726                            SampleMethod::System => self.write_keyword("SYSTEM"),
4727                            SampleMethod::Block => self.write_keyword("BLOCK"),
4728                            SampleMethod::Row => self.write_keyword("ROW"),
4729                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4730                            _ => {}
4731                        }
4732                    }
4733                    self.write(" (");
4734                    self.generate_expression(&sample.size)?;
4735                    self.write_space();
4736                    match sample.method {
4737                        SampleMethod::Percent => self.write_keyword("PERCENT"),
4738                        SampleMethod::Row => self.write_keyword("ROWS"),
4739                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
4740                        _ => {
4741                            self.write_keyword("PERCENT");
4742                        }
4743                    }
4744                    self.write(")");
4745                } else {
4746                    // Syntax: TABLESAMPLE METHOD (size)
4747                    self.write_space();
4748                    match sample.method {
4749                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4750                        SampleMethod::System => self.write_keyword("SYSTEM"),
4751                        SampleMethod::Block => self.write_keyword("BLOCK"),
4752                        SampleMethod::Row => self.write_keyword("ROW"),
4753                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
4754                        SampleMethod::Bucket => {}
4755                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4756                    }
4757                    self.write(" (");
4758                    self.generate_expression(&sample.size)?;
4759                    if matches!(sample.method, SampleMethod::Percent) {
4760                        self.write_space();
4761                        self.write_keyword("PERCENT");
4762                    }
4763                    self.write(")");
4764                }
4765            }
4766
4767            if let Some(seed) = &sample.seed {
4768                self.write_space();
4769                // Databricks/Spark use REPEATABLE, not SEED
4770                let use_seed = sample.use_seed_keyword
4771                    && !matches!(
4772                        self.config.dialect,
4773                        Some(crate::dialects::DialectType::Databricks)
4774                            | Some(crate::dialects::DialectType::Spark)
4775                    );
4776                if use_seed {
4777                    self.write_keyword("SEED");
4778                } else {
4779                    self.write_keyword("REPEATABLE");
4780                }
4781                self.write(" (");
4782                self.generate_expression(seed)?;
4783                self.write(")");
4784            }
4785        }
4786
4787        // FOR UPDATE/SHARE locks
4788        // Skip locking clauses for dialects that don't support them
4789        if self.config.locking_reads_supported {
4790            for lock in &select.locks {
4791                if self.config.pretty {
4792                    self.write_newline();
4793                    self.write_indent();
4794                } else {
4795                    self.write_space();
4796                }
4797                self.generate_lock(lock)?;
4798            }
4799        }
4800
4801        // FOR XML clause (T-SQL)
4802        if !select.for_xml.is_empty() {
4803            if self.config.pretty {
4804                self.write_newline();
4805                self.write_indent();
4806            } else {
4807                self.write_space();
4808            }
4809            self.write_keyword("FOR XML");
4810            for (i, opt) in select.for_xml.iter().enumerate() {
4811                if self.config.pretty {
4812                    if i > 0 {
4813                        self.write(",");
4814                    }
4815                    self.write_newline();
4816                    self.write_indent();
4817                    self.write("  "); // extra indent for options
4818                } else {
4819                    if i > 0 {
4820                        self.write(",");
4821                    }
4822                    self.write_space();
4823                }
4824                self.generate_for_xml_option(opt)?;
4825            }
4826        }
4827
4828        // TSQL: OPTION clause
4829        if let Some(ref option) = select.option {
4830            if matches!(
4831                self.config.dialect,
4832                Some(crate::dialects::DialectType::TSQL)
4833                    | Some(crate::dialects::DialectType::Fabric)
4834            ) {
4835                self.write_space();
4836                self.write(option);
4837            }
4838        }
4839
4840        Ok(())
4841    }
4842
4843    /// Generate a single FOR XML option
4844    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
4845        match opt {
4846            Expression::QueryOption(qo) => {
4847                // Extract the option name from Var
4848                if let Expression::Var(var) = &*qo.this {
4849                    self.write(&var.this);
4850                } else {
4851                    self.generate_expression(&qo.this)?;
4852                }
4853                // If there's an expression (like PATH('element')), output it in parens
4854                if let Some(expr) = &qo.expression {
4855                    self.write("(");
4856                    self.generate_expression(expr)?;
4857                    self.write(")");
4858                }
4859            }
4860            _ => {
4861                self.generate_expression(opt)?;
4862            }
4863        }
4864        Ok(())
4865    }
4866
4867    fn generate_with(&mut self, with: &With) -> Result<()> {
4868        use crate::dialects::DialectType;
4869
4870        // Output leading comments before WITH
4871        for comment in &with.leading_comments {
4872            self.write_formatted_comment(comment);
4873            self.write(" ");
4874        }
4875        self.write_keyword("WITH");
4876        if with.recursive && self.config.cte_recursive_keyword_required {
4877            self.write_space();
4878            self.write_keyword("RECURSIVE");
4879        }
4880        self.write_space();
4881
4882        // BigQuery doesn't support column aliases in CTE definitions
4883        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
4884
4885        for (i, cte) in with.ctes.iter().enumerate() {
4886            if i > 0 {
4887                self.write(",");
4888                if self.config.pretty {
4889                    self.write_space();
4890                } else {
4891                    self.write(" ");
4892                }
4893            }
4894            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
4895                self.generate_expression(&cte.this)?;
4896                self.write_space();
4897                self.write_keyword("AS");
4898                self.write_space();
4899                self.generate_identifier(&cte.alias)?;
4900                continue;
4901            }
4902            self.generate_identifier(&cte.alias)?;
4903            // Output CTE comments after alias name, before AS
4904            for comment in &cte.comments {
4905                self.write_space();
4906                self.write_formatted_comment(comment);
4907            }
4908            if !cte.columns.is_empty() && !skip_cte_columns {
4909                self.write("(");
4910                for (j, col) in cte.columns.iter().enumerate() {
4911                    if j > 0 {
4912                        self.write(", ");
4913                    }
4914                    self.generate_identifier(col)?;
4915                }
4916                self.write(")");
4917            }
4918            // USING KEY (columns) for DuckDB recursive CTEs
4919            if !cte.key_expressions.is_empty() {
4920                self.write_space();
4921                self.write_keyword("USING KEY");
4922                self.write(" (");
4923                for (i, key) in cte.key_expressions.iter().enumerate() {
4924                    if i > 0 {
4925                        self.write(", ");
4926                    }
4927                    self.generate_identifier(key)?;
4928                }
4929                self.write(")");
4930            }
4931            self.write_space();
4932            self.write_keyword("AS");
4933            // MATERIALIZED / NOT MATERIALIZED
4934            if let Some(materialized) = cte.materialized {
4935                self.write_space();
4936                if materialized {
4937                    self.write_keyword("MATERIALIZED");
4938                } else {
4939                    self.write_keyword("NOT MATERIALIZED");
4940                }
4941            }
4942            self.write(" (");
4943            if self.config.pretty {
4944                self.write_newline();
4945                self.indent_level += 1;
4946                self.write_indent();
4947            }
4948            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
4949            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
4950            let wrap_values_in_select = matches!(
4951                self.config.dialect,
4952                Some(DialectType::Spark) | Some(DialectType::Databricks)
4953            ) && matches!(&cte.this, Expression::Values(_));
4954
4955            if wrap_values_in_select {
4956                self.write_keyword("SELECT");
4957                self.write(" * ");
4958                self.write_keyword("FROM");
4959                self.write_space();
4960            }
4961            self.generate_expression(&cte.this)?;
4962            if self.config.pretty {
4963                self.write_newline();
4964                self.indent_level -= 1;
4965                self.write_indent();
4966            }
4967            self.write(")");
4968        }
4969
4970        // Generate SEARCH/CYCLE clause if present
4971        if let Some(search) = &with.search {
4972            self.write_space();
4973            self.generate_expression(search)?;
4974        }
4975
4976        Ok(())
4977    }
4978
4979    /// Generate joins with proper nesting structure for pretty printing.
4980    /// Deferred-condition joins "own" the non-deferred joins that follow them
4981    /// within the same nesting_group.
4982    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
4983        let mut i = 0;
4984        while i < joins.len() {
4985            if joins[i].deferred_condition {
4986                let parent_group = joins[i].nesting_group;
4987
4988                // This join owns the following non-deferred joins in the same nesting_group
4989                // First output the join keyword and table (without condition)
4990                self.generate_join_without_condition(&joins[i])?;
4991
4992                // Find the range of child joins: same nesting_group and not deferred
4993                let child_start = i + 1;
4994                let mut child_end = child_start;
4995                while child_end < joins.len()
4996                    && !joins[child_end].deferred_condition
4997                    && joins[child_end].nesting_group == parent_group
4998                {
4999                    child_end += 1;
5000                }
5001
5002                // Output child joins with extra indentation
5003                if child_start < child_end {
5004                    self.indent_level += 1;
5005                    for j in child_start..child_end {
5006                        self.generate_join(&joins[j])?;
5007                    }
5008                    self.indent_level -= 1;
5009                }
5010
5011                // Output the deferred condition at the parent level
5012                self.generate_join_condition(&joins[i])?;
5013
5014                i = child_end;
5015            } else {
5016                // Regular join (no nesting)
5017                self.generate_join(&joins[i])?;
5018                i += 1;
5019            }
5020        }
5021        Ok(())
5022    }
5023
5024    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5025    /// Used for deferred-condition joins where the condition is output after child joins.
5026    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5027        // Save and temporarily clear the condition to prevent generate_join from outputting it
5028        // We achieve this by creating a modified copy
5029        let mut join_copy = join.clone();
5030        join_copy.on = None;
5031        join_copy.using = Vec::new();
5032        join_copy.deferred_condition = false;
5033        self.generate_join(&join_copy)
5034    }
5035
5036    fn generate_join(&mut self, join: &Join) -> Result<()> {
5037        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5038        if join.kind == JoinKind::Implicit {
5039            self.write(",");
5040            if self.config.pretty {
5041                self.write_newline();
5042                self.write_indent();
5043            } else {
5044                self.write_space();
5045            }
5046            self.generate_expression(&join.this)?;
5047            return Ok(());
5048        }
5049
5050        if self.config.pretty {
5051            self.write_newline();
5052            self.write_indent();
5053        } else {
5054            self.write_space();
5055        }
5056
5057        // Helper: format hint suffix (e.g., " LOOP" or "")
5058        // Only include join hints for dialects that support them
5059        let hint_str = if self.config.join_hints {
5060            join.join_hint
5061                .as_ref()
5062                .map(|h| format!(" {}", h))
5063                .unwrap_or_default()
5064        } else {
5065            String::new()
5066        };
5067
5068        let clickhouse_join_keyword =
5069            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5070                if let Some(hint) = &join.join_hint {
5071                    let mut global = false;
5072                    let mut strictness: Option<&'static str> = None;
5073                    for part in hint.split_whitespace() {
5074                        match part.to_uppercase().as_str() {
5075                            "GLOBAL" => global = true,
5076                            "ANY" => strictness = Some("ANY"),
5077                            "ASOF" => strictness = Some("ASOF"),
5078                            "SEMI" => strictness = Some("SEMI"),
5079                            "ANTI" => strictness = Some("ANTI"),
5080                            _ => {}
5081                        }
5082                    }
5083
5084                    if global || strictness.is_some() {
5085                        let join_type = match join.kind {
5086                            JoinKind::Left => {
5087                                if join.use_outer_keyword {
5088                                    "LEFT OUTER"
5089                                } else if join.use_inner_keyword {
5090                                    "LEFT INNER"
5091                                } else {
5092                                    "LEFT"
5093                                }
5094                            }
5095                            JoinKind::Right => {
5096                                if join.use_outer_keyword {
5097                                    "RIGHT OUTER"
5098                                } else if join.use_inner_keyword {
5099                                    "RIGHT INNER"
5100                                } else {
5101                                    "RIGHT"
5102                                }
5103                            }
5104                            JoinKind::Full => {
5105                                if join.use_outer_keyword {
5106                                    "FULL OUTER"
5107                                } else {
5108                                    "FULL"
5109                                }
5110                            }
5111                            JoinKind::Inner => {
5112                                if join.use_inner_keyword {
5113                                    "INNER"
5114                                } else {
5115                                    ""
5116                                }
5117                            }
5118                            _ => "",
5119                        };
5120
5121                        let mut parts = Vec::new();
5122                        if global {
5123                            parts.push("GLOBAL");
5124                        }
5125                        if !join_type.is_empty() {
5126                            parts.push(join_type);
5127                        }
5128                        if let Some(strict) = strictness {
5129                            parts.push(strict);
5130                        }
5131                        parts.push("JOIN");
5132                        Some(parts.join(" "))
5133                    } else {
5134                        None
5135                    }
5136                } else {
5137                    None
5138                }
5139            } else {
5140                None
5141            };
5142
5143        // Output any comments associated with this join
5144        // In pretty mode, comments go on their own line before the join keyword
5145        // In non-pretty mode, comments go inline before the join keyword
5146        if !join.comments.is_empty() {
5147            if self.config.pretty {
5148                // In pretty mode, go back before the newline+indent we just wrote
5149                // and output comments on their own lines
5150                // We need to output comments BEFORE the join keyword on separate lines
5151                // Trim the trailing newline+indent we already wrote
5152                let trimmed = self.output.trim_end().len();
5153                self.output.truncate(trimmed);
5154                for comment in &join.comments {
5155                    self.write_newline();
5156                    self.write_indent();
5157                    self.write_formatted_comment(comment);
5158                }
5159                self.write_newline();
5160                self.write_indent();
5161            } else {
5162                for comment in &join.comments {
5163                    self.write_formatted_comment(comment);
5164                    self.write_space();
5165                }
5166            }
5167        }
5168
5169        let directed_str = if join.directed { " DIRECTED" } else { "" };
5170
5171        if let Some(keyword) = clickhouse_join_keyword {
5172            self.write_keyword(&keyword);
5173        } else {
5174            match join.kind {
5175                JoinKind::Inner => {
5176                    if join.use_inner_keyword {
5177                        self.write_keyword(&format!("INNER{}{} JOIN", hint_str, directed_str));
5178                    } else {
5179                        self.write_keyword(&format!(
5180                            "{}{}JOIN",
5181                            if hint_str.is_empty() {
5182                                String::new()
5183                            } else {
5184                                format!("{} ", hint_str.trim())
5185                            },
5186                            if directed_str.is_empty() {
5187                                ""
5188                            } else {
5189                                "DIRECTED "
5190                            }
5191                        ));
5192                    }
5193                }
5194                JoinKind::Left => {
5195                    if join.use_outer_keyword {
5196                        self.write_keyword(&format!("LEFT OUTER{}{} JOIN", hint_str, directed_str));
5197                    } else if join.use_inner_keyword {
5198                        self.write_keyword(&format!("LEFT INNER{}{} JOIN", hint_str, directed_str));
5199                    } else {
5200                        self.write_keyword(&format!("LEFT{}{} JOIN", hint_str, directed_str));
5201                    }
5202                }
5203                JoinKind::Right => {
5204                    if join.use_outer_keyword {
5205                        self.write_keyword(&format!(
5206                            "RIGHT OUTER{}{} JOIN",
5207                            hint_str, directed_str
5208                        ));
5209                    } else if join.use_inner_keyword {
5210                        self.write_keyword(&format!(
5211                            "RIGHT INNER{}{} JOIN",
5212                            hint_str, directed_str
5213                        ));
5214                    } else {
5215                        self.write_keyword(&format!("RIGHT{}{} JOIN", hint_str, directed_str));
5216                    }
5217                }
5218                JoinKind::Full => {
5219                    if join.use_outer_keyword {
5220                        self.write_keyword(&format!("FULL OUTER{}{} JOIN", hint_str, directed_str));
5221                    } else {
5222                        self.write_keyword(&format!("FULL{}{} JOIN", hint_str, directed_str));
5223                    }
5224                }
5225                JoinKind::Outer => self.write_keyword(&format!("OUTER{} JOIN", directed_str)),
5226                JoinKind::Cross => self.write_keyword(&format!("CROSS{} JOIN", directed_str)),
5227                JoinKind::Natural => {
5228                    if join.use_inner_keyword {
5229                        self.write_keyword(&format!("NATURAL INNER{} JOIN", directed_str));
5230                    } else {
5231                        self.write_keyword(&format!("NATURAL{} JOIN", directed_str));
5232                    }
5233                }
5234                JoinKind::NaturalLeft => {
5235                    if join.use_outer_keyword {
5236                        self.write_keyword(&format!("NATURAL LEFT OUTER{} JOIN", directed_str));
5237                    } else {
5238                        self.write_keyword(&format!("NATURAL LEFT{} JOIN", directed_str));
5239                    }
5240                }
5241                JoinKind::NaturalRight => {
5242                    if join.use_outer_keyword {
5243                        self.write_keyword(&format!("NATURAL RIGHT OUTER{} JOIN", directed_str));
5244                    } else {
5245                        self.write_keyword(&format!("NATURAL RIGHT{} JOIN", directed_str));
5246                    }
5247                }
5248                JoinKind::NaturalFull => {
5249                    if join.use_outer_keyword {
5250                        self.write_keyword(&format!("NATURAL FULL OUTER{} JOIN", directed_str));
5251                    } else {
5252                        self.write_keyword(&format!("NATURAL FULL{} JOIN", directed_str));
5253                    }
5254                }
5255                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
5256                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
5257                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
5258                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
5259                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
5260                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
5261                JoinKind::CrossApply => {
5262                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL dialects
5263                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5264                        self.write_keyword("CROSS APPLY");
5265                    } else {
5266                        self.write_keyword("INNER JOIN LATERAL");
5267                    }
5268                }
5269                JoinKind::OuterApply => {
5270                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL dialects
5271                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5272                        self.write_keyword("OUTER APPLY");
5273                    } else {
5274                        self.write_keyword("LEFT JOIN LATERAL");
5275                    }
5276                }
5277                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
5278                JoinKind::AsOfLeft => {
5279                    if join.use_outer_keyword {
5280                        self.write_keyword("ASOF LEFT OUTER JOIN");
5281                    } else {
5282                        self.write_keyword("ASOF LEFT JOIN");
5283                    }
5284                }
5285                JoinKind::AsOfRight => {
5286                    if join.use_outer_keyword {
5287                        self.write_keyword("ASOF RIGHT OUTER JOIN");
5288                    } else {
5289                        self.write_keyword("ASOF RIGHT JOIN");
5290                    }
5291                }
5292                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
5293                JoinKind::LeftLateral => {
5294                    if join.use_outer_keyword {
5295                        self.write_keyword("LEFT OUTER LATERAL JOIN");
5296                    } else {
5297                        self.write_keyword("LEFT LATERAL JOIN");
5298                    }
5299                }
5300                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
5301                JoinKind::Implicit => {
5302                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
5303                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
5304                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
5305                    use crate::dialects::DialectType;
5306                    let is_cj_dialect = matches!(
5307                        self.config.dialect,
5308                        Some(DialectType::BigQuery)
5309                            | Some(DialectType::Hive)
5310                            | Some(DialectType::Spark)
5311                            | Some(DialectType::Databricks)
5312                    );
5313                    let source_is_same = self.config.source_dialect.is_some()
5314                        && self.config.source_dialect == self.config.dialect;
5315                    let source_is_cj = matches!(
5316                        self.config.source_dialect,
5317                        Some(DialectType::BigQuery)
5318                            | Some(DialectType::Hive)
5319                            | Some(DialectType::Spark)
5320                            | Some(DialectType::Databricks)
5321                    );
5322                    if is_cj_dialect
5323                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
5324                    {
5325                        self.write_keyword("CROSS JOIN");
5326                    } else {
5327                        // Implicit join uses comma: FROM a, b
5328                        // We already wrote a space before the match, so replace with comma
5329                        // by removing trailing space and writing ", "
5330                        self.output.truncate(self.output.trim_end().len());
5331                        self.write(",");
5332                    }
5333                }
5334                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
5335                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
5336                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
5337            }
5338        }
5339
5340        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
5341        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
5342            self.write_space();
5343            match &join.this {
5344                Expression::Tuple(t) => {
5345                    for (i, item) in t.expressions.iter().enumerate() {
5346                        if i > 0 {
5347                            self.write(", ");
5348                        }
5349                        self.generate_expression(item)?;
5350                    }
5351                }
5352                other => {
5353                    self.generate_expression(other)?;
5354                }
5355            }
5356        } else {
5357            self.write_space();
5358            self.generate_expression(&join.this)?;
5359        }
5360
5361        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
5362        if !join.deferred_condition {
5363            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
5364            if let Some(match_cond) = &join.match_condition {
5365                self.write_space();
5366                self.write_keyword("MATCH_CONDITION");
5367                self.write(" (");
5368                self.generate_expression(match_cond)?;
5369                self.write(")");
5370            }
5371
5372            if let Some(on) = &join.on {
5373                if self.config.pretty {
5374                    self.write_newline();
5375                    self.indent_level += 1;
5376                    self.write_indent();
5377                    self.write_keyword("ON");
5378                    self.write_space();
5379                    self.generate_join_on_condition(on)?;
5380                    self.indent_level -= 1;
5381                } else {
5382                    self.write_space();
5383                    self.write_keyword("ON");
5384                    self.write_space();
5385                    self.generate_expression(on)?;
5386                }
5387            }
5388
5389            if !join.using.is_empty() {
5390                if self.config.pretty {
5391                    self.write_newline();
5392                    self.indent_level += 1;
5393                    self.write_indent();
5394                    self.write_keyword("USING");
5395                    self.write(" (");
5396                    for (i, col) in join.using.iter().enumerate() {
5397                        if i > 0 {
5398                            self.write(", ");
5399                        }
5400                        self.generate_identifier(col)?;
5401                    }
5402                    self.write(")");
5403                    self.indent_level -= 1;
5404                } else {
5405                    self.write_space();
5406                    self.write_keyword("USING");
5407                    self.write(" (");
5408                    for (i, col) in join.using.iter().enumerate() {
5409                        if i > 0 {
5410                            self.write(", ");
5411                        }
5412                        self.generate_identifier(col)?;
5413                    }
5414                    self.write(")");
5415                }
5416            }
5417        }
5418
5419        // Generate PIVOT/UNPIVOT expressions that follow this join
5420        for pivot in &join.pivots {
5421            self.write_space();
5422            self.generate_expression(pivot)?;
5423        }
5424
5425        Ok(())
5426    }
5427
5428    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
5429    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
5430        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
5431        if let Some(match_cond) = &join.match_condition {
5432            self.write_space();
5433            self.write_keyword("MATCH_CONDITION");
5434            self.write(" (");
5435            self.generate_expression(match_cond)?;
5436            self.write(")");
5437        }
5438
5439        if let Some(on) = &join.on {
5440            if self.config.pretty {
5441                self.write_newline();
5442                self.indent_level += 1;
5443                self.write_indent();
5444                self.write_keyword("ON");
5445                self.write_space();
5446                // In pretty mode, split AND conditions onto separate lines
5447                self.generate_join_on_condition(on)?;
5448                self.indent_level -= 1;
5449            } else {
5450                self.write_space();
5451                self.write_keyword("ON");
5452                self.write_space();
5453                self.generate_expression(on)?;
5454            }
5455        }
5456
5457        if !join.using.is_empty() {
5458            if self.config.pretty {
5459                self.write_newline();
5460                self.indent_level += 1;
5461                self.write_indent();
5462                self.write_keyword("USING");
5463                self.write(" (");
5464                for (i, col) in join.using.iter().enumerate() {
5465                    if i > 0 {
5466                        self.write(", ");
5467                    }
5468                    self.generate_identifier(col)?;
5469                }
5470                self.write(")");
5471                self.indent_level -= 1;
5472            } else {
5473                self.write_space();
5474                self.write_keyword("USING");
5475                self.write(" (");
5476                for (i, col) in join.using.iter().enumerate() {
5477                    if i > 0 {
5478                        self.write(", ");
5479                    }
5480                    self.generate_identifier(col)?;
5481                }
5482                self.write(")");
5483            }
5484        }
5485
5486        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
5487        for pivot in &join.pivots {
5488            self.write_space();
5489            self.generate_expression(pivot)?;
5490        }
5491
5492        Ok(())
5493    }
5494
5495    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
5496    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
5497        if let Expression::And(and_op) = expr {
5498            if let Some(conditions) = self.flatten_connector_terms(and_op, ConnectorOperator::And) {
5499                self.generate_expression(conditions[0])?;
5500                for condition in conditions.iter().skip(1) {
5501                    self.write_newline();
5502                    self.write_indent();
5503                    self.write_keyword("AND");
5504                    self.write_space();
5505                    self.generate_expression(condition)?;
5506                }
5507                return Ok(());
5508            }
5509        }
5510
5511        self.generate_expression(expr)
5512    }
5513
5514    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
5515        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
5516        self.write("(");
5517        self.generate_expression(&jt.left)?;
5518
5519        // Generate all joins
5520        for join in &jt.joins {
5521            self.generate_join(join)?;
5522        }
5523
5524        // Generate LATERAL VIEW clauses (Hive/Spark)
5525        for lv in &jt.lateral_views {
5526            self.generate_lateral_view(lv)?;
5527        }
5528
5529        self.write(")");
5530
5531        // Alias
5532        if let Some(alias) = &jt.alias {
5533            self.write_space();
5534            self.write_keyword("AS");
5535            self.write_space();
5536            self.generate_identifier(alias)?;
5537        }
5538
5539        Ok(())
5540    }
5541
5542    fn generate_lateral_view(&mut self, lv: &LateralView) -> Result<()> {
5543        use crate::dialects::DialectType;
5544
5545        if self.config.pretty {
5546            self.write_newline();
5547            self.write_indent();
5548        } else {
5549            self.write_space();
5550        }
5551
5552        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
5553        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
5554        let use_lateral_join = matches!(
5555            self.config.dialect,
5556            Some(DialectType::PostgreSQL)
5557                | Some(DialectType::DuckDB)
5558                | Some(DialectType::Snowflake)
5559                | Some(DialectType::TSQL)
5560                | Some(DialectType::Presto)
5561                | Some(DialectType::Trino)
5562                | Some(DialectType::Athena)
5563        );
5564
5565        // Check if target dialect should use UNNEST instead of EXPLODE
5566        let use_unnest = matches!(
5567            self.config.dialect,
5568            Some(DialectType::DuckDB)
5569                | Some(DialectType::Presto)
5570                | Some(DialectType::Trino)
5571                | Some(DialectType::Athena)
5572        );
5573
5574        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
5575        let (is_posexplode, func_args) = match &lv.this {
5576            Expression::Explode(uf) => {
5577                // Expression::Explode is the dedicated EXPLODE expression type
5578                (false, vec![uf.this.clone()])
5579            }
5580            Expression::Unnest(uf) => {
5581                let mut args = vec![uf.this.clone()];
5582                args.extend(uf.expressions.clone());
5583                (false, args)
5584            }
5585            Expression::Function(func) => {
5586                let name = func.name.to_uppercase();
5587                if name == "POSEXPLODE" || name == "POSEXPLODE_OUTER" {
5588                    (true, func.args.clone())
5589                } else if name == "EXPLODE" || name == "EXPLODE_OUTER" || name == "INLINE" {
5590                    (false, func.args.clone())
5591                } else {
5592                    (false, vec![])
5593                }
5594            }
5595            _ => (false, vec![]),
5596        };
5597
5598        if use_lateral_join {
5599            // Convert to CROSS JOIN for PostgreSQL-like dialects
5600            if lv.outer {
5601                self.write_keyword("LEFT JOIN LATERAL");
5602            } else {
5603                self.write_keyword("CROSS JOIN");
5604            }
5605            self.write_space();
5606
5607            if use_unnest && !func_args.is_empty() {
5608                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
5609                // For DuckDB, also convert ARRAY(y) -> [y]
5610                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
5611                    // DuckDB: ARRAY(y) -> [y]
5612                    func_args
5613                        .iter()
5614                        .map(|a| {
5615                            if let Expression::Function(ref f) = a {
5616                                if f.name.to_uppercase() == "ARRAY" && f.args.len() == 1 {
5617                                    return Expression::ArrayFunc(Box::new(
5618                                        crate::expressions::ArrayConstructor {
5619                                            expressions: f.args.clone(),
5620                                            bracket_notation: true,
5621                                            use_list_keyword: false,
5622                                        },
5623                                    ));
5624                                }
5625                            }
5626                            a.clone()
5627                        })
5628                        .collect::<Vec<_>>()
5629                } else if matches!(
5630                    self.config.dialect,
5631                    Some(DialectType::Presto)
5632                        | Some(DialectType::Trino)
5633                        | Some(DialectType::Athena)
5634                ) {
5635                    // Presto: ARRAY(y) -> ARRAY[y]
5636                    func_args
5637                        .iter()
5638                        .map(|a| {
5639                            if let Expression::Function(ref f) = a {
5640                                if f.name.to_uppercase() == "ARRAY" && f.args.len() >= 1 {
5641                                    return Expression::ArrayFunc(Box::new(
5642                                        crate::expressions::ArrayConstructor {
5643                                            expressions: f.args.clone(),
5644                                            bracket_notation: true,
5645                                            use_list_keyword: false,
5646                                        },
5647                                    ));
5648                                }
5649                            }
5650                            a.clone()
5651                        })
5652                        .collect::<Vec<_>>()
5653                } else {
5654                    func_args
5655                };
5656
5657                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
5658                if is_posexplode {
5659                    self.write_keyword("LATERAL");
5660                    self.write(" (");
5661                    self.write_keyword("SELECT");
5662                    self.write_space();
5663
5664                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
5665                    // column_aliases[0] is the position column, rest are data columns
5666                    let pos_alias = if !lv.column_aliases.is_empty() {
5667                        lv.column_aliases[0].clone()
5668                    } else {
5669                        Identifier::new("pos")
5670                    };
5671                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
5672                        lv.column_aliases[1..].to_vec()
5673                    } else {
5674                        vec![Identifier::new("col")]
5675                    };
5676
5677                    // pos - 1 AS pos
5678                    self.generate_identifier(&pos_alias)?;
5679                    self.write(" - 1");
5680                    self.write_space();
5681                    self.write_keyword("AS");
5682                    self.write_space();
5683                    self.generate_identifier(&pos_alias)?;
5684
5685                    // , col [, key, value ...]
5686                    for data_col in &data_aliases {
5687                        self.write(", ");
5688                        self.generate_identifier(data_col)?;
5689                    }
5690
5691                    self.write_space();
5692                    self.write_keyword("FROM");
5693                    self.write_space();
5694                    self.write_keyword("UNNEST");
5695                    self.write("(");
5696                    for (i, arg) in unnest_args.iter().enumerate() {
5697                        if i > 0 {
5698                            self.write(", ");
5699                        }
5700                        self.generate_expression(arg)?;
5701                    }
5702                    self.write(")");
5703                    self.write_space();
5704                    self.write_keyword("WITH ORDINALITY");
5705                    self.write_space();
5706                    self.write_keyword("AS");
5707                    self.write_space();
5708
5709                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
5710                    let table_alias_ident = lv
5711                        .table_alias
5712                        .clone()
5713                        .unwrap_or_else(|| Identifier::new("t"));
5714                    self.generate_identifier(&table_alias_ident)?;
5715                    self.write("(");
5716                    for (i, data_col) in data_aliases.iter().enumerate() {
5717                        if i > 0 {
5718                            self.write(", ");
5719                        }
5720                        self.generate_identifier(data_col)?;
5721                    }
5722                    self.write(", ");
5723                    self.generate_identifier(&pos_alias)?;
5724                    self.write("))");
5725                } else {
5726                    self.write_keyword("UNNEST");
5727                    self.write("(");
5728                    for (i, arg) in unnest_args.iter().enumerate() {
5729                        if i > 0 {
5730                            self.write(", ");
5731                        }
5732                        self.generate_expression(arg)?;
5733                    }
5734                    self.write(")");
5735
5736                    // Add table and column aliases for non-POSEXPLODE
5737                    if let Some(alias) = &lv.table_alias {
5738                        self.write_space();
5739                        self.write_keyword("AS");
5740                        self.write_space();
5741                        self.generate_identifier(alias)?;
5742                        if !lv.column_aliases.is_empty() {
5743                            self.write("(");
5744                            for (i, col) in lv.column_aliases.iter().enumerate() {
5745                                if i > 0 {
5746                                    self.write(", ");
5747                                }
5748                                self.generate_identifier(col)?;
5749                            }
5750                            self.write(")");
5751                        }
5752                    } else if !lv.column_aliases.is_empty() {
5753                        self.write_space();
5754                        self.write_keyword("AS");
5755                        self.write(" t(");
5756                        for (i, col) in lv.column_aliases.iter().enumerate() {
5757                            if i > 0 {
5758                                self.write(", ");
5759                            }
5760                            self.generate_identifier(col)?;
5761                        }
5762                        self.write(")");
5763                    }
5764                }
5765            } else {
5766                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
5767                if !lv.outer {
5768                    self.write_keyword("LATERAL");
5769                    self.write_space();
5770                }
5771                self.generate_expression(&lv.this)?;
5772
5773                // Add table and column aliases
5774                if let Some(alias) = &lv.table_alias {
5775                    self.write_space();
5776                    self.write_keyword("AS");
5777                    self.write_space();
5778                    self.generate_identifier(alias)?;
5779                    if !lv.column_aliases.is_empty() {
5780                        self.write("(");
5781                        for (i, col) in lv.column_aliases.iter().enumerate() {
5782                            if i > 0 {
5783                                self.write(", ");
5784                            }
5785                            self.generate_identifier(col)?;
5786                        }
5787                        self.write(")");
5788                    }
5789                } else if !lv.column_aliases.is_empty() {
5790                    self.write_space();
5791                    self.write_keyword("AS");
5792                    self.write(" t(");
5793                    for (i, col) in lv.column_aliases.iter().enumerate() {
5794                        if i > 0 {
5795                            self.write(", ");
5796                        }
5797                        self.generate_identifier(col)?;
5798                    }
5799                    self.write(")");
5800                }
5801            }
5802
5803            // For LEFT JOIN LATERAL, need ON TRUE
5804            if lv.outer {
5805                self.write_space();
5806                self.write_keyword("ON TRUE");
5807            }
5808        } else {
5809            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
5810            self.write_keyword("LATERAL VIEW");
5811            if lv.outer {
5812                self.write_space();
5813                self.write_keyword("OUTER");
5814            }
5815            if self.config.pretty {
5816                self.write_newline();
5817                self.write_indent();
5818            } else {
5819                self.write_space();
5820            }
5821            self.generate_expression(&lv.this)?;
5822
5823            // Table alias
5824            if let Some(alias) = &lv.table_alias {
5825                self.write_space();
5826                self.generate_identifier(alias)?;
5827            }
5828
5829            // Column aliases
5830            if !lv.column_aliases.is_empty() {
5831                self.write_space();
5832                self.write_keyword("AS");
5833                self.write_space();
5834                for (i, col) in lv.column_aliases.iter().enumerate() {
5835                    if i > 0 {
5836                        self.write(", ");
5837                    }
5838                    self.generate_identifier(col)?;
5839                }
5840            }
5841        }
5842
5843        Ok(())
5844    }
5845
5846    fn generate_union(&mut self, union: &Union) -> Result<()> {
5847        // WITH clause
5848        if let Some(with) = &union.with {
5849            self.generate_with(with)?;
5850            self.write_space();
5851        }
5852        self.generate_expression(&union.left)?;
5853        if self.config.pretty {
5854            self.write_newline();
5855            self.write_indent();
5856        } else {
5857            self.write_space();
5858        }
5859
5860        // BigQuery set operation modifiers: [side] [kind] UNION
5861        if let Some(side) = &union.side {
5862            self.write_keyword(side);
5863            self.write_space();
5864        }
5865        if let Some(kind) = &union.kind {
5866            self.write_keyword(kind);
5867            self.write_space();
5868        }
5869
5870        self.write_keyword("UNION");
5871        if union.all {
5872            self.write_space();
5873            self.write_keyword("ALL");
5874        } else if union.distinct {
5875            self.write_space();
5876            self.write_keyword("DISTINCT");
5877        }
5878
5879        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
5880        // DuckDB: BY NAME
5881        if union.corresponding || union.by_name {
5882            self.write_space();
5883            self.write_keyword("BY NAME");
5884        }
5885        if !union.on_columns.is_empty() {
5886            self.write_space();
5887            self.write_keyword("ON");
5888            self.write(" (");
5889            for (i, col) in union.on_columns.iter().enumerate() {
5890                if i > 0 {
5891                    self.write(", ");
5892                }
5893                self.generate_expression(col)?;
5894            }
5895            self.write(")");
5896        }
5897
5898        if self.config.pretty {
5899            self.write_newline();
5900            self.write_indent();
5901        } else {
5902            self.write_space();
5903        }
5904        self.generate_expression(&union.right)?;
5905        // ORDER BY, LIMIT, OFFSET for the set operation
5906        if let Some(order_by) = &union.order_by {
5907            if self.config.pretty {
5908                self.write_newline();
5909            } else {
5910                self.write_space();
5911            }
5912            self.write_keyword("ORDER BY");
5913            self.write_space();
5914            for (i, ordered) in order_by.expressions.iter().enumerate() {
5915                if i > 0 {
5916                    self.write(", ");
5917                }
5918                self.generate_ordered(ordered)?;
5919            }
5920        }
5921        if let Some(limit) = &union.limit {
5922            if self.config.pretty {
5923                self.write_newline();
5924            } else {
5925                self.write_space();
5926            }
5927            self.write_keyword("LIMIT");
5928            self.write_space();
5929            self.generate_expression(limit)?;
5930        }
5931        if let Some(offset) = &union.offset {
5932            if self.config.pretty {
5933                self.write_newline();
5934            } else {
5935                self.write_space();
5936            }
5937            self.write_keyword("OFFSET");
5938            self.write_space();
5939            self.generate_expression(offset)?;
5940        }
5941        // DISTRIBUTE BY (Hive/Spark)
5942        if let Some(distribute_by) = &union.distribute_by {
5943            self.write_space();
5944            self.write_keyword("DISTRIBUTE BY");
5945            self.write_space();
5946            for (i, expr) in distribute_by.expressions.iter().enumerate() {
5947                if i > 0 {
5948                    self.write(", ");
5949                }
5950                self.generate_expression(expr)?;
5951            }
5952        }
5953        // SORT BY (Hive/Spark)
5954        if let Some(sort_by) = &union.sort_by {
5955            self.write_space();
5956            self.write_keyword("SORT BY");
5957            self.write_space();
5958            for (i, ord) in sort_by.expressions.iter().enumerate() {
5959                if i > 0 {
5960                    self.write(", ");
5961                }
5962                self.generate_ordered(ord)?;
5963            }
5964        }
5965        // CLUSTER BY (Hive/Spark)
5966        if let Some(cluster_by) = &union.cluster_by {
5967            self.write_space();
5968            self.write_keyword("CLUSTER BY");
5969            self.write_space();
5970            for (i, ord) in cluster_by.expressions.iter().enumerate() {
5971                if i > 0 {
5972                    self.write(", ");
5973                }
5974                self.generate_ordered(ord)?;
5975            }
5976        }
5977        Ok(())
5978    }
5979
5980    fn generate_intersect(&mut self, intersect: &Intersect) -> Result<()> {
5981        // WITH clause
5982        if let Some(with) = &intersect.with {
5983            self.generate_with(with)?;
5984            self.write_space();
5985        }
5986        self.generate_expression(&intersect.left)?;
5987        if self.config.pretty {
5988            self.write_newline();
5989            self.write_indent();
5990        } else {
5991            self.write_space();
5992        }
5993
5994        // BigQuery set operation modifiers: [side] [kind] INTERSECT
5995        if let Some(side) = &intersect.side {
5996            self.write_keyword(side);
5997            self.write_space();
5998        }
5999        if let Some(kind) = &intersect.kind {
6000            self.write_keyword(kind);
6001            self.write_space();
6002        }
6003
6004        self.write_keyword("INTERSECT");
6005        if intersect.all {
6006            self.write_space();
6007            self.write_keyword("ALL");
6008        } else if intersect.distinct {
6009            self.write_space();
6010            self.write_keyword("DISTINCT");
6011        }
6012
6013        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6014        // DuckDB: BY NAME
6015        if intersect.corresponding || intersect.by_name {
6016            self.write_space();
6017            self.write_keyword("BY NAME");
6018        }
6019        if !intersect.on_columns.is_empty() {
6020            self.write_space();
6021            self.write_keyword("ON");
6022            self.write(" (");
6023            for (i, col) in intersect.on_columns.iter().enumerate() {
6024                if i > 0 {
6025                    self.write(", ");
6026                }
6027                self.generate_expression(col)?;
6028            }
6029            self.write(")");
6030        }
6031
6032        if self.config.pretty {
6033            self.write_newline();
6034            self.write_indent();
6035        } else {
6036            self.write_space();
6037        }
6038        self.generate_expression(&intersect.right)?;
6039        // ORDER BY, LIMIT, OFFSET for the set operation
6040        if let Some(order_by) = &intersect.order_by {
6041            if self.config.pretty {
6042                self.write_newline();
6043            } else {
6044                self.write_space();
6045            }
6046            self.write_keyword("ORDER BY");
6047            self.write_space();
6048            for (i, ordered) in order_by.expressions.iter().enumerate() {
6049                if i > 0 {
6050                    self.write(", ");
6051                }
6052                self.generate_ordered(ordered)?;
6053            }
6054        }
6055        if let Some(limit) = &intersect.limit {
6056            if self.config.pretty {
6057                self.write_newline();
6058            } else {
6059                self.write_space();
6060            }
6061            self.write_keyword("LIMIT");
6062            self.write_space();
6063            self.generate_expression(limit)?;
6064        }
6065        if let Some(offset) = &intersect.offset {
6066            if self.config.pretty {
6067                self.write_newline();
6068            } else {
6069                self.write_space();
6070            }
6071            self.write_keyword("OFFSET");
6072            self.write_space();
6073            self.generate_expression(offset)?;
6074        }
6075        // DISTRIBUTE BY (Hive/Spark)
6076        if let Some(distribute_by) = &intersect.distribute_by {
6077            self.write_space();
6078            self.write_keyword("DISTRIBUTE BY");
6079            self.write_space();
6080            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6081                if i > 0 {
6082                    self.write(", ");
6083                }
6084                self.generate_expression(expr)?;
6085            }
6086        }
6087        // SORT BY (Hive/Spark)
6088        if let Some(sort_by) = &intersect.sort_by {
6089            self.write_space();
6090            self.write_keyword("SORT BY");
6091            self.write_space();
6092            for (i, ord) in sort_by.expressions.iter().enumerate() {
6093                if i > 0 {
6094                    self.write(", ");
6095                }
6096                self.generate_ordered(ord)?;
6097            }
6098        }
6099        // CLUSTER BY (Hive/Spark)
6100        if let Some(cluster_by) = &intersect.cluster_by {
6101            self.write_space();
6102            self.write_keyword("CLUSTER BY");
6103            self.write_space();
6104            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6105                if i > 0 {
6106                    self.write(", ");
6107                }
6108                self.generate_ordered(ord)?;
6109            }
6110        }
6111        Ok(())
6112    }
6113
6114    fn generate_except(&mut self, except: &Except) -> Result<()> {
6115        use crate::dialects::DialectType;
6116
6117        // WITH clause
6118        if let Some(with) = &except.with {
6119            self.generate_with(with)?;
6120            self.write_space();
6121        }
6122
6123        self.generate_expression(&except.left)?;
6124        if self.config.pretty {
6125            self.write_newline();
6126            self.write_indent();
6127        } else {
6128            self.write_space();
6129        }
6130
6131        // BigQuery set operation modifiers: [side] [kind] EXCEPT
6132        if let Some(side) = &except.side {
6133            self.write_keyword(side);
6134            self.write_space();
6135        }
6136        if let Some(kind) = &except.kind {
6137            self.write_keyword(kind);
6138            self.write_space();
6139        }
6140
6141        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
6142        match self.config.dialect {
6143            Some(DialectType::Oracle) if !except.all => {
6144                self.write_keyword("MINUS");
6145            }
6146            Some(DialectType::ClickHouse) => {
6147                // ClickHouse: drop ALL from EXCEPT ALL
6148                self.write_keyword("EXCEPT");
6149                if except.distinct {
6150                    self.write_space();
6151                    self.write_keyword("DISTINCT");
6152                }
6153            }
6154            Some(DialectType::BigQuery) => {
6155                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
6156                self.write_keyword("EXCEPT");
6157                if except.all {
6158                    self.write_space();
6159                    self.write_keyword("ALL");
6160                } else {
6161                    self.write_space();
6162                    self.write_keyword("DISTINCT");
6163                }
6164            }
6165            _ => {
6166                self.write_keyword("EXCEPT");
6167                if except.all {
6168                    self.write_space();
6169                    self.write_keyword("ALL");
6170                } else if except.distinct {
6171                    self.write_space();
6172                    self.write_keyword("DISTINCT");
6173                }
6174            }
6175        }
6176
6177        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6178        // DuckDB: BY NAME
6179        if except.corresponding || except.by_name {
6180            self.write_space();
6181            self.write_keyword("BY NAME");
6182        }
6183        if !except.on_columns.is_empty() {
6184            self.write_space();
6185            self.write_keyword("ON");
6186            self.write(" (");
6187            for (i, col) in except.on_columns.iter().enumerate() {
6188                if i > 0 {
6189                    self.write(", ");
6190                }
6191                self.generate_expression(col)?;
6192            }
6193            self.write(")");
6194        }
6195
6196        if self.config.pretty {
6197            self.write_newline();
6198            self.write_indent();
6199        } else {
6200            self.write_space();
6201        }
6202        self.generate_expression(&except.right)?;
6203        // ORDER BY, LIMIT, OFFSET for the set operation
6204        if let Some(order_by) = &except.order_by {
6205            if self.config.pretty {
6206                self.write_newline();
6207            } else {
6208                self.write_space();
6209            }
6210            self.write_keyword("ORDER BY");
6211            self.write_space();
6212            for (i, ordered) in order_by.expressions.iter().enumerate() {
6213                if i > 0 {
6214                    self.write(", ");
6215                }
6216                self.generate_ordered(ordered)?;
6217            }
6218        }
6219        if let Some(limit) = &except.limit {
6220            if self.config.pretty {
6221                self.write_newline();
6222            } else {
6223                self.write_space();
6224            }
6225            self.write_keyword("LIMIT");
6226            self.write_space();
6227            self.generate_expression(limit)?;
6228        }
6229        if let Some(offset) = &except.offset {
6230            if self.config.pretty {
6231                self.write_newline();
6232            } else {
6233                self.write_space();
6234            }
6235            self.write_keyword("OFFSET");
6236            self.write_space();
6237            self.generate_expression(offset)?;
6238        }
6239        // DISTRIBUTE BY (Hive/Spark)
6240        if let Some(distribute_by) = &except.distribute_by {
6241            self.write_space();
6242            self.write_keyword("DISTRIBUTE BY");
6243            self.write_space();
6244            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6245                if i > 0 {
6246                    self.write(", ");
6247                }
6248                self.generate_expression(expr)?;
6249            }
6250        }
6251        // SORT BY (Hive/Spark)
6252        if let Some(sort_by) = &except.sort_by {
6253            self.write_space();
6254            self.write_keyword("SORT BY");
6255            self.write_space();
6256            for (i, ord) in sort_by.expressions.iter().enumerate() {
6257                if i > 0 {
6258                    self.write(", ");
6259                }
6260                self.generate_ordered(ord)?;
6261            }
6262        }
6263        // CLUSTER BY (Hive/Spark)
6264        if let Some(cluster_by) = &except.cluster_by {
6265            self.write_space();
6266            self.write_keyword("CLUSTER BY");
6267            self.write_space();
6268            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6269                if i > 0 {
6270                    self.write(", ");
6271                }
6272                self.generate_ordered(ord)?;
6273            }
6274        }
6275        Ok(())
6276    }
6277
6278    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
6279        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
6280        let prepend_query_cte = if insert.with.is_none() {
6281            use crate::dialects::DialectType;
6282            let should_prepend = matches!(
6283                self.config.dialect,
6284                Some(DialectType::TSQL)
6285                    | Some(DialectType::Fabric)
6286                    | Some(DialectType::Spark)
6287                    | Some(DialectType::Databricks)
6288                    | Some(DialectType::Hive)
6289            );
6290            if should_prepend {
6291                if let Some(Expression::Select(select)) = &insert.query {
6292                    select.with.clone()
6293                } else {
6294                    None
6295                }
6296            } else {
6297                None
6298            }
6299        } else {
6300            None
6301        };
6302
6303        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
6304        if let Some(with) = &insert.with {
6305            self.generate_with(with)?;
6306            self.write_space();
6307        } else if let Some(with) = &prepend_query_cte {
6308            self.generate_with(with)?;
6309            self.write_space();
6310        }
6311
6312        // Output leading comments before INSERT
6313        for comment in &insert.leading_comments {
6314            self.write_formatted_comment(comment);
6315            self.write(" ");
6316        }
6317
6318        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
6319        if let Some(dir) = &insert.directory {
6320            self.write_keyword("INSERT OVERWRITE");
6321            if dir.local {
6322                self.write_space();
6323                self.write_keyword("LOCAL");
6324            }
6325            self.write_space();
6326            self.write_keyword("DIRECTORY");
6327            self.write_space();
6328            self.write("'");
6329            self.write(&dir.path);
6330            self.write("'");
6331
6332            // ROW FORMAT clause
6333            if let Some(row_format) = &dir.row_format {
6334                self.write_space();
6335                self.write_keyword("ROW FORMAT");
6336                if row_format.delimited {
6337                    self.write_space();
6338                    self.write_keyword("DELIMITED");
6339                }
6340                if let Some(val) = &row_format.fields_terminated_by {
6341                    self.write_space();
6342                    self.write_keyword("FIELDS TERMINATED BY");
6343                    self.write_space();
6344                    self.write("'");
6345                    self.write(val);
6346                    self.write("'");
6347                }
6348                if let Some(val) = &row_format.collection_items_terminated_by {
6349                    self.write_space();
6350                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
6351                    self.write_space();
6352                    self.write("'");
6353                    self.write(val);
6354                    self.write("'");
6355                }
6356                if let Some(val) = &row_format.map_keys_terminated_by {
6357                    self.write_space();
6358                    self.write_keyword("MAP KEYS TERMINATED BY");
6359                    self.write_space();
6360                    self.write("'");
6361                    self.write(val);
6362                    self.write("'");
6363                }
6364                if let Some(val) = &row_format.lines_terminated_by {
6365                    self.write_space();
6366                    self.write_keyword("LINES TERMINATED BY");
6367                    self.write_space();
6368                    self.write("'");
6369                    self.write(val);
6370                    self.write("'");
6371                }
6372                if let Some(val) = &row_format.null_defined_as {
6373                    self.write_space();
6374                    self.write_keyword("NULL DEFINED AS");
6375                    self.write_space();
6376                    self.write("'");
6377                    self.write(val);
6378                    self.write("'");
6379                }
6380            }
6381
6382            // STORED AS clause
6383            if let Some(format) = &dir.stored_as {
6384                self.write_space();
6385                self.write_keyword("STORED AS");
6386                self.write_space();
6387                self.write_keyword(format);
6388            }
6389
6390            // Query (SELECT statement)
6391            if let Some(query) = &insert.query {
6392                self.write_space();
6393                self.generate_expression(query)?;
6394            }
6395
6396            return Ok(());
6397        }
6398
6399        if insert.is_replace {
6400            // MySQL/SQLite REPLACE INTO statement
6401            self.write_keyword("REPLACE INTO");
6402        } else if insert.overwrite {
6403            // Use dialect-specific INSERT OVERWRITE format
6404            self.write_keyword("INSERT");
6405            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6406            if let Some(ref hint) = insert.hint {
6407                self.generate_hint(hint)?;
6408            }
6409            self.write(&self.config.insert_overwrite.to_uppercase());
6410        } else if let Some(ref action) = insert.conflict_action {
6411            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
6412            self.write_keyword("INSERT OR");
6413            self.write_space();
6414            self.write_keyword(action);
6415            self.write_space();
6416            self.write_keyword("INTO");
6417        } else if insert.ignore {
6418            // MySQL INSERT IGNORE syntax
6419            self.write_keyword("INSERT IGNORE INTO");
6420        } else {
6421            self.write_keyword("INSERT");
6422            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6423            if let Some(ref hint) = insert.hint {
6424                self.generate_hint(hint)?;
6425            }
6426            self.write_space();
6427            self.write_keyword("INTO");
6428        }
6429        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
6430        if let Some(ref func) = insert.function_target {
6431            self.write_space();
6432            self.write_keyword("FUNCTION");
6433            self.write_space();
6434            self.generate_expression(func)?;
6435        } else {
6436            self.write_space();
6437            self.generate_table(&insert.table)?;
6438        }
6439
6440        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
6441        if let Some(ref alias) = insert.alias {
6442            self.write_space();
6443            if insert.alias_explicit_as {
6444                self.write_keyword("AS");
6445                self.write_space();
6446            }
6447            self.generate_identifier(alias)?;
6448        }
6449
6450        // IF EXISTS clause (Hive)
6451        if insert.if_exists {
6452            self.write_space();
6453            self.write_keyword("IF EXISTS");
6454        }
6455
6456        // REPLACE WHERE clause (Databricks)
6457        if let Some(ref replace_where) = insert.replace_where {
6458            if self.config.pretty {
6459                self.write_newline();
6460                self.write_indent();
6461            } else {
6462                self.write_space();
6463            }
6464            self.write_keyword("REPLACE WHERE");
6465            self.write_space();
6466            self.generate_expression(replace_where)?;
6467        }
6468
6469        // Generate PARTITION clause if present
6470        if !insert.partition.is_empty() {
6471            self.write_space();
6472            self.write_keyword("PARTITION");
6473            self.write("(");
6474            for (i, (col, val)) in insert.partition.iter().enumerate() {
6475                if i > 0 {
6476                    self.write(", ");
6477                }
6478                self.generate_identifier(col)?;
6479                if let Some(v) = val {
6480                    self.write(" = ");
6481                    self.generate_expression(v)?;
6482                }
6483            }
6484            self.write(")");
6485        }
6486
6487        // ClickHouse: PARTITION BY expr
6488        if let Some(ref partition_by) = insert.partition_by {
6489            self.write_space();
6490            self.write_keyword("PARTITION BY");
6491            self.write_space();
6492            self.generate_expression(partition_by)?;
6493        }
6494
6495        // ClickHouse: SETTINGS key = val, ...
6496        if !insert.settings.is_empty() {
6497            self.write_space();
6498            self.write_keyword("SETTINGS");
6499            self.write_space();
6500            for (i, setting) in insert.settings.iter().enumerate() {
6501                if i > 0 {
6502                    self.write(", ");
6503                }
6504                self.generate_expression(setting)?;
6505            }
6506        }
6507
6508        if !insert.columns.is_empty() {
6509            if insert.alias.is_some() && insert.alias_explicit_as {
6510                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
6511                self.write("(");
6512            } else {
6513                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
6514                self.write(" (");
6515            }
6516            for (i, col) in insert.columns.iter().enumerate() {
6517                if i > 0 {
6518                    self.write(", ");
6519                }
6520                self.generate_identifier(col)?;
6521            }
6522            self.write(")");
6523        }
6524
6525        // OUTPUT clause (TSQL)
6526        if let Some(ref output) = insert.output {
6527            self.generate_output_clause(output)?;
6528        }
6529
6530        // BY NAME modifier (DuckDB)
6531        if insert.by_name {
6532            self.write_space();
6533            self.write_keyword("BY NAME");
6534        }
6535
6536        if insert.default_values {
6537            self.write_space();
6538            self.write_keyword("DEFAULT VALUES");
6539        } else if let Some(query) = &insert.query {
6540            if self.config.pretty {
6541                self.write_newline();
6542            } else {
6543                self.write_space();
6544            }
6545            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
6546            if prepend_query_cte.is_some() {
6547                if let Expression::Select(select) = query {
6548                    let mut select_no_with = select.clone();
6549                    select_no_with.with = None;
6550                    self.generate_select(&select_no_with)?;
6551                } else {
6552                    self.generate_expression(query)?;
6553                }
6554            } else {
6555                self.generate_expression(query)?;
6556            }
6557        } else if !insert.values.is_empty() {
6558            if self.config.pretty {
6559                // Pretty printing: VALUES on new line, each tuple indented
6560                self.write_newline();
6561                self.write_keyword("VALUES");
6562                self.write_newline();
6563                self.indent_level += 1;
6564                for (i, row) in insert.values.iter().enumerate() {
6565                    if i > 0 {
6566                        self.write(",");
6567                        self.write_newline();
6568                    }
6569                    self.write_indent();
6570                    self.write("(");
6571                    for (j, val) in row.iter().enumerate() {
6572                        if j > 0 {
6573                            self.write(", ");
6574                        }
6575                        self.generate_expression(val)?;
6576                    }
6577                    self.write(")");
6578                }
6579                self.indent_level -= 1;
6580            } else {
6581                // Non-pretty: single line
6582                self.write_space();
6583                self.write_keyword("VALUES");
6584                for (i, row) in insert.values.iter().enumerate() {
6585                    if i > 0 {
6586                        self.write(",");
6587                    }
6588                    self.write(" (");
6589                    for (j, val) in row.iter().enumerate() {
6590                        if j > 0 {
6591                            self.write(", ");
6592                        }
6593                        self.generate_expression(val)?;
6594                    }
6595                    self.write(")");
6596                }
6597            }
6598        }
6599
6600        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
6601        if let Some(ref source) = insert.source {
6602            self.write_space();
6603            self.write_keyword("TABLE");
6604            self.write_space();
6605            self.generate_expression(source)?;
6606        }
6607
6608        // Source alias (MySQL: VALUES (...) AS new_data)
6609        if let Some(alias) = &insert.source_alias {
6610            self.write_space();
6611            self.write_keyword("AS");
6612            self.write_space();
6613            self.generate_identifier(alias)?;
6614        }
6615
6616        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
6617        if let Some(on_conflict) = &insert.on_conflict {
6618            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
6619                self.write_space();
6620                self.generate_expression(on_conflict)?;
6621            }
6622        }
6623
6624        // RETURNING clause
6625        if !insert.returning.is_empty() {
6626            self.write_space();
6627            self.write_keyword("RETURNING");
6628            self.write_space();
6629            for (i, expr) in insert.returning.iter().enumerate() {
6630                if i > 0 {
6631                    self.write(", ");
6632                }
6633                self.generate_expression(expr)?;
6634            }
6635        }
6636
6637        Ok(())
6638    }
6639
6640    fn generate_update(&mut self, update: &Update) -> Result<()> {
6641        // Output leading comments before UPDATE
6642        for comment in &update.leading_comments {
6643            self.write_formatted_comment(comment);
6644            self.write(" ");
6645        }
6646
6647        // WITH clause (CTEs)
6648        if let Some(ref with) = update.with {
6649            self.generate_with(with)?;
6650            self.write_space();
6651        }
6652
6653        self.write_keyword("UPDATE");
6654        self.write_space();
6655        self.generate_table(&update.table)?;
6656
6657        let mysql_like_update_from = matches!(
6658            self.config.dialect,
6659            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
6660        ) && update.from_clause.is_some();
6661
6662        let mut set_pairs = update.set.clone();
6663
6664        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
6665        let mut pre_set_joins = update.table_joins.clone();
6666        if mysql_like_update_from {
6667            let target_name = update
6668                .table
6669                .alias
6670                .as_ref()
6671                .map(|a| a.name.clone())
6672                .unwrap_or_else(|| update.table.name.name.clone());
6673
6674            for (col, _) in &mut set_pairs {
6675                if !col.name.contains('.') {
6676                    col.name = format!("{}.{}", target_name, col.name);
6677                }
6678            }
6679
6680            if let Some(from_clause) = &update.from_clause {
6681                for table_expr in &from_clause.expressions {
6682                    pre_set_joins.push(crate::expressions::Join {
6683                        this: table_expr.clone(),
6684                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
6685                            value: true,
6686                        })),
6687                        using: Vec::new(),
6688                        kind: crate::expressions::JoinKind::Inner,
6689                        use_inner_keyword: false,
6690                        use_outer_keyword: false,
6691                        deferred_condition: false,
6692                        join_hint: None,
6693                        match_condition: None,
6694                        pivots: Vec::new(),
6695                        comments: Vec::new(),
6696                        nesting_group: 0,
6697                        directed: false,
6698                    });
6699                }
6700            }
6701            for join in &update.from_joins {
6702                let mut join = join.clone();
6703                if join.on.is_none() && join.using.is_empty() {
6704                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
6705                        value: true,
6706                    }));
6707                }
6708                pre_set_joins.push(join);
6709            }
6710        }
6711
6712        // Extra tables for multi-table UPDATE (MySQL syntax)
6713        for extra_table in &update.extra_tables {
6714            self.write(", ");
6715            self.generate_table(extra_table)?;
6716        }
6717
6718        // JOINs attached to the table list (MySQL multi-table syntax)
6719        for join in &pre_set_joins {
6720            // generate_join already adds a leading space
6721            self.generate_join(join)?;
6722        }
6723
6724        // Teradata: FROM clause comes before SET
6725        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
6726        if teradata_from_before_set && !mysql_like_update_from {
6727            if let Some(ref from_clause) = update.from_clause {
6728                self.write_space();
6729                self.write_keyword("FROM");
6730                self.write_space();
6731                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
6732                    if i > 0 {
6733                        self.write(", ");
6734                    }
6735                    self.generate_expression(table_expr)?;
6736                }
6737            }
6738            for join in &update.from_joins {
6739                self.generate_join(join)?;
6740            }
6741        }
6742
6743        self.write_space();
6744        self.write_keyword("SET");
6745        self.write_space();
6746
6747        for (i, (col, val)) in set_pairs.iter().enumerate() {
6748            if i > 0 {
6749                self.write(", ");
6750            }
6751            self.generate_identifier(col)?;
6752            self.write(" = ");
6753            self.generate_expression(val)?;
6754        }
6755
6756        // OUTPUT clause (TSQL)
6757        if let Some(ref output) = update.output {
6758            self.generate_output_clause(output)?;
6759        }
6760
6761        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
6762        if !mysql_like_update_from && !teradata_from_before_set {
6763            if let Some(ref from_clause) = update.from_clause {
6764                self.write_space();
6765                self.write_keyword("FROM");
6766                self.write_space();
6767                // Generate each table in the FROM clause
6768                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
6769                    if i > 0 {
6770                        self.write(", ");
6771                    }
6772                    self.generate_expression(table_expr)?;
6773                }
6774            }
6775        }
6776
6777        if !mysql_like_update_from && !teradata_from_before_set {
6778            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
6779            for join in &update.from_joins {
6780                self.generate_join(join)?;
6781            }
6782        }
6783
6784        if let Some(where_clause) = &update.where_clause {
6785            self.write_space();
6786            self.write_keyword("WHERE");
6787            self.write_space();
6788            self.generate_expression(&where_clause.this)?;
6789        }
6790
6791        // RETURNING clause
6792        if !update.returning.is_empty() {
6793            self.write_space();
6794            self.write_keyword("RETURNING");
6795            self.write_space();
6796            for (i, expr) in update.returning.iter().enumerate() {
6797                if i > 0 {
6798                    self.write(", ");
6799                }
6800                self.generate_expression(expr)?;
6801            }
6802        }
6803
6804        // ORDER BY clause (MySQL)
6805        if let Some(ref order_by) = update.order_by {
6806            self.write_space();
6807            self.generate_order_by(order_by)?;
6808        }
6809
6810        // LIMIT clause (MySQL)
6811        if let Some(ref limit) = update.limit {
6812            self.write_space();
6813            self.write_keyword("LIMIT");
6814            self.write_space();
6815            self.generate_expression(limit)?;
6816        }
6817
6818        Ok(())
6819    }
6820
6821    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
6822        // Output WITH clause if present
6823        if let Some(with) = &delete.with {
6824            self.generate_with(with)?;
6825            self.write_space();
6826        }
6827
6828        // Output leading comments before DELETE
6829        for comment in &delete.leading_comments {
6830            self.write_formatted_comment(comment);
6831            self.write(" ");
6832        }
6833
6834        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
6835        if !delete.tables.is_empty() && !delete.tables_from_using {
6836            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
6837            self.write_keyword("DELETE");
6838            self.write_space();
6839            for (i, tbl) in delete.tables.iter().enumerate() {
6840                if i > 0 {
6841                    self.write(", ");
6842                }
6843                self.generate_table(tbl)?;
6844            }
6845            // TSQL: OUTPUT clause between target table and FROM
6846            if let Some(ref output) = delete.output {
6847                self.generate_output_clause(output)?;
6848            }
6849            self.write_space();
6850            self.write_keyword("FROM");
6851            self.write_space();
6852            self.generate_table(&delete.table)?;
6853        } else if !delete.tables.is_empty() && delete.tables_from_using {
6854            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
6855            self.write_keyword("DELETE FROM");
6856            self.write_space();
6857            for (i, tbl) in delete.tables.iter().enumerate() {
6858                if i > 0 {
6859                    self.write(", ");
6860                }
6861                self.generate_table(tbl)?;
6862            }
6863        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
6864            // BigQuery-style DELETE without FROM keyword
6865            self.write_keyword("DELETE");
6866            self.write_space();
6867            self.generate_table(&delete.table)?;
6868        } else {
6869            self.write_keyword("DELETE FROM");
6870            self.write_space();
6871            self.generate_table(&delete.table)?;
6872        }
6873
6874        // ClickHouse: ON CLUSTER clause
6875        if let Some(ref on_cluster) = delete.on_cluster {
6876            self.write_space();
6877            self.generate_on_cluster(on_cluster)?;
6878        }
6879
6880        // FORCE INDEX hint (MySQL)
6881        if let Some(ref idx) = delete.force_index {
6882            self.write_space();
6883            self.write_keyword("FORCE INDEX");
6884            self.write(" (");
6885            self.write(idx);
6886            self.write(")");
6887        }
6888
6889        // Optional alias
6890        if let Some(ref alias) = delete.alias {
6891            self.write_space();
6892            if delete.alias_explicit_as
6893                || matches!(self.config.dialect, Some(DialectType::BigQuery))
6894            {
6895                self.write_keyword("AS");
6896                self.write_space();
6897            }
6898            self.generate_identifier(alias)?;
6899        }
6900
6901        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
6902        if !delete.tables_from_using {
6903            for join in &delete.joins {
6904                self.generate_join(join)?;
6905            }
6906        }
6907
6908        // USING clause (PostgreSQL/DuckDB/MySQL)
6909        if !delete.using.is_empty() {
6910            self.write_space();
6911            self.write_keyword("USING");
6912            for (i, table) in delete.using.iter().enumerate() {
6913                if i > 0 {
6914                    self.write(",");
6915                }
6916                self.write_space();
6917                // Check if the table has subquery hints (DuckDB USING with subquery)
6918                if !table.hints.is_empty() && table.name.is_empty() {
6919                    // Subquery in USING: (VALUES ...) AS alias(cols)
6920                    self.generate_expression(&table.hints[0])?;
6921                    if let Some(ref alias) = table.alias {
6922                        self.write_space();
6923                        if table.alias_explicit_as {
6924                            self.write_keyword("AS");
6925                            self.write_space();
6926                        }
6927                        self.generate_identifier(alias)?;
6928                        if !table.column_aliases.is_empty() {
6929                            self.write("(");
6930                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
6931                                if j > 0 {
6932                                    self.write(", ");
6933                                }
6934                                self.generate_identifier(col_alias)?;
6935                            }
6936                            self.write(")");
6937                        }
6938                    }
6939                } else {
6940                    self.generate_table(table)?;
6941                }
6942            }
6943        }
6944
6945        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
6946        if delete.tables_from_using {
6947            for join in &delete.joins {
6948                self.generate_join(join)?;
6949            }
6950        }
6951
6952        // OUTPUT clause (TSQL) - only if not already emitted in the early position
6953        let output_already_emitted =
6954            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
6955        if !output_already_emitted {
6956            if let Some(ref output) = delete.output {
6957                self.generate_output_clause(output)?;
6958            }
6959        }
6960
6961        if let Some(where_clause) = &delete.where_clause {
6962            self.write_space();
6963            self.write_keyword("WHERE");
6964            self.write_space();
6965            self.generate_expression(&where_clause.this)?;
6966        }
6967
6968        // ORDER BY clause (MySQL)
6969        if let Some(ref order_by) = delete.order_by {
6970            self.write_space();
6971            self.generate_order_by(order_by)?;
6972        }
6973
6974        // LIMIT clause (MySQL)
6975        if let Some(ref limit) = delete.limit {
6976            self.write_space();
6977            self.write_keyword("LIMIT");
6978            self.write_space();
6979            self.generate_expression(limit)?;
6980        }
6981
6982        // RETURNING clause (PostgreSQL)
6983        if !delete.returning.is_empty() {
6984            self.write_space();
6985            self.write_keyword("RETURNING");
6986            self.write_space();
6987            for (i, expr) in delete.returning.iter().enumerate() {
6988                if i > 0 {
6989                    self.write(", ");
6990                }
6991                self.generate_expression(expr)?;
6992            }
6993        }
6994
6995        Ok(())
6996    }
6997
6998    // ==================== DDL Generation ====================
6999
7000    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
7001        // Athena: Determine if this is Hive-style DDL or Trino-style DML
7002        // CREATE TABLE AS SELECT uses Trino (double quotes)
7003        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
7004        let saved_athena_hive_context = self.athena_hive_context;
7005        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
7006        if matches!(
7007            self.config.dialect,
7008            Some(crate::dialects::DialectType::Athena)
7009        ) {
7010            // Use Hive context if:
7011            // 1. It's an EXTERNAL table, OR
7012            // 2. There's no AS SELECT clause
7013            let is_external = ct
7014                .table_modifier
7015                .as_ref()
7016                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
7017                .unwrap_or(false);
7018            let has_as_select = ct.as_select.is_some();
7019            self.athena_hive_context = is_external || !has_as_select;
7020        }
7021
7022        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
7023        if matches!(
7024            self.config.dialect,
7025            Some(crate::dialects::DialectType::TSQL)
7026        ) {
7027            if let Some(ref query) = ct.as_select {
7028                // Output WITH CTE clause if present
7029                if let Some(with_cte) = &ct.with_cte {
7030                    self.generate_with(with_cte)?;
7031                    self.write_space();
7032                }
7033
7034                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
7035                self.write_keyword("SELECT");
7036                self.write(" * ");
7037                self.write_keyword("INTO");
7038                self.write_space();
7039
7040                // If temporary, prefix with # for TSQL temp table
7041                if ct.temporary {
7042                    self.write("#");
7043                }
7044                self.generate_table(&ct.name)?;
7045
7046                self.write_space();
7047                self.write_keyword("FROM");
7048                self.write(" (");
7049                // For TSQL, add aliases to select columns to preserve column names
7050                let aliased_query = Self::add_column_aliases_to_query(query.clone());
7051                self.generate_expression(&aliased_query)?;
7052                self.write(") ");
7053                self.write_keyword("AS");
7054                self.write(" temp");
7055                return Ok(());
7056            }
7057        }
7058
7059        // Output WITH CTE clause if present
7060        if let Some(with_cte) = &ct.with_cte {
7061            self.generate_with(with_cte)?;
7062            self.write_space();
7063        }
7064
7065        // Output leading comments before CREATE
7066        for comment in &ct.leading_comments {
7067            self.write_formatted_comment(comment);
7068            self.write(" ");
7069        }
7070        self.write_keyword("CREATE");
7071
7072        if ct.or_replace {
7073            self.write_space();
7074            self.write_keyword("OR REPLACE");
7075        }
7076
7077        if ct.temporary {
7078            self.write_space();
7079            // Oracle uses GLOBAL TEMPORARY TABLE syntax
7080            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
7081                self.write_keyword("GLOBAL TEMPORARY");
7082            } else {
7083                self.write_keyword("TEMPORARY");
7084            }
7085        }
7086
7087        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
7088        let is_dictionary = ct
7089            .table_modifier
7090            .as_ref()
7091            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
7092            .unwrap_or(false);
7093        if let Some(ref modifier) = ct.table_modifier {
7094            // TRANSIENT is Snowflake-specific - skip for other dialects
7095            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
7096                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
7097            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
7098            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
7099                || modifier.eq_ignore_ascii_case("SET")
7100                || modifier.eq_ignore_ascii_case("MULTISET")
7101                || modifier.to_uppercase().contains("VOLATILE")
7102                || modifier.to_uppercase().starts_with("SET ")
7103                || modifier.to_uppercase().starts_with("MULTISET ");
7104            let skip_teradata =
7105                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
7106            if !skip_transient && !skip_teradata {
7107                self.write_space();
7108                self.write_keyword(modifier);
7109            }
7110        }
7111
7112        if !is_dictionary {
7113            self.write_space();
7114            self.write_keyword("TABLE");
7115        }
7116
7117        if ct.if_not_exists {
7118            self.write_space();
7119            self.write_keyword("IF NOT EXISTS");
7120        }
7121
7122        self.write_space();
7123        self.generate_table(&ct.name)?;
7124
7125        // ClickHouse: ON CLUSTER clause
7126        if let Some(ref on_cluster) = ct.on_cluster {
7127            self.write_space();
7128            self.generate_on_cluster(on_cluster)?;
7129        }
7130
7131        // Teradata: options after table name before column list (comma-separated)
7132        if matches!(
7133            self.config.dialect,
7134            Some(crate::dialects::DialectType::Teradata)
7135        ) && !ct.teradata_post_name_options.is_empty()
7136        {
7137            for opt in &ct.teradata_post_name_options {
7138                self.write(", ");
7139                self.write(opt);
7140            }
7141        }
7142
7143        // Snowflake: COPY GRANTS clause
7144        if ct.copy_grants {
7145            self.write_space();
7146            self.write_keyword("COPY GRANTS");
7147        }
7148
7149        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
7150        if let Some(ref using_template) = ct.using_template {
7151            self.write_space();
7152            self.write_keyword("USING TEMPLATE");
7153            self.write_space();
7154            self.generate_expression(using_template)?;
7155            return Ok(());
7156        }
7157
7158        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
7159        if let Some(ref clone_source) = ct.clone_source {
7160            self.write_space();
7161            if ct.is_copy && self.config.supports_table_copy {
7162                // BigQuery uses COPY
7163                self.write_keyword("COPY");
7164            } else if ct.shallow_clone {
7165                self.write_keyword("SHALLOW CLONE");
7166            } else {
7167                self.write_keyword("CLONE");
7168            }
7169            self.write_space();
7170            self.generate_table(clone_source)?;
7171            // Generate AT/BEFORE time travel clause (stored as Raw expression)
7172            if let Some(ref at_clause) = ct.clone_at_clause {
7173                self.write_space();
7174                self.generate_expression(at_clause)?;
7175            }
7176            return Ok(());
7177        }
7178
7179        // Handle PARTITION OF property
7180        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
7181        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
7182        if let Some(ref partition_of) = ct.partition_of {
7183            self.write_space();
7184
7185            // Extract the PartitionedOfProperty parts to generate them separately
7186            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
7187                // Output: PARTITION OF <table>
7188                self.write_keyword("PARTITION OF");
7189                self.write_space();
7190                self.generate_expression(&pop.this)?;
7191
7192                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
7193                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7194                    self.write(" (");
7195                    let mut first = true;
7196                    for col in &ct.columns {
7197                        if !first {
7198                            self.write(", ");
7199                        }
7200                        first = false;
7201                        self.generate_column_def(col)?;
7202                    }
7203                    for constraint in &ct.constraints {
7204                        if !first {
7205                            self.write(", ");
7206                        }
7207                        first = false;
7208                        self.generate_table_constraint(constraint)?;
7209                    }
7210                    self.write(")");
7211                }
7212
7213                // Output partition bound spec: FOR VALUES ... or DEFAULT
7214                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
7215                    self.write_space();
7216                    self.write_keyword("FOR VALUES");
7217                    self.write_space();
7218                    self.generate_expression(&pop.expression)?;
7219                } else {
7220                    self.write_space();
7221                    self.write_keyword("DEFAULT");
7222                }
7223            } else {
7224                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
7225                self.generate_expression(partition_of)?;
7226
7227                // Output columns/constraints if present
7228                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7229                    self.write(" (");
7230                    let mut first = true;
7231                    for col in &ct.columns {
7232                        if !first {
7233                            self.write(", ");
7234                        }
7235                        first = false;
7236                        self.generate_column_def(col)?;
7237                    }
7238                    for constraint in &ct.constraints {
7239                        if !first {
7240                            self.write(", ");
7241                        }
7242                        first = false;
7243                        self.generate_table_constraint(constraint)?;
7244                    }
7245                    self.write(")");
7246                }
7247            }
7248
7249            // Output table properties (e.g., PARTITION BY RANGE(population))
7250            for prop in &ct.properties {
7251                self.write_space();
7252                self.generate_expression(prop)?;
7253            }
7254
7255            return Ok(());
7256        }
7257
7258        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
7259        // This matches Python sqlglot's behavior for SQLite dialect
7260        self.sqlite_inline_pk_columns.clear();
7261        if matches!(
7262            self.config.dialect,
7263            Some(crate::dialects::DialectType::SQLite)
7264        ) {
7265            for constraint in &ct.constraints {
7266                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7267                    // Only inline if: single column, no constraint name, and column exists in table
7268                    if columns.len() == 1 && name.is_none() {
7269                        let pk_col_name = columns[0].name.to_lowercase();
7270                        // Check if this column exists in the table
7271                        if ct
7272                            .columns
7273                            .iter()
7274                            .any(|c| c.name.name.to_lowercase() == pk_col_name)
7275                        {
7276                            self.sqlite_inline_pk_columns.insert(pk_col_name);
7277                        }
7278                    }
7279                }
7280            }
7281        }
7282
7283        // Output columns if present (even for CTAS with columns)
7284        if !ct.columns.is_empty() {
7285            if self.config.pretty {
7286                // Pretty print: each column on new line
7287                self.write(" (");
7288                self.write_newline();
7289                self.indent_level += 1;
7290                for (i, col) in ct.columns.iter().enumerate() {
7291                    if i > 0 {
7292                        self.write(",");
7293                        self.write_newline();
7294                    }
7295                    self.write_indent();
7296                    self.generate_column_def(col)?;
7297                }
7298                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7299                for constraint in &ct.constraints {
7300                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7301                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7302                        if columns.len() == 1
7303                            && name.is_none()
7304                            && self
7305                                .sqlite_inline_pk_columns
7306                                .contains(&columns[0].name.to_lowercase())
7307                        {
7308                            continue;
7309                        }
7310                    }
7311                    self.write(",");
7312                    self.write_newline();
7313                    self.write_indent();
7314                    self.generate_table_constraint(constraint)?;
7315                }
7316                self.indent_level -= 1;
7317                self.write_newline();
7318                self.write(")");
7319            } else {
7320                self.write(" (");
7321                for (i, col) in ct.columns.iter().enumerate() {
7322                    if i > 0 {
7323                        self.write(", ");
7324                    }
7325                    self.generate_column_def(col)?;
7326                }
7327                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7328                let mut first_constraint = true;
7329                for constraint in &ct.constraints {
7330                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7331                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7332                        if columns.len() == 1
7333                            && name.is_none()
7334                            && self
7335                                .sqlite_inline_pk_columns
7336                                .contains(&columns[0].name.to_lowercase())
7337                        {
7338                            continue;
7339                        }
7340                    }
7341                    if first_constraint {
7342                        self.write(", ");
7343                        first_constraint = false;
7344                    } else {
7345                        self.write(", ");
7346                    }
7347                    self.generate_table_constraint(constraint)?;
7348                }
7349                self.write(")");
7350            }
7351        } else if !ct.constraints.is_empty() {
7352            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
7353            let has_like_only = ct
7354                .constraints
7355                .iter()
7356                .all(|c| matches!(c, TableConstraint::Like { .. }));
7357            let has_tags_only = ct
7358                .constraints
7359                .iter()
7360                .all(|c| matches!(c, TableConstraint::Tags(_)));
7361            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
7362            // Most dialects: CREATE TABLE A LIKE B (no parens)
7363            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
7364            let is_pg_like = matches!(
7365                self.config.dialect,
7366                Some(crate::dialects::DialectType::PostgreSQL)
7367                    | Some(crate::dialects::DialectType::CockroachDB)
7368                    | Some(crate::dialects::DialectType::Materialize)
7369                    | Some(crate::dialects::DialectType::RisingWave)
7370                    | Some(crate::dialects::DialectType::Redshift)
7371                    | Some(crate::dialects::DialectType::Presto)
7372                    | Some(crate::dialects::DialectType::Trino)
7373                    | Some(crate::dialects::DialectType::Athena)
7374            );
7375            let use_parens = if has_like_only {
7376                is_pg_like
7377            } else {
7378                !has_tags_only
7379            };
7380            if self.config.pretty && use_parens {
7381                self.write(" (");
7382                self.write_newline();
7383                self.indent_level += 1;
7384                for (i, constraint) in ct.constraints.iter().enumerate() {
7385                    if i > 0 {
7386                        self.write(",");
7387                        self.write_newline();
7388                    }
7389                    self.write_indent();
7390                    self.generate_table_constraint(constraint)?;
7391                }
7392                self.indent_level -= 1;
7393                self.write_newline();
7394                self.write(")");
7395            } else {
7396                if use_parens {
7397                    self.write(" (");
7398                } else {
7399                    self.write_space();
7400                }
7401                for (i, constraint) in ct.constraints.iter().enumerate() {
7402                    if i > 0 {
7403                        self.write(", ");
7404                    }
7405                    self.generate_table_constraint(constraint)?;
7406                }
7407                if use_parens {
7408                    self.write(")");
7409                }
7410            }
7411        }
7412
7413        // TSQL ON filegroup or ON filegroup (partition_column) clause
7414        if let Some(ref on_prop) = ct.on_property {
7415            self.write(" ");
7416            self.write_keyword("ON");
7417            self.write(" ");
7418            self.generate_expression(&on_prop.this)?;
7419        }
7420
7421        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
7422        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
7423        if !is_clickhouse {
7424            for prop in &ct.properties {
7425                if let Expression::SchemaCommentProperty(_) = prop {
7426                    if self.config.pretty {
7427                        self.write_newline();
7428                    } else {
7429                        self.write_space();
7430                    }
7431                    self.generate_expression(prop)?;
7432                }
7433            }
7434        }
7435
7436        // WITH properties (output after columns if columns exist, otherwise before AS)
7437        if !ct.with_properties.is_empty() {
7438            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
7439            let is_snowflake_special_table = matches!(
7440                self.config.dialect,
7441                Some(crate::dialects::DialectType::Snowflake)
7442            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
7443                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
7444            if is_snowflake_special_table {
7445                for (key, value) in &ct.with_properties {
7446                    self.write_space();
7447                    self.write(key);
7448                    self.write("=");
7449                    self.write(value);
7450                }
7451            } else if self.config.pretty {
7452                self.write_newline();
7453                self.write_keyword("WITH");
7454                self.write(" (");
7455                self.write_newline();
7456                self.indent_level += 1;
7457                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
7458                    if i > 0 {
7459                        self.write(",");
7460                        self.write_newline();
7461                    }
7462                    self.write_indent();
7463                    self.write(key);
7464                    self.write("=");
7465                    self.write(value);
7466                }
7467                self.indent_level -= 1;
7468                self.write_newline();
7469                self.write(")");
7470            } else {
7471                self.write_space();
7472                self.write_keyword("WITH");
7473                self.write(" (");
7474                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
7475                    if i > 0 {
7476                        self.write(", ");
7477                    }
7478                    self.write(key);
7479                    self.write("=");
7480                    self.write(value);
7481                }
7482                self.write(")");
7483            }
7484        }
7485
7486        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
7487            if is_clickhouse && ct.as_select.is_some() {
7488                let mut pre = Vec::new();
7489                let mut post = Vec::new();
7490                for prop in &ct.properties {
7491                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
7492                        post.push(prop);
7493                    } else {
7494                        pre.push(prop);
7495                    }
7496                }
7497                (pre, post)
7498            } else {
7499                (ct.properties.iter().collect(), Vec::new())
7500            };
7501
7502        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
7503        for prop in pre_as_properties {
7504            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
7505            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
7506                continue;
7507            }
7508            if self.config.pretty {
7509                self.write_newline();
7510            } else {
7511                self.write_space();
7512            }
7513            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
7514            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
7515            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
7516            if let Expression::Properties(props) = prop {
7517                let is_hive_dialect = matches!(
7518                    self.config.dialect,
7519                    Some(crate::dialects::DialectType::Hive)
7520                        | Some(crate::dialects::DialectType::Spark)
7521                        | Some(crate::dialects::DialectType::Databricks)
7522                        | Some(crate::dialects::DialectType::Athena)
7523                );
7524                let is_doris_starrocks = matches!(
7525                    self.config.dialect,
7526                    Some(crate::dialects::DialectType::Doris)
7527                        | Some(crate::dialects::DialectType::StarRocks)
7528                );
7529                if is_hive_dialect {
7530                    self.generate_tblproperties_clause(&props.expressions)?;
7531                } else if is_doris_starrocks {
7532                    self.generate_properties_clause(&props.expressions)?;
7533                } else {
7534                    self.generate_options_clause(&props.expressions)?;
7535                }
7536            } else {
7537                self.generate_expression(prop)?;
7538            }
7539        }
7540
7541        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
7542        for prop in &ct.post_table_properties {
7543            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
7544                self.write(" WITH(");
7545                self.generate_system_versioning_content(svp)?;
7546                self.write(")");
7547            } else if let Expression::Properties(props) = prop {
7548                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
7549                let is_doris_starrocks = matches!(
7550                    self.config.dialect,
7551                    Some(crate::dialects::DialectType::Doris)
7552                        | Some(crate::dialects::DialectType::StarRocks)
7553                );
7554                self.write_space();
7555                if is_doris_starrocks {
7556                    self.generate_properties_clause(&props.expressions)?;
7557                } else {
7558                    self.generate_options_clause(&props.expressions)?;
7559                }
7560            } else {
7561                self.write_space();
7562                self.generate_expression(prop)?;
7563            }
7564        }
7565
7566        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
7567        // Only output for StarRocks target
7568        if let Some(ref rollup) = ct.rollup {
7569            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
7570                self.write_space();
7571                self.generate_rollup_property(rollup)?;
7572            }
7573        }
7574
7575        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
7576        // Only output for MySQL-compatible dialects; strip for others during transpilation
7577        // COMMENT is also used by Hive/Spark so we selectively preserve it
7578        let is_mysql_compatible = matches!(
7579            self.config.dialect,
7580            Some(DialectType::MySQL)
7581                | Some(DialectType::SingleStore)
7582                | Some(DialectType::Doris)
7583                | Some(DialectType::StarRocks)
7584                | None
7585        );
7586        let is_hive_compatible = matches!(
7587            self.config.dialect,
7588            Some(DialectType::Hive)
7589                | Some(DialectType::Spark)
7590                | Some(DialectType::Databricks)
7591                | Some(DialectType::Athena)
7592        );
7593        let mysql_pretty_options =
7594            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
7595        for (key, value) in &ct.mysql_table_options {
7596            // Skip non-MySQL-specific options for non-MySQL targets
7597            let should_output = if is_mysql_compatible {
7598                true
7599            } else if is_hive_compatible && key == "COMMENT" {
7600                true // COMMENT is valid in Hive/Spark table definitions
7601            } else {
7602                false
7603            };
7604            if should_output {
7605                if mysql_pretty_options {
7606                    self.write_newline();
7607                    self.write_indent();
7608                } else {
7609                    self.write_space();
7610                }
7611                self.write_keyword(key);
7612                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
7613                if key == "COMMENT" && !self.config.schema_comment_with_eq {
7614                    self.write_space();
7615                } else {
7616                    self.write("=");
7617                }
7618                self.write(value);
7619            }
7620        }
7621
7622        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
7623        if ct.temporary
7624            && matches!(
7625                self.config.dialect,
7626                Some(DialectType::Spark) | Some(DialectType::Databricks)
7627            )
7628            && ct.as_select.is_none()
7629        {
7630            self.write_space();
7631            self.write_keyword("USING PARQUET");
7632        }
7633
7634        // PostgreSQL INHERITS clause
7635        if !ct.inherits.is_empty() {
7636            self.write_space();
7637            self.write_keyword("INHERITS");
7638            self.write(" (");
7639            for (i, parent) in ct.inherits.iter().enumerate() {
7640                if i > 0 {
7641                    self.write(", ");
7642                }
7643                self.generate_table(parent)?;
7644            }
7645            self.write(")");
7646        }
7647
7648        // CREATE TABLE AS SELECT
7649        if let Some(ref query) = ct.as_select {
7650            self.write_space();
7651            self.write_keyword("AS");
7652            self.write_space();
7653            if ct.as_select_parenthesized {
7654                self.write("(");
7655            }
7656            self.generate_expression(query)?;
7657            if ct.as_select_parenthesized {
7658                self.write(")");
7659            }
7660
7661            // Teradata: WITH DATA / WITH NO DATA
7662            if let Some(with_data) = ct.with_data {
7663                self.write_space();
7664                self.write_keyword("WITH");
7665                if !with_data {
7666                    self.write_space();
7667                    self.write_keyword("NO");
7668                }
7669                self.write_space();
7670                self.write_keyword("DATA");
7671            }
7672
7673            // Teradata: AND STATISTICS / AND NO STATISTICS
7674            if let Some(with_statistics) = ct.with_statistics {
7675                self.write_space();
7676                self.write_keyword("AND");
7677                if !with_statistics {
7678                    self.write_space();
7679                    self.write_keyword("NO");
7680                }
7681                self.write_space();
7682                self.write_keyword("STATISTICS");
7683            }
7684
7685            // Teradata: Index specifications
7686            for index in &ct.teradata_indexes {
7687                self.write_space();
7688                match index.kind {
7689                    TeradataIndexKind::NoPrimary => {
7690                        self.write_keyword("NO PRIMARY INDEX");
7691                    }
7692                    TeradataIndexKind::Primary => {
7693                        self.write_keyword("PRIMARY INDEX");
7694                    }
7695                    TeradataIndexKind::PrimaryAmp => {
7696                        self.write_keyword("PRIMARY AMP INDEX");
7697                    }
7698                    TeradataIndexKind::Unique => {
7699                        self.write_keyword("UNIQUE INDEX");
7700                    }
7701                    TeradataIndexKind::UniquePrimary => {
7702                        self.write_keyword("UNIQUE PRIMARY INDEX");
7703                    }
7704                    TeradataIndexKind::Secondary => {
7705                        self.write_keyword("INDEX");
7706                    }
7707                }
7708                // Output index name if present
7709                if let Some(ref name) = index.name {
7710                    self.write_space();
7711                    self.write(name);
7712                }
7713                // Output columns if present
7714                if !index.columns.is_empty() {
7715                    self.write(" (");
7716                    for (i, col) in index.columns.iter().enumerate() {
7717                        if i > 0 {
7718                            self.write(", ");
7719                        }
7720                        self.write(col);
7721                    }
7722                    self.write(")");
7723                }
7724            }
7725
7726            // Teradata: ON COMMIT behavior for volatile tables
7727            if let Some(ref on_commit) = ct.on_commit {
7728                self.write_space();
7729                self.write_keyword("ON COMMIT");
7730                self.write_space();
7731                match on_commit {
7732                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
7733                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
7734                }
7735            }
7736
7737            if !post_as_properties.is_empty() {
7738                for prop in post_as_properties {
7739                    self.write_space();
7740                    self.generate_expression(prop)?;
7741                }
7742            }
7743
7744            // Restore Athena Hive context before early return
7745            self.athena_hive_context = saved_athena_hive_context;
7746            return Ok(());
7747        }
7748
7749        // ON COMMIT behavior (for non-CTAS tables)
7750        if let Some(ref on_commit) = ct.on_commit {
7751            self.write_space();
7752            self.write_keyword("ON COMMIT");
7753            self.write_space();
7754            match on_commit {
7755                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
7756                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
7757            }
7758        }
7759
7760        // Restore Athena Hive context
7761        self.athena_hive_context = saved_athena_hive_context;
7762
7763        Ok(())
7764    }
7765
7766    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
7767    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
7768    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
7769        // Output column name
7770        self.generate_identifier(&col.name)?;
7771        // Output data type if known
7772        if !matches!(col.data_type, DataType::Unknown) {
7773            self.write_space();
7774            self.generate_data_type(&col.data_type)?;
7775        }
7776        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
7777        for constraint in &col.constraints {
7778            if let ColumnConstraint::Path(path_expr) = constraint {
7779                self.write_space();
7780                self.write_keyword("PATH");
7781                self.write_space();
7782                self.generate_expression(path_expr)?;
7783            }
7784        }
7785        Ok(())
7786    }
7787
7788    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
7789        // Check if this is a TSQL computed column (no data type)
7790        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
7791            && col
7792                .constraints
7793                .iter()
7794                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
7795        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
7796        let omit_computed_type = !self.config.computed_column_with_type
7797            && col
7798                .constraints
7799                .iter()
7800                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
7801
7802        // Check if this is a partition column spec (no data type, type is Unknown)
7803        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
7804        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
7805
7806        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
7807        // Also check the no_type flag for SQLite columns without types
7808        let has_no_type = col.no_type
7809            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
7810                && col.constraints.is_empty());
7811
7812        self.generate_identifier(&col.name)?;
7813
7814        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
7815        let serial_expansion = if matches!(
7816            self.config.dialect,
7817            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
7818        ) {
7819            if let DataType::Custom { ref name } = col.data_type {
7820                match name.to_uppercase().as_str() {
7821                    "SERIAL" => Some("INT"),
7822                    "BIGSERIAL" => Some("BIGINT"),
7823                    "SMALLSERIAL" => Some("SMALLINT"),
7824                    _ => None,
7825                }
7826            } else {
7827                None
7828            }
7829        } else {
7830            None
7831        };
7832
7833        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
7834        {
7835            self.write_space();
7836            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
7837            // since ClickHouse uses explicit Nullable() in its type system.
7838            let saved_nullable_depth = self.clickhouse_nullable_depth;
7839            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
7840                self.clickhouse_nullable_depth = -1;
7841            }
7842            if let Some(int_type) = serial_expansion {
7843                // SERIAL -> INT (+ constraints added below)
7844                self.write_keyword(int_type);
7845            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
7846                // For DuckDB: convert unsigned integer types to their unsigned equivalents
7847                let unsigned_type = match &col.data_type {
7848                    DataType::Int { .. } => Some("UINTEGER"),
7849                    DataType::BigInt { .. } => Some("UBIGINT"),
7850                    DataType::SmallInt { .. } => Some("USMALLINT"),
7851                    DataType::TinyInt { .. } => Some("UTINYINT"),
7852                    _ => None,
7853                };
7854                if let Some(utype) = unsigned_type {
7855                    self.write_keyword(utype);
7856                } else {
7857                    self.generate_data_type(&col.data_type)?;
7858                }
7859            } else {
7860                self.generate_data_type(&col.data_type)?;
7861            }
7862            self.clickhouse_nullable_depth = saved_nullable_depth;
7863        }
7864
7865        // MySQL type modifiers (must come right after data type)
7866        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
7867        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
7868            self.write_space();
7869            self.write_keyword("UNSIGNED");
7870        }
7871        if col.zerofill {
7872            self.write_space();
7873            self.write_keyword("ZEROFILL");
7874        }
7875
7876        // Teradata column attributes (must come right after data type, in specific order)
7877        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
7878
7879        if let Some(ref charset) = col.character_set {
7880            self.write_space();
7881            self.write_keyword("CHARACTER SET");
7882            self.write_space();
7883            self.write(charset);
7884        }
7885
7886        if col.uppercase {
7887            self.write_space();
7888            self.write_keyword("UPPERCASE");
7889        }
7890
7891        if let Some(casespecific) = col.casespecific {
7892            self.write_space();
7893            if casespecific {
7894                self.write_keyword("CASESPECIFIC");
7895            } else {
7896                self.write_keyword("NOT CASESPECIFIC");
7897            }
7898        }
7899
7900        if let Some(ref format) = col.format {
7901            self.write_space();
7902            self.write_keyword("FORMAT");
7903            self.write(" '");
7904            self.write(format);
7905            self.write("'");
7906        }
7907
7908        if let Some(ref title) = col.title {
7909            self.write_space();
7910            self.write_keyword("TITLE");
7911            self.write(" '");
7912            self.write(title);
7913            self.write("'");
7914        }
7915
7916        if let Some(length) = col.inline_length {
7917            self.write_space();
7918            self.write_keyword("INLINE LENGTH");
7919            self.write(" ");
7920            self.write(&length.to_string());
7921        }
7922
7923        if let Some(ref compress) = col.compress {
7924            self.write_space();
7925            self.write_keyword("COMPRESS");
7926            if !compress.is_empty() {
7927                // Single string literal: output without parentheses (Teradata syntax)
7928                if compress.len() == 1 {
7929                    if let Expression::Literal(Literal::String(_)) = &compress[0] {
7930                        self.write_space();
7931                        self.generate_expression(&compress[0])?;
7932                    } else {
7933                        self.write(" (");
7934                        self.generate_expression(&compress[0])?;
7935                        self.write(")");
7936                    }
7937                } else {
7938                    self.write(" (");
7939                    for (i, val) in compress.iter().enumerate() {
7940                        if i > 0 {
7941                            self.write(", ");
7942                        }
7943                        self.generate_expression(val)?;
7944                    }
7945                    self.write(")");
7946                }
7947            }
7948        }
7949
7950        // Column constraints - output in original order if constraint_order is populated
7951        // Otherwise fall back to legacy fixed order for backward compatibility
7952        if !col.constraint_order.is_empty() {
7953            // Use constraint_order for original ordering
7954            // Track indices for constraints stored in the constraints Vec
7955            let mut references_idx = 0;
7956            let mut check_idx = 0;
7957            let mut generated_idx = 0;
7958            let mut collate_idx = 0;
7959            let mut comment_idx = 0;
7960            // The preprocessing in dialects/mod.rs now handles the correct ordering of
7961            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
7962            let defer_not_null_after_identity = false;
7963            let mut pending_not_null_after_identity = false;
7964
7965            for constraint_type in &col.constraint_order {
7966                match constraint_type {
7967                    ConstraintType::PrimaryKey => {
7968                        // Materialize doesn't support PRIMARY KEY column constraints
7969                        if col.primary_key
7970                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
7971                        {
7972                            if let Some(ref cname) = col.primary_key_constraint_name {
7973                                self.write_space();
7974                                self.write_keyword("CONSTRAINT");
7975                                self.write_space();
7976                                self.write(cname);
7977                            }
7978                            self.write_space();
7979                            self.write_keyword("PRIMARY KEY");
7980                            if let Some(ref order) = col.primary_key_order {
7981                                self.write_space();
7982                                match order {
7983                                    SortOrder::Asc => self.write_keyword("ASC"),
7984                                    SortOrder::Desc => self.write_keyword("DESC"),
7985                                }
7986                            }
7987                        }
7988                    }
7989                    ConstraintType::Unique => {
7990                        if col.unique {
7991                            if let Some(ref cname) = col.unique_constraint_name {
7992                                self.write_space();
7993                                self.write_keyword("CONSTRAINT");
7994                                self.write_space();
7995                                self.write(cname);
7996                            }
7997                            self.write_space();
7998                            self.write_keyword("UNIQUE");
7999                            // PostgreSQL 15+: NULLS NOT DISTINCT
8000                            if col.unique_nulls_not_distinct {
8001                                self.write(" NULLS NOT DISTINCT");
8002                            }
8003                        }
8004                    }
8005                    ConstraintType::NotNull => {
8006                        if col.nullable == Some(false) {
8007                            if defer_not_null_after_identity {
8008                                pending_not_null_after_identity = true;
8009                                continue;
8010                            }
8011                            if let Some(ref cname) = col.not_null_constraint_name {
8012                                self.write_space();
8013                                self.write_keyword("CONSTRAINT");
8014                                self.write_space();
8015                                self.write(cname);
8016                            }
8017                            self.write_space();
8018                            self.write_keyword("NOT NULL");
8019                        }
8020                    }
8021                    ConstraintType::Null => {
8022                        if col.nullable == Some(true) {
8023                            self.write_space();
8024                            self.write_keyword("NULL");
8025                        }
8026                    }
8027                    ConstraintType::Default => {
8028                        if let Some(ref default) = col.default {
8029                            self.write_space();
8030                            self.write_keyword("DEFAULT");
8031                            self.write_space();
8032                            self.generate_expression(default)?;
8033                        }
8034                    }
8035                    ConstraintType::AutoIncrement => {
8036                        if col.auto_increment {
8037                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
8038                            if matches!(
8039                                self.config.dialect,
8040                                Some(crate::dialects::DialectType::DuckDB)
8041                            ) {
8042                                // Skip - DuckDB uses sequences or rowid instead
8043                            } else if matches!(
8044                                self.config.dialect,
8045                                Some(crate::dialects::DialectType::Materialize)
8046                            ) {
8047                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
8048                                if !matches!(col.nullable, Some(false)) {
8049                                    self.write_space();
8050                                    self.write_keyword("NOT NULL");
8051                                }
8052                            } else if matches!(
8053                                self.config.dialect,
8054                                Some(crate::dialects::DialectType::PostgreSQL)
8055                            ) {
8056                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
8057                                self.write_space();
8058                                self.generate_auto_increment_keyword(col)?;
8059                            } else {
8060                                self.write_space();
8061                                self.generate_auto_increment_keyword(col)?;
8062                                if pending_not_null_after_identity {
8063                                    self.write_space();
8064                                    self.write_keyword("NOT NULL");
8065                                    pending_not_null_after_identity = false;
8066                                }
8067                            }
8068                        } // close else for DuckDB skip
8069                    }
8070                    ConstraintType::References => {
8071                        // Find next References constraint
8072                        while references_idx < col.constraints.len() {
8073                            if let ColumnConstraint::References(fk_ref) =
8074                                &col.constraints[references_idx]
8075                            {
8076                                // CONSTRAINT name if present
8077                                if let Some(ref name) = fk_ref.constraint_name {
8078                                    self.write_space();
8079                                    self.write_keyword("CONSTRAINT");
8080                                    self.write_space();
8081                                    self.write(name);
8082                                }
8083                                self.write_space();
8084                                if fk_ref.has_foreign_key_keywords {
8085                                    self.write_keyword("FOREIGN KEY");
8086                                    self.write_space();
8087                                }
8088                                self.write_keyword("REFERENCES");
8089                                self.write_space();
8090                                self.generate_table(&fk_ref.table)?;
8091                                if !fk_ref.columns.is_empty() {
8092                                    self.write(" (");
8093                                    for (i, c) in fk_ref.columns.iter().enumerate() {
8094                                        if i > 0 {
8095                                            self.write(", ");
8096                                        }
8097                                        self.generate_identifier(c)?;
8098                                    }
8099                                    self.write(")");
8100                                }
8101                                self.generate_referential_actions(fk_ref)?;
8102                                references_idx += 1;
8103                                break;
8104                            }
8105                            references_idx += 1;
8106                        }
8107                    }
8108                    ConstraintType::Check => {
8109                        // Find next Check constraint
8110                        while check_idx < col.constraints.len() {
8111                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
8112                                // Output CONSTRAINT name if present (only for first CHECK)
8113                                if check_idx == 0 {
8114                                    if let Some(ref cname) = col.check_constraint_name {
8115                                        self.write_space();
8116                                        self.write_keyword("CONSTRAINT");
8117                                        self.write_space();
8118                                        self.write(cname);
8119                                    }
8120                                }
8121                                self.write_space();
8122                                self.write_keyword("CHECK");
8123                                self.write(" (");
8124                                self.generate_expression(expr)?;
8125                                self.write(")");
8126                                check_idx += 1;
8127                                break;
8128                            }
8129                            check_idx += 1;
8130                        }
8131                    }
8132                    ConstraintType::GeneratedAsIdentity => {
8133                        // Find next GeneratedAsIdentity constraint
8134                        while generated_idx < col.constraints.len() {
8135                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
8136                                &col.constraints[generated_idx]
8137                            {
8138                                self.write_space();
8139                                // Redshift uses IDENTITY(start, increment) syntax
8140                                if matches!(
8141                                    self.config.dialect,
8142                                    Some(crate::dialects::DialectType::Redshift)
8143                                ) {
8144                                    self.write_keyword("IDENTITY");
8145                                    self.write("(");
8146                                    if let Some(ref start) = gen.start {
8147                                        self.generate_expression(start)?;
8148                                    } else {
8149                                        self.write("0");
8150                                    }
8151                                    self.write(", ");
8152                                    if let Some(ref incr) = gen.increment {
8153                                        self.generate_expression(incr)?;
8154                                    } else {
8155                                        self.write("1");
8156                                    }
8157                                    self.write(")");
8158                                } else {
8159                                    self.write_keyword("GENERATED");
8160                                    if gen.always {
8161                                        self.write_space();
8162                                        self.write_keyword("ALWAYS");
8163                                    } else {
8164                                        self.write_space();
8165                                        self.write_keyword("BY DEFAULT");
8166                                        if gen.on_null {
8167                                            self.write_space();
8168                                            self.write_keyword("ON NULL");
8169                                        }
8170                                    }
8171                                    self.write_space();
8172                                    self.write_keyword("AS IDENTITY");
8173
8174                                    let has_options = gen.start.is_some()
8175                                        || gen.increment.is_some()
8176                                        || gen.minvalue.is_some()
8177                                        || gen.maxvalue.is_some()
8178                                        || gen.cycle.is_some();
8179                                    if has_options {
8180                                        self.write(" (");
8181                                        let mut first = true;
8182                                        if let Some(ref start) = gen.start {
8183                                            if !first {
8184                                                self.write(" ");
8185                                            }
8186                                            first = false;
8187                                            self.write_keyword("START WITH");
8188                                            self.write_space();
8189                                            self.generate_expression(start)?;
8190                                        }
8191                                        if let Some(ref incr) = gen.increment {
8192                                            if !first {
8193                                                self.write(" ");
8194                                            }
8195                                            first = false;
8196                                            self.write_keyword("INCREMENT BY");
8197                                            self.write_space();
8198                                            self.generate_expression(incr)?;
8199                                        }
8200                                        if let Some(ref minv) = gen.minvalue {
8201                                            if !first {
8202                                                self.write(" ");
8203                                            }
8204                                            first = false;
8205                                            self.write_keyword("MINVALUE");
8206                                            self.write_space();
8207                                            self.generate_expression(minv)?;
8208                                        }
8209                                        if let Some(ref maxv) = gen.maxvalue {
8210                                            if !first {
8211                                                self.write(" ");
8212                                            }
8213                                            first = false;
8214                                            self.write_keyword("MAXVALUE");
8215                                            self.write_space();
8216                                            self.generate_expression(maxv)?;
8217                                        }
8218                                        if let Some(cycle) = gen.cycle {
8219                                            if !first {
8220                                                self.write(" ");
8221                                            }
8222                                            if cycle {
8223                                                self.write_keyword("CYCLE");
8224                                            } else {
8225                                                self.write_keyword("NO CYCLE");
8226                                            }
8227                                        }
8228                                        self.write(")");
8229                                    }
8230                                }
8231                                generated_idx += 1;
8232                                break;
8233                            }
8234                            generated_idx += 1;
8235                        }
8236                    }
8237                    ConstraintType::Collate => {
8238                        // Find next Collate constraint
8239                        while collate_idx < col.constraints.len() {
8240                            if let ColumnConstraint::Collate(collation) =
8241                                &col.constraints[collate_idx]
8242                            {
8243                                self.write_space();
8244                                self.write_keyword("COLLATE");
8245                                self.write_space();
8246                                self.generate_identifier(collation)?;
8247                                collate_idx += 1;
8248                                break;
8249                            }
8250                            collate_idx += 1;
8251                        }
8252                    }
8253                    ConstraintType::Comment => {
8254                        // Find next Comment constraint
8255                        while comment_idx < col.constraints.len() {
8256                            if let ColumnConstraint::Comment(comment) =
8257                                &col.constraints[comment_idx]
8258                            {
8259                                self.write_space();
8260                                self.write_keyword("COMMENT");
8261                                self.write_space();
8262                                self.generate_string_literal(comment)?;
8263                                comment_idx += 1;
8264                                break;
8265                            }
8266                            comment_idx += 1;
8267                        }
8268                    }
8269                    ConstraintType::Tags => {
8270                        // Find next Tags constraint (Snowflake)
8271                        for constraint in &col.constraints {
8272                            if let ColumnConstraint::Tags(tags) = constraint {
8273                                self.write_space();
8274                                self.write_keyword("TAG");
8275                                self.write(" (");
8276                                for (i, expr) in tags.expressions.iter().enumerate() {
8277                                    if i > 0 {
8278                                        self.write(", ");
8279                                    }
8280                                    self.generate_expression(expr)?;
8281                                }
8282                                self.write(")");
8283                                break;
8284                            }
8285                        }
8286                    }
8287                    ConstraintType::ComputedColumn => {
8288                        // Find next ComputedColumn constraint
8289                        for constraint in &col.constraints {
8290                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
8291                                self.write_space();
8292                                self.generate_computed_column_inline(cc)?;
8293                                break;
8294                            }
8295                        }
8296                    }
8297                    ConstraintType::GeneratedAsRow => {
8298                        // Find next GeneratedAsRow constraint
8299                        for constraint in &col.constraints {
8300                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
8301                                self.write_space();
8302                                self.generate_generated_as_row_inline(gar)?;
8303                                break;
8304                            }
8305                        }
8306                    }
8307                    ConstraintType::OnUpdate => {
8308                        if let Some(ref expr) = col.on_update {
8309                            self.write_space();
8310                            self.write_keyword("ON UPDATE");
8311                            self.write_space();
8312                            self.generate_expression(expr)?;
8313                        }
8314                    }
8315                    ConstraintType::Encode => {
8316                        if let Some(ref encoding) = col.encoding {
8317                            self.write_space();
8318                            self.write_keyword("ENCODE");
8319                            self.write_space();
8320                            self.write(encoding);
8321                        }
8322                    }
8323                    ConstraintType::Path => {
8324                        // Find next Path constraint
8325                        for constraint in &col.constraints {
8326                            if let ColumnConstraint::Path(path_expr) = constraint {
8327                                self.write_space();
8328                                self.write_keyword("PATH");
8329                                self.write_space();
8330                                self.generate_expression(path_expr)?;
8331                                break;
8332                            }
8333                        }
8334                    }
8335                }
8336            }
8337            if pending_not_null_after_identity {
8338                self.write_space();
8339                self.write_keyword("NOT NULL");
8340            }
8341        } else {
8342            // Legacy fixed order for backward compatibility
8343            if col.primary_key {
8344                self.write_space();
8345                self.write_keyword("PRIMARY KEY");
8346                if let Some(ref order) = col.primary_key_order {
8347                    self.write_space();
8348                    match order {
8349                        SortOrder::Asc => self.write_keyword("ASC"),
8350                        SortOrder::Desc => self.write_keyword("DESC"),
8351                    }
8352                }
8353            }
8354
8355            if col.unique {
8356                self.write_space();
8357                self.write_keyword("UNIQUE");
8358                // PostgreSQL 15+: NULLS NOT DISTINCT
8359                if col.unique_nulls_not_distinct {
8360                    self.write(" NULLS NOT DISTINCT");
8361                }
8362            }
8363
8364            match col.nullable {
8365                Some(false) => {
8366                    self.write_space();
8367                    self.write_keyword("NOT NULL");
8368                }
8369                Some(true) => {
8370                    self.write_space();
8371                    self.write_keyword("NULL");
8372                }
8373                None => {}
8374            }
8375
8376            if let Some(ref default) = col.default {
8377                self.write_space();
8378                self.write_keyword("DEFAULT");
8379                self.write_space();
8380                self.generate_expression(default)?;
8381            }
8382
8383            if col.auto_increment {
8384                self.write_space();
8385                self.generate_auto_increment_keyword(col)?;
8386            }
8387
8388            // Column-level constraints from Vec
8389            for constraint in &col.constraints {
8390                match constraint {
8391                    ColumnConstraint::References(fk_ref) => {
8392                        self.write_space();
8393                        if fk_ref.has_foreign_key_keywords {
8394                            self.write_keyword("FOREIGN KEY");
8395                            self.write_space();
8396                        }
8397                        self.write_keyword("REFERENCES");
8398                        self.write_space();
8399                        self.generate_table(&fk_ref.table)?;
8400                        if !fk_ref.columns.is_empty() {
8401                            self.write(" (");
8402                            for (i, c) in fk_ref.columns.iter().enumerate() {
8403                                if i > 0 {
8404                                    self.write(", ");
8405                                }
8406                                self.generate_identifier(c)?;
8407                            }
8408                            self.write(")");
8409                        }
8410                        self.generate_referential_actions(fk_ref)?;
8411                    }
8412                    ColumnConstraint::Check(expr) => {
8413                        self.write_space();
8414                        self.write_keyword("CHECK");
8415                        self.write(" (");
8416                        self.generate_expression(expr)?;
8417                        self.write(")");
8418                    }
8419                    ColumnConstraint::GeneratedAsIdentity(gen) => {
8420                        self.write_space();
8421                        // Redshift uses IDENTITY(start, increment) syntax
8422                        if matches!(
8423                            self.config.dialect,
8424                            Some(crate::dialects::DialectType::Redshift)
8425                        ) {
8426                            self.write_keyword("IDENTITY");
8427                            self.write("(");
8428                            if let Some(ref start) = gen.start {
8429                                self.generate_expression(start)?;
8430                            } else {
8431                                self.write("0");
8432                            }
8433                            self.write(", ");
8434                            if let Some(ref incr) = gen.increment {
8435                                self.generate_expression(incr)?;
8436                            } else {
8437                                self.write("1");
8438                            }
8439                            self.write(")");
8440                        } else {
8441                            self.write_keyword("GENERATED");
8442                            if gen.always {
8443                                self.write_space();
8444                                self.write_keyword("ALWAYS");
8445                            } else {
8446                                self.write_space();
8447                                self.write_keyword("BY DEFAULT");
8448                                if gen.on_null {
8449                                    self.write_space();
8450                                    self.write_keyword("ON NULL");
8451                                }
8452                            }
8453                            self.write_space();
8454                            self.write_keyword("AS IDENTITY");
8455
8456                            let has_options = gen.start.is_some()
8457                                || gen.increment.is_some()
8458                                || gen.minvalue.is_some()
8459                                || gen.maxvalue.is_some()
8460                                || gen.cycle.is_some();
8461                            if has_options {
8462                                self.write(" (");
8463                                let mut first = true;
8464                                if let Some(ref start) = gen.start {
8465                                    if !first {
8466                                        self.write(" ");
8467                                    }
8468                                    first = false;
8469                                    self.write_keyword("START WITH");
8470                                    self.write_space();
8471                                    self.generate_expression(start)?;
8472                                }
8473                                if let Some(ref incr) = gen.increment {
8474                                    if !first {
8475                                        self.write(" ");
8476                                    }
8477                                    first = false;
8478                                    self.write_keyword("INCREMENT BY");
8479                                    self.write_space();
8480                                    self.generate_expression(incr)?;
8481                                }
8482                                if let Some(ref minv) = gen.minvalue {
8483                                    if !first {
8484                                        self.write(" ");
8485                                    }
8486                                    first = false;
8487                                    self.write_keyword("MINVALUE");
8488                                    self.write_space();
8489                                    self.generate_expression(minv)?;
8490                                }
8491                                if let Some(ref maxv) = gen.maxvalue {
8492                                    if !first {
8493                                        self.write(" ");
8494                                    }
8495                                    first = false;
8496                                    self.write_keyword("MAXVALUE");
8497                                    self.write_space();
8498                                    self.generate_expression(maxv)?;
8499                                }
8500                                if let Some(cycle) = gen.cycle {
8501                                    if !first {
8502                                        self.write(" ");
8503                                    }
8504                                    if cycle {
8505                                        self.write_keyword("CYCLE");
8506                                    } else {
8507                                        self.write_keyword("NO CYCLE");
8508                                    }
8509                                }
8510                                self.write(")");
8511                            }
8512                        }
8513                    }
8514                    ColumnConstraint::Collate(collation) => {
8515                        self.write_space();
8516                        self.write_keyword("COLLATE");
8517                        self.write_space();
8518                        self.generate_identifier(collation)?;
8519                    }
8520                    ColumnConstraint::Comment(comment) => {
8521                        self.write_space();
8522                        self.write_keyword("COMMENT");
8523                        self.write_space();
8524                        self.generate_string_literal(comment)?;
8525                    }
8526                    ColumnConstraint::Path(path_expr) => {
8527                        self.write_space();
8528                        self.write_keyword("PATH");
8529                        self.write_space();
8530                        self.generate_expression(path_expr)?;
8531                    }
8532                    _ => {} // Other constraints handled above
8533                }
8534            }
8535
8536            // Redshift: ENCODE encoding_type (legacy path)
8537            if let Some(ref encoding) = col.encoding {
8538                self.write_space();
8539                self.write_keyword("ENCODE");
8540                self.write_space();
8541                self.write(encoding);
8542            }
8543        }
8544
8545        // ClickHouse: CODEC(...)
8546        if let Some(ref codec) = col.codec {
8547            self.write_space();
8548            self.write_keyword("CODEC");
8549            self.write("(");
8550            self.write(codec);
8551            self.write(")");
8552        }
8553
8554        // ClickHouse: EPHEMERAL [expr]
8555        if let Some(ref ephemeral) = col.ephemeral {
8556            self.write_space();
8557            self.write_keyword("EPHEMERAL");
8558            if let Some(ref expr) = ephemeral {
8559                self.write_space();
8560                self.generate_expression(expr)?;
8561            }
8562        }
8563
8564        // ClickHouse: MATERIALIZED expr
8565        if let Some(ref mat_expr) = col.materialized_expr {
8566            self.write_space();
8567            self.write_keyword("MATERIALIZED");
8568            self.write_space();
8569            self.generate_expression(mat_expr)?;
8570        }
8571
8572        // ClickHouse: ALIAS expr
8573        if let Some(ref alias_expr) = col.alias_expr {
8574            self.write_space();
8575            self.write_keyword("ALIAS");
8576            self.write_space();
8577            self.generate_expression(alias_expr)?;
8578        }
8579
8580        // ClickHouse: TTL expr
8581        if let Some(ref ttl_expr) = col.ttl_expr {
8582            self.write_space();
8583            self.write_keyword("TTL");
8584            self.write_space();
8585            self.generate_expression(ttl_expr)?;
8586        }
8587
8588        // TSQL: NOT FOR REPLICATION
8589        if col.not_for_replication
8590            && matches!(
8591                self.config.dialect,
8592                Some(crate::dialects::DialectType::TSQL)
8593                    | Some(crate::dialects::DialectType::Fabric)
8594            )
8595        {
8596            self.write_space();
8597            self.write_keyword("NOT FOR REPLICATION");
8598        }
8599
8600        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
8601        if !col.options.is_empty() {
8602            self.write_space();
8603            self.generate_options_clause(&col.options)?;
8604        }
8605
8606        // SQLite: Inline PRIMARY KEY from table constraint
8607        // This comes at the end, after all existing column constraints
8608        if !col.primary_key
8609            && self
8610                .sqlite_inline_pk_columns
8611                .contains(&col.name.name.to_lowercase())
8612        {
8613            self.write_space();
8614            self.write_keyword("PRIMARY KEY");
8615        }
8616
8617        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
8618        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
8619        if serial_expansion.is_some() {
8620            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
8621                self.write_space();
8622                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
8623            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
8624                self.write_space();
8625                self.write_keyword("NOT NULL");
8626            }
8627        }
8628
8629        Ok(())
8630    }
8631
8632    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
8633        match constraint {
8634            TableConstraint::PrimaryKey {
8635                name,
8636                columns,
8637                include_columns,
8638                modifiers,
8639                has_constraint_keyword,
8640            } => {
8641                if let Some(ref n) = name {
8642                    if *has_constraint_keyword {
8643                        self.write_keyword("CONSTRAINT");
8644                        self.write_space();
8645                        self.generate_identifier(n)?;
8646                        self.write_space();
8647                    }
8648                }
8649                self.write_keyword("PRIMARY KEY");
8650                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
8651                if let Some(ref clustered) = modifiers.clustered {
8652                    self.write_space();
8653                    self.write_keyword(clustered);
8654                }
8655                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
8656                if let Some(ref n) = name {
8657                    if !*has_constraint_keyword {
8658                        self.write_space();
8659                        self.generate_identifier(n)?;
8660                    }
8661                }
8662                self.write(" (");
8663                for (i, col) in columns.iter().enumerate() {
8664                    if i > 0 {
8665                        self.write(", ");
8666                    }
8667                    self.generate_identifier(col)?;
8668                }
8669                self.write(")");
8670                if !include_columns.is_empty() {
8671                    self.write_space();
8672                    self.write_keyword("INCLUDE");
8673                    self.write(" (");
8674                    for (i, col) in include_columns.iter().enumerate() {
8675                        if i > 0 {
8676                            self.write(", ");
8677                        }
8678                        self.generate_identifier(col)?;
8679                    }
8680                    self.write(")");
8681                }
8682                self.generate_constraint_modifiers(modifiers);
8683            }
8684            TableConstraint::Unique {
8685                name,
8686                columns,
8687                columns_parenthesized,
8688                modifiers,
8689                has_constraint_keyword,
8690                nulls_not_distinct,
8691            } => {
8692                if let Some(ref n) = name {
8693                    if *has_constraint_keyword {
8694                        self.write_keyword("CONSTRAINT");
8695                        self.write_space();
8696                        self.generate_identifier(n)?;
8697                        self.write_space();
8698                    }
8699                }
8700                self.write_keyword("UNIQUE");
8701                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
8702                if let Some(ref clustered) = modifiers.clustered {
8703                    self.write_space();
8704                    self.write_keyword(clustered);
8705                }
8706                // PostgreSQL 15+: NULLS NOT DISTINCT
8707                if *nulls_not_distinct {
8708                    self.write(" NULLS NOT DISTINCT");
8709                }
8710                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
8711                if let Some(ref n) = name {
8712                    if !*has_constraint_keyword {
8713                        self.write_space();
8714                        self.generate_identifier(n)?;
8715                    }
8716                }
8717                if *columns_parenthesized {
8718                    self.write(" (");
8719                    for (i, col) in columns.iter().enumerate() {
8720                        if i > 0 {
8721                            self.write(", ");
8722                        }
8723                        self.generate_identifier(col)?;
8724                    }
8725                    self.write(")");
8726                } else {
8727                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
8728                    for col in columns.iter() {
8729                        self.write_space();
8730                        self.generate_identifier(col)?;
8731                    }
8732                }
8733                self.generate_constraint_modifiers(modifiers);
8734            }
8735            TableConstraint::ForeignKey {
8736                name,
8737                columns,
8738                references,
8739                on_delete,
8740                on_update,
8741                modifiers,
8742            } => {
8743                if let Some(ref n) = name {
8744                    self.write_keyword("CONSTRAINT");
8745                    self.write_space();
8746                    self.generate_identifier(n)?;
8747                    self.write_space();
8748                }
8749                self.write_keyword("FOREIGN KEY");
8750                self.write(" (");
8751                for (i, col) in columns.iter().enumerate() {
8752                    if i > 0 {
8753                        self.write(", ");
8754                    }
8755                    self.generate_identifier(col)?;
8756                }
8757                self.write(")");
8758                if let Some(ref refs) = references {
8759                    self.write(" ");
8760                    self.write_keyword("REFERENCES");
8761                    self.write_space();
8762                    self.generate_table(&refs.table)?;
8763                    if !refs.columns.is_empty() {
8764                        if self.config.pretty {
8765                            self.write(" (");
8766                            self.write_newline();
8767                            self.indent_level += 1;
8768                            for (i, col) in refs.columns.iter().enumerate() {
8769                                if i > 0 {
8770                                    self.write(",");
8771                                    self.write_newline();
8772                                }
8773                                self.write_indent();
8774                                self.generate_identifier(col)?;
8775                            }
8776                            self.indent_level -= 1;
8777                            self.write_newline();
8778                            self.write_indent();
8779                            self.write(")");
8780                        } else {
8781                            self.write(" (");
8782                            for (i, col) in refs.columns.iter().enumerate() {
8783                                if i > 0 {
8784                                    self.write(", ");
8785                                }
8786                                self.generate_identifier(col)?;
8787                            }
8788                            self.write(")");
8789                        }
8790                    }
8791                    self.generate_referential_actions(refs)?;
8792                } else {
8793                    // No REFERENCES - output ON DELETE/ON UPDATE directly
8794                    if let Some(ref action) = on_delete {
8795                        self.write_space();
8796                        self.write_keyword("ON DELETE");
8797                        self.write_space();
8798                        self.generate_referential_action(action);
8799                    }
8800                    if let Some(ref action) = on_update {
8801                        self.write_space();
8802                        self.write_keyword("ON UPDATE");
8803                        self.write_space();
8804                        self.generate_referential_action(action);
8805                    }
8806                }
8807                self.generate_constraint_modifiers(modifiers);
8808            }
8809            TableConstraint::Check {
8810                name,
8811                expression,
8812                modifiers,
8813            } => {
8814                if let Some(ref n) = name {
8815                    self.write_keyword("CONSTRAINT");
8816                    self.write_space();
8817                    self.generate_identifier(n)?;
8818                    self.write_space();
8819                }
8820                self.write_keyword("CHECK");
8821                self.write(" (");
8822                self.generate_expression(expression)?;
8823                self.write(")");
8824                self.generate_constraint_modifiers(modifiers);
8825            }
8826            TableConstraint::Index {
8827                name,
8828                columns,
8829                kind,
8830                modifiers,
8831                use_key_keyword,
8832                expression,
8833                index_type,
8834                granularity,
8835            } => {
8836                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
8837                if expression.is_some() {
8838                    self.write_keyword("INDEX");
8839                    if let Some(ref n) = name {
8840                        self.write_space();
8841                        self.generate_identifier(n)?;
8842                    }
8843                    if let Some(ref expr) = expression {
8844                        self.write_space();
8845                        self.generate_expression(expr)?;
8846                    }
8847                    if let Some(ref idx_type) = index_type {
8848                        self.write_space();
8849                        self.write_keyword("TYPE");
8850                        self.write_space();
8851                        self.generate_expression(idx_type)?;
8852                    }
8853                    if let Some(ref gran) = granularity {
8854                        self.write_space();
8855                        self.write_keyword("GRANULARITY");
8856                        self.write_space();
8857                        self.generate_expression(gran)?;
8858                    }
8859                } else {
8860                    // Standard INDEX syntax
8861                    // Determine the index keyword to use
8862                    // MySQL normalizes KEY to INDEX
8863                    use crate::dialects::DialectType;
8864                    let index_keyword = if *use_key_keyword
8865                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
8866                    {
8867                        "KEY"
8868                    } else {
8869                        "INDEX"
8870                    };
8871
8872                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
8873                    if let Some(ref k) = kind {
8874                        self.write_keyword(k);
8875                        // For UNIQUE, don't add INDEX/KEY keyword
8876                        if k != "UNIQUE" {
8877                            self.write_space();
8878                            self.write_keyword(index_keyword);
8879                        }
8880                    } else {
8881                        self.write_keyword(index_keyword);
8882                    }
8883
8884                    // Output USING before name if using_before_columns is true and there's no name
8885                    if modifiers.using_before_columns && name.is_none() {
8886                        if let Some(ref using) = modifiers.using {
8887                            self.write_space();
8888                            self.write_keyword("USING");
8889                            self.write_space();
8890                            self.write_keyword(using);
8891                        }
8892                    }
8893
8894                    // Output index name if present
8895                    if let Some(ref n) = name {
8896                        self.write_space();
8897                        self.generate_identifier(n)?;
8898                    }
8899
8900                    // Output USING after name but before columns if using_before_columns and there's a name
8901                    if modifiers.using_before_columns && name.is_some() {
8902                        if let Some(ref using) = modifiers.using {
8903                            self.write_space();
8904                            self.write_keyword("USING");
8905                            self.write_space();
8906                            self.write_keyword(using);
8907                        }
8908                    }
8909
8910                    // Output columns
8911                    self.write(" (");
8912                    for (i, col) in columns.iter().enumerate() {
8913                        if i > 0 {
8914                            self.write(", ");
8915                        }
8916                        self.generate_identifier(col)?;
8917                    }
8918                    self.write(")");
8919
8920                    // Output USING after columns if not using_before_columns
8921                    if !modifiers.using_before_columns {
8922                        if let Some(ref using) = modifiers.using {
8923                            self.write_space();
8924                            self.write_keyword("USING");
8925                            self.write_space();
8926                            self.write_keyword(using);
8927                        }
8928                    }
8929
8930                    // Output other constraint modifiers (but skip USING since we already handled it)
8931                    self.generate_constraint_modifiers_without_using(modifiers);
8932                }
8933            }
8934            TableConstraint::Projection { name, expression } => {
8935                // ClickHouse: PROJECTION name (SELECT ...)
8936                self.write_keyword("PROJECTION");
8937                self.write_space();
8938                self.generate_identifier(name)?;
8939                self.write(" (");
8940                self.generate_expression(expression)?;
8941                self.write(")");
8942            }
8943            TableConstraint::Like { source, options } => {
8944                self.write_keyword("LIKE");
8945                self.write_space();
8946                self.generate_table(source)?;
8947                for (action, prop) in options {
8948                    self.write_space();
8949                    match action {
8950                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
8951                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
8952                    }
8953                    self.write_space();
8954                    self.write_keyword(prop);
8955                }
8956            }
8957            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
8958                self.write_keyword("PERIOD FOR SYSTEM_TIME");
8959                self.write(" (");
8960                self.generate_identifier(start_col)?;
8961                self.write(", ");
8962                self.generate_identifier(end_col)?;
8963                self.write(")");
8964            }
8965            TableConstraint::Exclude {
8966                name,
8967                using,
8968                elements,
8969                include_columns,
8970                where_clause,
8971                with_params,
8972                using_index_tablespace,
8973                modifiers: _,
8974            } => {
8975                if let Some(ref n) = name {
8976                    self.write_keyword("CONSTRAINT");
8977                    self.write_space();
8978                    self.generate_identifier(n)?;
8979                    self.write_space();
8980                }
8981                self.write_keyword("EXCLUDE");
8982                if let Some(ref method) = using {
8983                    self.write_space();
8984                    self.write_keyword("USING");
8985                    self.write_space();
8986                    self.write(method);
8987                    self.write("(");
8988                } else {
8989                    self.write(" (");
8990                }
8991                for (i, elem) in elements.iter().enumerate() {
8992                    if i > 0 {
8993                        self.write(", ");
8994                    }
8995                    self.write(&elem.expression);
8996                    self.write_space();
8997                    self.write_keyword("WITH");
8998                    self.write_space();
8999                    self.write(&elem.operator);
9000                }
9001                self.write(")");
9002                if !include_columns.is_empty() {
9003                    self.write_space();
9004                    self.write_keyword("INCLUDE");
9005                    self.write(" (");
9006                    for (i, col) in include_columns.iter().enumerate() {
9007                        if i > 0 {
9008                            self.write(", ");
9009                        }
9010                        self.generate_identifier(col)?;
9011                    }
9012                    self.write(")");
9013                }
9014                if !with_params.is_empty() {
9015                    self.write_space();
9016                    self.write_keyword("WITH");
9017                    self.write(" (");
9018                    for (i, (key, val)) in with_params.iter().enumerate() {
9019                        if i > 0 {
9020                            self.write(", ");
9021                        }
9022                        self.write(key);
9023                        self.write("=");
9024                        self.write(val);
9025                    }
9026                    self.write(")");
9027                }
9028                if let Some(ref tablespace) = using_index_tablespace {
9029                    self.write_space();
9030                    self.write_keyword("USING INDEX TABLESPACE");
9031                    self.write_space();
9032                    self.write(tablespace);
9033                }
9034                if let Some(ref where_expr) = where_clause {
9035                    self.write_space();
9036                    self.write_keyword("WHERE");
9037                    self.write(" (");
9038                    self.generate_expression(where_expr)?;
9039                    self.write(")");
9040                }
9041            }
9042            TableConstraint::Tags(tags) => {
9043                self.write_keyword("TAG");
9044                self.write(" (");
9045                for (i, expr) in tags.expressions.iter().enumerate() {
9046                    if i > 0 {
9047                        self.write(", ");
9048                    }
9049                    self.generate_expression(expr)?;
9050                }
9051                self.write(")");
9052            }
9053            TableConstraint::InitiallyDeferred { deferred } => {
9054                self.write_keyword("INITIALLY");
9055                self.write_space();
9056                if *deferred {
9057                    self.write_keyword("DEFERRED");
9058                } else {
9059                    self.write_keyword("IMMEDIATE");
9060                }
9061            }
9062        }
9063        Ok(())
9064    }
9065
9066    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9067        // Output USING BTREE/HASH (MySQL) - comes first
9068        if let Some(using) = &modifiers.using {
9069            self.write_space();
9070            self.write_keyword("USING");
9071            self.write_space();
9072            self.write_keyword(using);
9073        }
9074        // Output ENFORCED/NOT ENFORCED
9075        if let Some(enforced) = modifiers.enforced {
9076            self.write_space();
9077            if enforced {
9078                self.write_keyword("ENFORCED");
9079            } else {
9080                self.write_keyword("NOT ENFORCED");
9081            }
9082        }
9083        // Output DEFERRABLE/NOT DEFERRABLE
9084        if let Some(deferrable) = modifiers.deferrable {
9085            self.write_space();
9086            if deferrable {
9087                self.write_keyword("DEFERRABLE");
9088            } else {
9089                self.write_keyword("NOT DEFERRABLE");
9090            }
9091        }
9092        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9093        if let Some(initially_deferred) = modifiers.initially_deferred {
9094            self.write_space();
9095            if initially_deferred {
9096                self.write_keyword("INITIALLY DEFERRED");
9097            } else {
9098                self.write_keyword("INITIALLY IMMEDIATE");
9099            }
9100        }
9101        // Output NORELY
9102        if modifiers.norely {
9103            self.write_space();
9104            self.write_keyword("NORELY");
9105        }
9106        // Output RELY
9107        if modifiers.rely {
9108            self.write_space();
9109            self.write_keyword("RELY");
9110        }
9111        // Output NOT VALID (PostgreSQL)
9112        if modifiers.not_valid {
9113            self.write_space();
9114            self.write_keyword("NOT VALID");
9115        }
9116        // Output ON CONFLICT (SQLite)
9117        if let Some(on_conflict) = &modifiers.on_conflict {
9118            self.write_space();
9119            self.write_keyword("ON CONFLICT");
9120            self.write_space();
9121            self.write_keyword(on_conflict);
9122        }
9123        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
9124        if !modifiers.with_options.is_empty() {
9125            self.write_space();
9126            self.write_keyword("WITH");
9127            self.write(" (");
9128            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
9129                if i > 0 {
9130                    self.write(", ");
9131                }
9132                self.write(key);
9133                self.write("=");
9134                self.write(value);
9135            }
9136            self.write(")");
9137        }
9138        // Output TSQL ON filegroup
9139        if let Some(ref fg) = modifiers.on_filegroup {
9140            self.write_space();
9141            self.write_keyword("ON");
9142            self.write_space();
9143            let _ = self.generate_identifier(fg);
9144        }
9145    }
9146
9147    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
9148    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
9149        // Output ENFORCED/NOT ENFORCED
9150        if let Some(enforced) = modifiers.enforced {
9151            self.write_space();
9152            if enforced {
9153                self.write_keyword("ENFORCED");
9154            } else {
9155                self.write_keyword("NOT ENFORCED");
9156            }
9157        }
9158        // Output DEFERRABLE/NOT DEFERRABLE
9159        if let Some(deferrable) = modifiers.deferrable {
9160            self.write_space();
9161            if deferrable {
9162                self.write_keyword("DEFERRABLE");
9163            } else {
9164                self.write_keyword("NOT DEFERRABLE");
9165            }
9166        }
9167        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9168        if let Some(initially_deferred) = modifiers.initially_deferred {
9169            self.write_space();
9170            if initially_deferred {
9171                self.write_keyword("INITIALLY DEFERRED");
9172            } else {
9173                self.write_keyword("INITIALLY IMMEDIATE");
9174            }
9175        }
9176        // Output NORELY
9177        if modifiers.norely {
9178            self.write_space();
9179            self.write_keyword("NORELY");
9180        }
9181        // Output RELY
9182        if modifiers.rely {
9183            self.write_space();
9184            self.write_keyword("RELY");
9185        }
9186        // Output NOT VALID (PostgreSQL)
9187        if modifiers.not_valid {
9188            self.write_space();
9189            self.write_keyword("NOT VALID");
9190        }
9191        // Output ON CONFLICT (SQLite)
9192        if let Some(on_conflict) = &modifiers.on_conflict {
9193            self.write_space();
9194            self.write_keyword("ON CONFLICT");
9195            self.write_space();
9196            self.write_keyword(on_conflict);
9197        }
9198        // Output MySQL index-specific modifiers
9199        self.generate_index_specific_modifiers(modifiers);
9200    }
9201
9202    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
9203    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9204        if let Some(ref comment) = modifiers.comment {
9205            self.write_space();
9206            self.write_keyword("COMMENT");
9207            self.write(" '");
9208            self.write(comment);
9209            self.write("'");
9210        }
9211        if let Some(visible) = modifiers.visible {
9212            self.write_space();
9213            if visible {
9214                self.write_keyword("VISIBLE");
9215            } else {
9216                self.write_keyword("INVISIBLE");
9217            }
9218        }
9219        if let Some(ref attr) = modifiers.engine_attribute {
9220            self.write_space();
9221            self.write_keyword("ENGINE_ATTRIBUTE");
9222            self.write(" = '");
9223            self.write(attr);
9224            self.write("'");
9225        }
9226        if let Some(ref parser) = modifiers.with_parser {
9227            self.write_space();
9228            self.write_keyword("WITH PARSER");
9229            self.write_space();
9230            self.write(parser);
9231        }
9232    }
9233
9234    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
9235        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
9236        if !fk_ref.match_after_actions {
9237            if let Some(ref match_type) = fk_ref.match_type {
9238                self.write_space();
9239                self.write_keyword("MATCH");
9240                self.write_space();
9241                match match_type {
9242                    MatchType::Full => self.write_keyword("FULL"),
9243                    MatchType::Partial => self.write_keyword("PARTIAL"),
9244                    MatchType::Simple => self.write_keyword("SIMPLE"),
9245                }
9246            }
9247        }
9248
9249        // Output ON UPDATE and ON DELETE in the original order
9250        if fk_ref.on_update_first {
9251            if let Some(ref action) = fk_ref.on_update {
9252                self.write_space();
9253                self.write_keyword("ON UPDATE");
9254                self.write_space();
9255                self.generate_referential_action(action);
9256            }
9257            if let Some(ref action) = fk_ref.on_delete {
9258                self.write_space();
9259                self.write_keyword("ON DELETE");
9260                self.write_space();
9261                self.generate_referential_action(action);
9262            }
9263        } else {
9264            if let Some(ref action) = fk_ref.on_delete {
9265                self.write_space();
9266                self.write_keyword("ON DELETE");
9267                self.write_space();
9268                self.generate_referential_action(action);
9269            }
9270            if let Some(ref action) = fk_ref.on_update {
9271                self.write_space();
9272                self.write_keyword("ON UPDATE");
9273                self.write_space();
9274                self.generate_referential_action(action);
9275            }
9276        }
9277
9278        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
9279        if fk_ref.match_after_actions {
9280            if let Some(ref match_type) = fk_ref.match_type {
9281                self.write_space();
9282                self.write_keyword("MATCH");
9283                self.write_space();
9284                match match_type {
9285                    MatchType::Full => self.write_keyword("FULL"),
9286                    MatchType::Partial => self.write_keyword("PARTIAL"),
9287                    MatchType::Simple => self.write_keyword("SIMPLE"),
9288                }
9289            }
9290        }
9291
9292        // DEFERRABLE / NOT DEFERRABLE
9293        if let Some(deferrable) = fk_ref.deferrable {
9294            self.write_space();
9295            if deferrable {
9296                self.write_keyword("DEFERRABLE");
9297            } else {
9298                self.write_keyword("NOT DEFERRABLE");
9299            }
9300        }
9301
9302        Ok(())
9303    }
9304
9305    fn generate_referential_action(&mut self, action: &ReferentialAction) {
9306        match action {
9307            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
9308            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
9309            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
9310            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
9311            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
9312        }
9313    }
9314
9315    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
9316        // Athena: DROP TABLE uses Hive engine (backticks)
9317        let saved_athena_hive_context = self.athena_hive_context;
9318        if matches!(
9319            self.config.dialect,
9320            Some(crate::dialects::DialectType::Athena)
9321        ) {
9322            self.athena_hive_context = true;
9323        }
9324
9325        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
9326        for comment in &dt.leading_comments {
9327            self.write_formatted_comment(comment);
9328            self.write_space();
9329        }
9330        self.write_keyword("DROP TABLE");
9331
9332        if dt.if_exists {
9333            self.write_space();
9334            self.write_keyword("IF EXISTS");
9335        }
9336
9337        self.write_space();
9338        for (i, table) in dt.names.iter().enumerate() {
9339            if i > 0 {
9340                self.write(", ");
9341            }
9342            self.generate_table(table)?;
9343        }
9344
9345        if dt.cascade_constraints {
9346            self.write_space();
9347            self.write_keyword("CASCADE CONSTRAINTS");
9348        } else if dt.cascade {
9349            self.write_space();
9350            self.write_keyword("CASCADE");
9351        }
9352
9353        if dt.purge {
9354            self.write_space();
9355            self.write_keyword("PURGE");
9356        }
9357
9358        // Restore Athena Hive context
9359        self.athena_hive_context = saved_athena_hive_context;
9360
9361        Ok(())
9362    }
9363
9364    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
9365        // Athena: ALTER TABLE uses Hive engine (backticks)
9366        let saved_athena_hive_context = self.athena_hive_context;
9367        if matches!(
9368            self.config.dialect,
9369            Some(crate::dialects::DialectType::Athena)
9370        ) {
9371            self.athena_hive_context = true;
9372        }
9373
9374        self.write_keyword("ALTER TABLE");
9375        if at.if_exists {
9376            self.write_space();
9377            self.write_keyword("IF EXISTS");
9378        }
9379        self.write_space();
9380        self.generate_table(&at.name)?;
9381
9382        // ClickHouse: ON CLUSTER clause
9383        if let Some(ref on_cluster) = at.on_cluster {
9384            self.write_space();
9385            self.generate_on_cluster(on_cluster)?;
9386        }
9387
9388        // Hive: PARTITION(key=value, ...) clause
9389        if let Some(ref partition) = at.partition {
9390            self.write_space();
9391            self.write_keyword("PARTITION");
9392            self.write("(");
9393            for (i, (key, value)) in partition.iter().enumerate() {
9394                if i > 0 {
9395                    self.write(", ");
9396                }
9397                self.generate_identifier(key)?;
9398                self.write(" = ");
9399                self.generate_expression(value)?;
9400            }
9401            self.write(")");
9402        }
9403
9404        // TSQL: WITH CHECK / WITH NOCHECK modifier
9405        if let Some(ref with_check) = at.with_check {
9406            self.write_space();
9407            self.write_keyword(with_check);
9408        }
9409
9410        if self.config.pretty {
9411            // In pretty mode, format actions with newlines and indentation
9412            self.write_newline();
9413            self.indent_level += 1;
9414            for (i, action) in at.actions.iter().enumerate() {
9415                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
9416                let is_continuation = i > 0
9417                    && matches!(
9418                        (&at.actions[i - 1], action),
9419                        (
9420                            AlterTableAction::AddColumn { .. },
9421                            AlterTableAction::AddColumn { .. }
9422                        ) | (
9423                            AlterTableAction::AddConstraint(_),
9424                            AlterTableAction::AddConstraint(_)
9425                        )
9426                    );
9427                if i > 0 {
9428                    self.write(",");
9429                    self.write_newline();
9430                }
9431                self.write_indent();
9432                self.generate_alter_action_with_continuation(action, is_continuation)?;
9433            }
9434            self.indent_level -= 1;
9435        } else {
9436            for (i, action) in at.actions.iter().enumerate() {
9437                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
9438                let is_continuation = i > 0
9439                    && matches!(
9440                        (&at.actions[i - 1], action),
9441                        (
9442                            AlterTableAction::AddColumn { .. },
9443                            AlterTableAction::AddColumn { .. }
9444                        ) | (
9445                            AlterTableAction::AddConstraint(_),
9446                            AlterTableAction::AddConstraint(_)
9447                        )
9448                    );
9449                if i > 0 {
9450                    self.write(",");
9451                }
9452                self.write_space();
9453                self.generate_alter_action_with_continuation(action, is_continuation)?;
9454            }
9455        }
9456
9457        // MySQL ALTER TABLE trailing options
9458        if let Some(ref algorithm) = at.algorithm {
9459            self.write(", ");
9460            self.write_keyword("ALGORITHM");
9461            self.write("=");
9462            self.write_keyword(algorithm);
9463        }
9464        if let Some(ref lock) = at.lock {
9465            self.write(", ");
9466            self.write_keyword("LOCK");
9467            self.write("=");
9468            self.write_keyword(lock);
9469        }
9470
9471        // Restore Athena Hive context
9472        self.athena_hive_context = saved_athena_hive_context;
9473
9474        Ok(())
9475    }
9476
9477    fn generate_alter_action_with_continuation(
9478        &mut self,
9479        action: &AlterTableAction,
9480        is_continuation: bool,
9481    ) -> Result<()> {
9482        match action {
9483            AlterTableAction::AddColumn {
9484                column,
9485                if_not_exists,
9486                position,
9487            } => {
9488                use crate::dialects::DialectType;
9489                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
9490                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
9491                // For other dialects, repeat ADD COLUMN for each
9492                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
9493                let is_tsql_like = matches!(
9494                    self.config.dialect,
9495                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
9496                );
9497                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
9498                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
9499
9500                if is_continuation && (is_snowflake || is_tsql_like) {
9501                    // Don't write ADD keyword for continuation in Snowflake/TSQL
9502                } else if is_snowflake {
9503                    self.write_keyword("ADD");
9504                    self.write_space();
9505                } else if is_athena {
9506                    // Athena uses ADD COLUMNS (col_def) syntax
9507                    self.write_keyword("ADD COLUMNS");
9508                    self.write(" (");
9509                } else if self.config.alter_table_include_column_keyword {
9510                    self.write_keyword("ADD COLUMN");
9511                    self.write_space();
9512                } else {
9513                    // Dialects like Oracle and TSQL don't use COLUMN keyword
9514                    self.write_keyword("ADD");
9515                    self.write_space();
9516                }
9517
9518                if *if_not_exists {
9519                    self.write_keyword("IF NOT EXISTS");
9520                    self.write_space();
9521                }
9522                self.generate_column_def(column)?;
9523
9524                // Close parenthesis for Athena
9525                if is_athena {
9526                    self.write(")");
9527                }
9528
9529                // Column position (FIRST or AFTER)
9530                if let Some(pos) = position {
9531                    self.write_space();
9532                    match pos {
9533                        ColumnPosition::First => self.write_keyword("FIRST"),
9534                        ColumnPosition::After(col_name) => {
9535                            self.write_keyword("AFTER");
9536                            self.write_space();
9537                            self.generate_identifier(col_name)?;
9538                        }
9539                    }
9540                }
9541            }
9542            AlterTableAction::DropColumn {
9543                name,
9544                if_exists,
9545                cascade,
9546            } => {
9547                self.write_keyword("DROP COLUMN");
9548                if *if_exists {
9549                    self.write_space();
9550                    self.write_keyword("IF EXISTS");
9551                }
9552                self.write_space();
9553                self.generate_identifier(name)?;
9554                if *cascade {
9555                    self.write_space();
9556                    self.write_keyword("CASCADE");
9557                }
9558            }
9559            AlterTableAction::DropColumns { names } => {
9560                self.write_keyword("DROP COLUMNS");
9561                self.write(" (");
9562                for (i, name) in names.iter().enumerate() {
9563                    if i > 0 {
9564                        self.write(", ");
9565                    }
9566                    self.generate_identifier(name)?;
9567                }
9568                self.write(")");
9569            }
9570            AlterTableAction::RenameColumn {
9571                old_name,
9572                new_name,
9573                if_exists,
9574            } => {
9575                self.write_keyword("RENAME COLUMN");
9576                if *if_exists {
9577                    self.write_space();
9578                    self.write_keyword("IF EXISTS");
9579                }
9580                self.write_space();
9581                self.generate_identifier(old_name)?;
9582                self.write_space();
9583                self.write_keyword("TO");
9584                self.write_space();
9585                self.generate_identifier(new_name)?;
9586            }
9587            AlterTableAction::AlterColumn {
9588                name,
9589                action,
9590                use_modify_keyword,
9591            } => {
9592                use crate::dialects::DialectType;
9593                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
9594                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
9595                let use_modify = *use_modify_keyword
9596                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
9597                        && matches!(action, AlterColumnAction::SetDataType { .. }));
9598                if use_modify {
9599                    self.write_keyword("MODIFY COLUMN");
9600                    self.write_space();
9601                    self.generate_identifier(name)?;
9602                    // For MODIFY COLUMN, output the type directly
9603                    if let AlterColumnAction::SetDataType {
9604                        data_type,
9605                        using: _,
9606                        collate,
9607                    } = action
9608                    {
9609                        self.write_space();
9610                        self.generate_data_type(data_type)?;
9611                        // Output COLLATE clause if present
9612                        if let Some(collate_name) = collate {
9613                            self.write_space();
9614                            self.write_keyword("COLLATE");
9615                            self.write_space();
9616                            // Output as single-quoted string
9617                            self.write(&format!("'{}'", collate_name));
9618                        }
9619                    } else {
9620                        self.write_space();
9621                        self.generate_alter_column_action(action)?;
9622                    }
9623                } else if matches!(self.config.dialect, Some(DialectType::Hive))
9624                    && matches!(action, AlterColumnAction::SetDataType { .. })
9625                {
9626                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
9627                    self.write_keyword("CHANGE COLUMN");
9628                    self.write_space();
9629                    self.generate_identifier(name)?;
9630                    self.write_space();
9631                    self.generate_identifier(name)?;
9632                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
9633                        self.write_space();
9634                        self.generate_data_type(data_type)?;
9635                    }
9636                } else {
9637                    self.write_keyword("ALTER COLUMN");
9638                    self.write_space();
9639                    self.generate_identifier(name)?;
9640                    self.write_space();
9641                    self.generate_alter_column_action(action)?;
9642                }
9643            }
9644            AlterTableAction::RenameTable(new_name) => {
9645                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
9646                let mysql_like = matches!(
9647                    self.config.dialect,
9648                    Some(DialectType::MySQL)
9649                        | Some(DialectType::Doris)
9650                        | Some(DialectType::StarRocks)
9651                        | Some(DialectType::SingleStore)
9652                );
9653                if mysql_like {
9654                    self.write_keyword("RENAME");
9655                } else {
9656                    self.write_keyword("RENAME TO");
9657                }
9658                self.write_space();
9659                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
9660                let rename_table_with_db = !matches!(
9661                    self.config.dialect,
9662                    Some(DialectType::Doris)
9663                        | Some(DialectType::DuckDB)
9664                        | Some(DialectType::BigQuery)
9665                        | Some(DialectType::PostgreSQL)
9666                );
9667                if !rename_table_with_db {
9668                    let mut stripped = new_name.clone();
9669                    stripped.schema = None;
9670                    stripped.catalog = None;
9671                    self.generate_table(&stripped)?;
9672                } else {
9673                    self.generate_table(new_name)?;
9674                }
9675            }
9676            AlterTableAction::AddConstraint(constraint) => {
9677                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
9678                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
9679                if !is_continuation {
9680                    self.write_keyword("ADD");
9681                    self.write_space();
9682                }
9683                self.generate_table_constraint(constraint)?;
9684            }
9685            AlterTableAction::DropConstraint { name, if_exists } => {
9686                self.write_keyword("DROP CONSTRAINT");
9687                if *if_exists {
9688                    self.write_space();
9689                    self.write_keyword("IF EXISTS");
9690                }
9691                self.write_space();
9692                self.generate_identifier(name)?;
9693            }
9694            AlterTableAction::DropForeignKey { name } => {
9695                self.write_keyword("DROP FOREIGN KEY");
9696                self.write_space();
9697                self.generate_identifier(name)?;
9698            }
9699            AlterTableAction::DropPartition {
9700                partitions,
9701                if_exists,
9702            } => {
9703                self.write_keyword("DROP");
9704                if *if_exists {
9705                    self.write_space();
9706                    self.write_keyword("IF EXISTS");
9707                }
9708                for (i, partition) in partitions.iter().enumerate() {
9709                    if i > 0 {
9710                        self.write(",");
9711                    }
9712                    self.write_space();
9713                    self.write_keyword("PARTITION");
9714                    // Check for special ClickHouse partition formats
9715                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
9716                        // ClickHouse: PARTITION <expression>
9717                        self.write_space();
9718                        self.generate_expression(&partition[0].1)?;
9719                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
9720                        // ClickHouse: PARTITION ALL
9721                        self.write_space();
9722                        self.write_keyword("ALL");
9723                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
9724                        // ClickHouse: PARTITION ID 'string'
9725                        self.write_space();
9726                        self.write_keyword("ID");
9727                        self.write_space();
9728                        self.generate_expression(&partition[0].1)?;
9729                    } else {
9730                        // Standard SQL: PARTITION(key=value, ...)
9731                        self.write("(");
9732                        for (j, (key, value)) in partition.iter().enumerate() {
9733                            if j > 0 {
9734                                self.write(", ");
9735                            }
9736                            self.generate_identifier(key)?;
9737                            self.write(" = ");
9738                            self.generate_expression(value)?;
9739                        }
9740                        self.write(")");
9741                    }
9742                }
9743            }
9744            AlterTableAction::Delete { where_clause } => {
9745                self.write_keyword("DELETE");
9746                self.write_space();
9747                self.write_keyword("WHERE");
9748                self.write_space();
9749                self.generate_expression(where_clause)?;
9750            }
9751            AlterTableAction::SwapWith(target) => {
9752                self.write_keyword("SWAP WITH");
9753                self.write_space();
9754                self.generate_table(target)?;
9755            }
9756            AlterTableAction::SetProperty { properties } => {
9757                use crate::dialects::DialectType;
9758                self.write_keyword("SET");
9759                // Trino/Presto use SET PROPERTIES syntax with spaces around =
9760                let is_trino_presto = matches!(
9761                    self.config.dialect,
9762                    Some(DialectType::Trino) | Some(DialectType::Presto)
9763                );
9764                if is_trino_presto {
9765                    self.write_space();
9766                    self.write_keyword("PROPERTIES");
9767                }
9768                let eq = if is_trino_presto { " = " } else { "=" };
9769                for (i, (key, value)) in properties.iter().enumerate() {
9770                    if i > 0 {
9771                        self.write(",");
9772                    }
9773                    self.write_space();
9774                    // Handle quoted property names for Trino
9775                    if key.contains(' ') {
9776                        self.generate_string_literal(key)?;
9777                    } else {
9778                        self.write(key);
9779                    }
9780                    self.write(eq);
9781                    self.generate_expression(value)?;
9782                }
9783            }
9784            AlterTableAction::UnsetProperty { properties } => {
9785                self.write_keyword("UNSET");
9786                for (i, name) in properties.iter().enumerate() {
9787                    if i > 0 {
9788                        self.write(",");
9789                    }
9790                    self.write_space();
9791                    self.write(name);
9792                }
9793            }
9794            AlterTableAction::ClusterBy { expressions } => {
9795                self.write_keyword("CLUSTER BY");
9796                self.write(" (");
9797                for (i, expr) in expressions.iter().enumerate() {
9798                    if i > 0 {
9799                        self.write(", ");
9800                    }
9801                    self.generate_expression(expr)?;
9802                }
9803                self.write(")");
9804            }
9805            AlterTableAction::SetTag { expressions } => {
9806                self.write_keyword("SET TAG");
9807                for (i, (key, value)) in expressions.iter().enumerate() {
9808                    if i > 0 {
9809                        self.write(",");
9810                    }
9811                    self.write_space();
9812                    self.write(key);
9813                    self.write(" = ");
9814                    self.generate_expression(value)?;
9815                }
9816            }
9817            AlterTableAction::UnsetTag { names } => {
9818                self.write_keyword("UNSET TAG");
9819                for (i, name) in names.iter().enumerate() {
9820                    if i > 0 {
9821                        self.write(",");
9822                    }
9823                    self.write_space();
9824                    self.write(name);
9825                }
9826            }
9827            AlterTableAction::SetOptions { expressions } => {
9828                self.write_keyword("SET");
9829                self.write(" (");
9830                for (i, expr) in expressions.iter().enumerate() {
9831                    if i > 0 {
9832                        self.write(", ");
9833                    }
9834                    self.generate_expression(expr)?;
9835                }
9836                self.write(")");
9837            }
9838            AlterTableAction::AlterIndex { name, visible } => {
9839                self.write_keyword("ALTER INDEX");
9840                self.write_space();
9841                self.generate_identifier(name)?;
9842                self.write_space();
9843                if *visible {
9844                    self.write_keyword("VISIBLE");
9845                } else {
9846                    self.write_keyword("INVISIBLE");
9847                }
9848            }
9849            AlterTableAction::SetAttribute { attribute } => {
9850                self.write_keyword("SET");
9851                self.write_space();
9852                self.write_keyword(attribute);
9853            }
9854            AlterTableAction::SetStageFileFormat { options } => {
9855                self.write_keyword("SET");
9856                self.write_space();
9857                self.write_keyword("STAGE_FILE_FORMAT");
9858                self.write(" = (");
9859                if let Some(opts) = options {
9860                    self.generate_space_separated_properties(opts)?;
9861                }
9862                self.write(")");
9863            }
9864            AlterTableAction::SetStageCopyOptions { options } => {
9865                self.write_keyword("SET");
9866                self.write_space();
9867                self.write_keyword("STAGE_COPY_OPTIONS");
9868                self.write(" = (");
9869                if let Some(opts) = options {
9870                    self.generate_space_separated_properties(opts)?;
9871                }
9872                self.write(")");
9873            }
9874            AlterTableAction::AddColumns { columns, cascade } => {
9875                // Oracle uses ADD (...) without COLUMNS keyword
9876                // Hive/Spark uses ADD COLUMNS (...)
9877                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
9878                if is_oracle {
9879                    self.write_keyword("ADD");
9880                } else {
9881                    self.write_keyword("ADD COLUMNS");
9882                }
9883                self.write(" (");
9884                for (i, col) in columns.iter().enumerate() {
9885                    if i > 0 {
9886                        self.write(", ");
9887                    }
9888                    self.generate_column_def(col)?;
9889                }
9890                self.write(")");
9891                if *cascade {
9892                    self.write_space();
9893                    self.write_keyword("CASCADE");
9894                }
9895            }
9896            AlterTableAction::ChangeColumn {
9897                old_name,
9898                new_name,
9899                data_type,
9900                comment,
9901                cascade,
9902            } => {
9903                use crate::dialects::DialectType;
9904                let is_spark = matches!(
9905                    self.config.dialect,
9906                    Some(DialectType::Spark) | Some(DialectType::Databricks)
9907                );
9908                let is_rename = old_name.name != new_name.name;
9909
9910                if is_spark {
9911                    if is_rename {
9912                        // Spark: RENAME COLUMN old TO new
9913                        self.write_keyword("RENAME COLUMN");
9914                        self.write_space();
9915                        self.generate_identifier(old_name)?;
9916                        self.write_space();
9917                        self.write_keyword("TO");
9918                        self.write_space();
9919                        self.generate_identifier(new_name)?;
9920                    } else if comment.is_some() {
9921                        // Spark: ALTER COLUMN old COMMENT 'comment'
9922                        self.write_keyword("ALTER COLUMN");
9923                        self.write_space();
9924                        self.generate_identifier(old_name)?;
9925                        self.write_space();
9926                        self.write_keyword("COMMENT");
9927                        self.write_space();
9928                        self.write("'");
9929                        self.write(comment.as_ref().unwrap());
9930                        self.write("'");
9931                    } else if data_type.is_some() {
9932                        // Spark: ALTER COLUMN old TYPE data_type
9933                        self.write_keyword("ALTER COLUMN");
9934                        self.write_space();
9935                        self.generate_identifier(old_name)?;
9936                        self.write_space();
9937                        self.write_keyword("TYPE");
9938                        self.write_space();
9939                        self.generate_data_type(data_type.as_ref().unwrap())?;
9940                    } else {
9941                        // Fallback to CHANGE COLUMN
9942                        self.write_keyword("CHANGE COLUMN");
9943                        self.write_space();
9944                        self.generate_identifier(old_name)?;
9945                        self.write_space();
9946                        self.generate_identifier(new_name)?;
9947                    }
9948                } else {
9949                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
9950                    if data_type.is_some() {
9951                        self.write_keyword("CHANGE COLUMN");
9952                    } else {
9953                        self.write_keyword("CHANGE");
9954                    }
9955                    self.write_space();
9956                    self.generate_identifier(old_name)?;
9957                    self.write_space();
9958                    self.generate_identifier(new_name)?;
9959                    if let Some(ref dt) = data_type {
9960                        self.write_space();
9961                        self.generate_data_type(dt)?;
9962                    }
9963                    if let Some(ref c) = comment {
9964                        self.write_space();
9965                        self.write_keyword("COMMENT");
9966                        self.write_space();
9967                        self.write("'");
9968                        self.write(c);
9969                        self.write("'");
9970                    }
9971                    if *cascade {
9972                        self.write_space();
9973                        self.write_keyword("CASCADE");
9974                    }
9975                }
9976            }
9977            AlterTableAction::AddPartition {
9978                partition,
9979                if_not_exists,
9980                location,
9981            } => {
9982                self.write_keyword("ADD");
9983                self.write_space();
9984                if *if_not_exists {
9985                    self.write_keyword("IF NOT EXISTS");
9986                    self.write_space();
9987                }
9988                self.generate_expression(partition)?;
9989                if let Some(ref loc) = location {
9990                    self.write_space();
9991                    self.write_keyword("LOCATION");
9992                    self.write_space();
9993                    self.generate_expression(loc)?;
9994                }
9995            }
9996            AlterTableAction::AlterSortKey {
9997                this,
9998                expressions,
9999                compound,
10000            } => {
10001                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
10002                self.write_keyword("ALTER");
10003                if *compound {
10004                    self.write_space();
10005                    self.write_keyword("COMPOUND");
10006                }
10007                self.write_space();
10008                self.write_keyword("SORTKEY");
10009                self.write_space();
10010                if let Some(style) = this {
10011                    self.write_keyword(style);
10012                } else if !expressions.is_empty() {
10013                    self.write("(");
10014                    for (i, expr) in expressions.iter().enumerate() {
10015                        if i > 0 {
10016                            self.write(", ");
10017                        }
10018                        self.generate_expression(expr)?;
10019                    }
10020                    self.write(")");
10021                }
10022            }
10023            AlterTableAction::AlterDistStyle { style, distkey } => {
10024                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
10025                self.write_keyword("ALTER");
10026                self.write_space();
10027                self.write_keyword("DISTSTYLE");
10028                self.write_space();
10029                self.write_keyword(style);
10030                if let Some(col) = distkey {
10031                    self.write_space();
10032                    self.write_keyword("DISTKEY");
10033                    self.write_space();
10034                    self.generate_identifier(col)?;
10035                }
10036            }
10037            AlterTableAction::SetTableProperties { properties } => {
10038                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
10039                self.write_keyword("SET TABLE PROPERTIES");
10040                self.write(" (");
10041                for (i, (key, value)) in properties.iter().enumerate() {
10042                    if i > 0 {
10043                        self.write(", ");
10044                    }
10045                    self.generate_expression(key)?;
10046                    self.write(" = ");
10047                    self.generate_expression(value)?;
10048                }
10049                self.write(")");
10050            }
10051            AlterTableAction::SetLocation { location } => {
10052                // Redshift: SET LOCATION 's3://bucket/folder/'
10053                self.write_keyword("SET LOCATION");
10054                self.write_space();
10055                self.write("'");
10056                self.write(location);
10057                self.write("'");
10058            }
10059            AlterTableAction::SetFileFormat { format } => {
10060                // Redshift: SET FILE FORMAT AVRO
10061                self.write_keyword("SET FILE FORMAT");
10062                self.write_space();
10063                self.write_keyword(format);
10064            }
10065            AlterTableAction::ReplacePartition { partition, source } => {
10066                // ClickHouse: REPLACE PARTITION expr FROM source
10067                self.write_keyword("REPLACE PARTITION");
10068                self.write_space();
10069                self.generate_expression(partition)?;
10070                if let Some(src) = source {
10071                    self.write_space();
10072                    self.write_keyword("FROM");
10073                    self.write_space();
10074                    self.generate_expression(src)?;
10075                }
10076            }
10077            AlterTableAction::Raw { sql } => {
10078                self.write(sql);
10079            }
10080        }
10081        Ok(())
10082    }
10083
10084    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
10085        match action {
10086            AlterColumnAction::SetDataType {
10087                data_type,
10088                using,
10089                collate,
10090            } => {
10091                use crate::dialects::DialectType;
10092                // Dialect-specific type change syntax:
10093                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
10094                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
10095                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
10096                let is_no_prefix = matches!(
10097                    self.config.dialect,
10098                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
10099                );
10100                let is_type_only = matches!(
10101                    self.config.dialect,
10102                    Some(DialectType::Redshift)
10103                        | Some(DialectType::Spark)
10104                        | Some(DialectType::Databricks)
10105                );
10106                if is_type_only {
10107                    self.write_keyword("TYPE");
10108                    self.write_space();
10109                } else if !is_no_prefix {
10110                    self.write_keyword("SET DATA TYPE");
10111                    self.write_space();
10112                }
10113                self.generate_data_type(data_type)?;
10114                if let Some(ref collation) = collate {
10115                    self.write_space();
10116                    self.write_keyword("COLLATE");
10117                    self.write_space();
10118                    self.write(collation);
10119                }
10120                if let Some(ref using_expr) = using {
10121                    self.write_space();
10122                    self.write_keyword("USING");
10123                    self.write_space();
10124                    self.generate_expression(using_expr)?;
10125                }
10126            }
10127            AlterColumnAction::SetDefault(expr) => {
10128                self.write_keyword("SET DEFAULT");
10129                self.write_space();
10130                self.generate_expression(expr)?;
10131            }
10132            AlterColumnAction::DropDefault => {
10133                self.write_keyword("DROP DEFAULT");
10134            }
10135            AlterColumnAction::SetNotNull => {
10136                self.write_keyword("SET NOT NULL");
10137            }
10138            AlterColumnAction::DropNotNull => {
10139                self.write_keyword("DROP NOT NULL");
10140            }
10141            AlterColumnAction::Comment(comment) => {
10142                self.write_keyword("COMMENT");
10143                self.write_space();
10144                self.generate_string_literal(comment)?;
10145            }
10146            AlterColumnAction::SetVisible => {
10147                self.write_keyword("SET VISIBLE");
10148            }
10149            AlterColumnAction::SetInvisible => {
10150                self.write_keyword("SET INVISIBLE");
10151            }
10152        }
10153        Ok(())
10154    }
10155
10156    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
10157        self.write_keyword("CREATE");
10158
10159        if ci.unique {
10160            self.write_space();
10161            self.write_keyword("UNIQUE");
10162        }
10163
10164        // TSQL CLUSTERED/NONCLUSTERED modifier
10165        if let Some(ref clustered) = ci.clustered {
10166            self.write_space();
10167            self.write_keyword(clustered);
10168        }
10169
10170        self.write_space();
10171        self.write_keyword("INDEX");
10172
10173        // PostgreSQL CONCURRENTLY modifier
10174        if ci.concurrently {
10175            self.write_space();
10176            self.write_keyword("CONCURRENTLY");
10177        }
10178
10179        if ci.if_not_exists {
10180            self.write_space();
10181            self.write_keyword("IF NOT EXISTS");
10182        }
10183
10184        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
10185        if !ci.name.name.is_empty() {
10186            self.write_space();
10187            self.generate_identifier(&ci.name)?;
10188        }
10189        self.write_space();
10190        self.write_keyword("ON");
10191        // Hive uses ON TABLE
10192        if matches!(self.config.dialect, Some(DialectType::Hive)) {
10193            self.write_space();
10194            self.write_keyword("TABLE");
10195        }
10196        self.write_space();
10197        self.generate_table(&ci.table)?;
10198
10199        // Column list (optional for COLUMNSTORE indexes)
10200        // Standard SQL convention: ON t(a) without space before paren
10201        if !ci.columns.is_empty() || ci.using.is_some() {
10202            let space_before_paren = false;
10203
10204            if let Some(ref using) = ci.using {
10205                self.write_space();
10206                self.write_keyword("USING");
10207                self.write_space();
10208                self.write(using);
10209                if space_before_paren {
10210                    self.write(" (");
10211                } else {
10212                    self.write("(");
10213                }
10214            } else {
10215                if space_before_paren {
10216                    self.write(" (");
10217                } else {
10218                    self.write("(");
10219                }
10220            }
10221            for (i, col) in ci.columns.iter().enumerate() {
10222                if i > 0 {
10223                    self.write(", ");
10224                }
10225                self.generate_identifier(&col.column)?;
10226                if let Some(ref opclass) = col.opclass {
10227                    self.write_space();
10228                    self.write(opclass);
10229                }
10230                if col.desc {
10231                    self.write_space();
10232                    self.write_keyword("DESC");
10233                } else if col.asc {
10234                    self.write_space();
10235                    self.write_keyword("ASC");
10236                }
10237                if let Some(nulls_first) = col.nulls_first {
10238                    self.write_space();
10239                    self.write_keyword("NULLS");
10240                    self.write_space();
10241                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
10242                }
10243            }
10244            self.write(")");
10245        }
10246
10247        // PostgreSQL INCLUDE (col1, col2) clause
10248        if !ci.include_columns.is_empty() {
10249            self.write_space();
10250            self.write_keyword("INCLUDE");
10251            self.write(" (");
10252            for (i, col) in ci.include_columns.iter().enumerate() {
10253                if i > 0 {
10254                    self.write(", ");
10255                }
10256                self.generate_identifier(col)?;
10257            }
10258            self.write(")");
10259        }
10260
10261        // TSQL: WITH (option=value, ...) clause
10262        if !ci.with_options.is_empty() {
10263            self.write_space();
10264            self.write_keyword("WITH");
10265            self.write(" (");
10266            for (i, (key, value)) in ci.with_options.iter().enumerate() {
10267                if i > 0 {
10268                    self.write(", ");
10269                }
10270                self.write(key);
10271                self.write("=");
10272                self.write(value);
10273            }
10274            self.write(")");
10275        }
10276
10277        // PostgreSQL WHERE clause for partial indexes
10278        if let Some(ref where_clause) = ci.where_clause {
10279            self.write_space();
10280            self.write_keyword("WHERE");
10281            self.write_space();
10282            self.generate_expression(where_clause)?;
10283        }
10284
10285        // TSQL: ON filegroup or partition scheme clause
10286        if let Some(ref on_fg) = ci.on_filegroup {
10287            self.write_space();
10288            self.write_keyword("ON");
10289            self.write_space();
10290            self.write(on_fg);
10291        }
10292
10293        Ok(())
10294    }
10295
10296    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
10297        self.write_keyword("DROP INDEX");
10298
10299        if di.concurrently {
10300            self.write_space();
10301            self.write_keyword("CONCURRENTLY");
10302        }
10303
10304        if di.if_exists {
10305            self.write_space();
10306            self.write_keyword("IF EXISTS");
10307        }
10308
10309        self.write_space();
10310        self.generate_identifier(&di.name)?;
10311
10312        if let Some(ref table) = di.table {
10313            self.write_space();
10314            self.write_keyword("ON");
10315            self.write_space();
10316            self.generate_table(table)?;
10317        }
10318
10319        Ok(())
10320    }
10321
10322    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
10323        self.write_keyword("CREATE");
10324
10325        // MySQL: ALGORITHM=...
10326        if let Some(ref algorithm) = cv.algorithm {
10327            self.write_space();
10328            self.write_keyword("ALGORITHM");
10329            self.write("=");
10330            self.write_keyword(algorithm);
10331        }
10332
10333        // MySQL: DEFINER=...
10334        if let Some(ref definer) = cv.definer {
10335            self.write_space();
10336            self.write_keyword("DEFINER");
10337            self.write("=");
10338            self.write(definer);
10339        }
10340
10341        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword)
10342        if cv.security_sql_style {
10343            if let Some(ref security) = cv.security {
10344                self.write_space();
10345                self.write_keyword("SQL SECURITY");
10346                self.write_space();
10347                match security {
10348                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
10349                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
10350                    FunctionSecurity::None => self.write_keyword("NONE"),
10351                }
10352            }
10353        }
10354
10355        if cv.or_replace {
10356            self.write_space();
10357            self.write_keyword("OR REPLACE");
10358        }
10359
10360        if cv.temporary {
10361            self.write_space();
10362            self.write_keyword("TEMPORARY");
10363        }
10364
10365        if cv.materialized {
10366            self.write_space();
10367            self.write_keyword("MATERIALIZED");
10368        }
10369
10370        // Snowflake: SECURE VIEW
10371        if cv.secure {
10372            self.write_space();
10373            self.write_keyword("SECURE");
10374        }
10375
10376        self.write_space();
10377        self.write_keyword("VIEW");
10378
10379        if cv.if_not_exists {
10380            self.write_space();
10381            self.write_keyword("IF NOT EXISTS");
10382        }
10383
10384        self.write_space();
10385        self.generate_table(&cv.name)?;
10386
10387        // ClickHouse: ON CLUSTER clause
10388        if let Some(ref on_cluster) = cv.on_cluster {
10389            self.write_space();
10390            self.generate_on_cluster(on_cluster)?;
10391        }
10392
10393        // ClickHouse: TO destination_table
10394        if let Some(ref to_table) = cv.to_table {
10395            self.write_space();
10396            self.write_keyword("TO");
10397            self.write_space();
10398            self.generate_table(to_table)?;
10399        }
10400
10401        // For regular VIEW: columns come before COPY GRANTS
10402        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
10403        if !cv.materialized {
10404            // Regular VIEW: columns first
10405            if !cv.columns.is_empty() {
10406                self.write(" (");
10407                for (i, col) in cv.columns.iter().enumerate() {
10408                    if i > 0 {
10409                        self.write(", ");
10410                    }
10411                    self.generate_identifier(&col.name)?;
10412                    // BigQuery: OPTIONS (key=value, ...) on view column
10413                    if !col.options.is_empty() {
10414                        self.write_space();
10415                        self.generate_options_clause(&col.options)?;
10416                    }
10417                    if let Some(ref comment) = col.comment {
10418                        self.write_space();
10419                        self.write_keyword("COMMENT");
10420                        self.write_space();
10421                        self.generate_string_literal(comment)?;
10422                    }
10423                }
10424                self.write(")");
10425            }
10426
10427            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
10428            if !cv.security_sql_style {
10429                if let Some(ref security) = cv.security {
10430                    self.write_space();
10431                    self.write_keyword("SECURITY");
10432                    self.write_space();
10433                    match security {
10434                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
10435                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
10436                        FunctionSecurity::None => self.write_keyword("NONE"),
10437                    }
10438                }
10439            }
10440
10441            // Snowflake: COPY GRANTS
10442            if cv.copy_grants {
10443                self.write_space();
10444                self.write_keyword("COPY GRANTS");
10445            }
10446        } else {
10447            // MATERIALIZED VIEW: COPY GRANTS first
10448            if cv.copy_grants {
10449                self.write_space();
10450                self.write_keyword("COPY GRANTS");
10451            }
10452
10453            // Doris: If we have a schema (typed columns), generate that instead
10454            if let Some(ref schema) = cv.schema {
10455                self.write(" (");
10456                for (i, expr) in schema.expressions.iter().enumerate() {
10457                    if i > 0 {
10458                        self.write(", ");
10459                    }
10460                    self.generate_expression(expr)?;
10461                }
10462                self.write(")");
10463            } else if !cv.columns.is_empty() {
10464                // Then columns (simple column names without types)
10465                self.write(" (");
10466                for (i, col) in cv.columns.iter().enumerate() {
10467                    if i > 0 {
10468                        self.write(", ");
10469                    }
10470                    self.generate_identifier(&col.name)?;
10471                    // BigQuery: OPTIONS (key=value, ...) on view column
10472                    if !col.options.is_empty() {
10473                        self.write_space();
10474                        self.generate_options_clause(&col.options)?;
10475                    }
10476                    if let Some(ref comment) = col.comment {
10477                        self.write_space();
10478                        self.write_keyword("COMMENT");
10479                        self.write_space();
10480                        self.generate_string_literal(comment)?;
10481                    }
10482                }
10483                self.write(")");
10484            }
10485
10486            // Doris: KEY (columns) for materialized views
10487            if let Some(ref unique_key) = cv.unique_key {
10488                self.write_space();
10489                self.write_keyword("KEY");
10490                self.write(" (");
10491                for (i, expr) in unique_key.expressions.iter().enumerate() {
10492                    if i > 0 {
10493                        self.write(", ");
10494                    }
10495                    self.generate_expression(expr)?;
10496                }
10497                self.write(")");
10498            }
10499        }
10500
10501        // Snowflake: COMMENT = 'text'
10502        if let Some(ref comment) = cv.comment {
10503            self.write_space();
10504            self.write_keyword("COMMENT");
10505            self.write("=");
10506            self.generate_string_literal(comment)?;
10507        }
10508
10509        // Snowflake: TAG (name='value', ...)
10510        if !cv.tags.is_empty() {
10511            self.write_space();
10512            self.write_keyword("TAG");
10513            self.write(" (");
10514            for (i, (name, value)) in cv.tags.iter().enumerate() {
10515                if i > 0 {
10516                    self.write(", ");
10517                }
10518                self.write(name);
10519                self.write("='");
10520                self.write(value);
10521                self.write("'");
10522            }
10523            self.write(")");
10524        }
10525
10526        // BigQuery: OPTIONS (key=value, ...)
10527        if !cv.options.is_empty() {
10528            self.write_space();
10529            self.generate_options_clause(&cv.options)?;
10530        }
10531
10532        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
10533        if let Some(ref build) = cv.build {
10534            self.write_space();
10535            self.write_keyword("BUILD");
10536            self.write_space();
10537            self.write_keyword(build);
10538        }
10539
10540        // Doris: REFRESH clause for materialized views
10541        if let Some(ref refresh) = cv.refresh {
10542            self.write_space();
10543            self.generate_refresh_trigger_property(refresh)?;
10544        }
10545
10546        // Redshift: AUTO REFRESH YES|NO for materialized views
10547        if let Some(auto_refresh) = cv.auto_refresh {
10548            self.write_space();
10549            self.write_keyword("AUTO REFRESH");
10550            self.write_space();
10551            if auto_refresh {
10552                self.write_keyword("YES");
10553            } else {
10554                self.write_keyword("NO");
10555            }
10556        }
10557
10558        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
10559        for prop in &cv.table_properties {
10560            self.write_space();
10561            self.generate_expression(prop)?;
10562        }
10563
10564        // Only output AS clause if there's a real query (not just NULL placeholder)
10565        if !matches!(&cv.query, Expression::Null(_)) {
10566            self.write_space();
10567            self.write_keyword("AS");
10568            self.write_space();
10569
10570            // Teradata: LOCKING clause (between AS and query)
10571            if let Some(ref mode) = cv.locking_mode {
10572                self.write_keyword("LOCKING");
10573                self.write_space();
10574                self.write_keyword(mode);
10575                if let Some(ref access) = cv.locking_access {
10576                    self.write_space();
10577                    self.write_keyword("FOR");
10578                    self.write_space();
10579                    self.write_keyword(access);
10580                }
10581                self.write_space();
10582            }
10583
10584            if cv.query_parenthesized {
10585                self.write("(");
10586            }
10587            self.generate_expression(&cv.query)?;
10588            if cv.query_parenthesized {
10589                self.write(")");
10590            }
10591        }
10592
10593        // Redshift: WITH NO SCHEMA BINDING (after query)
10594        if cv.no_schema_binding {
10595            self.write_space();
10596            self.write_keyword("WITH NO SCHEMA BINDING");
10597        }
10598
10599        Ok(())
10600    }
10601
10602    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
10603        self.write_keyword("DROP");
10604
10605        if dv.materialized {
10606            self.write_space();
10607            self.write_keyword("MATERIALIZED");
10608        }
10609
10610        self.write_space();
10611        self.write_keyword("VIEW");
10612
10613        if dv.if_exists {
10614            self.write_space();
10615            self.write_keyword("IF EXISTS");
10616        }
10617
10618        self.write_space();
10619        self.generate_table(&dv.name)?;
10620
10621        Ok(())
10622    }
10623
10624    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
10625        match tr.target {
10626            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
10627            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
10628        }
10629        if tr.if_exists {
10630            self.write_space();
10631            self.write_keyword("IF EXISTS");
10632        }
10633        self.write_space();
10634        self.generate_table(&tr.table)?;
10635
10636        // ClickHouse: ON CLUSTER clause
10637        if let Some(ref on_cluster) = tr.on_cluster {
10638            self.write_space();
10639            self.generate_on_cluster(on_cluster)?;
10640        }
10641
10642        // Check if first table has a * (multi-table with star)
10643        if !tr.extra_tables.is_empty() {
10644            // Check if the first entry matches the main table (star case)
10645            let skip_first = if let Some(first) = tr.extra_tables.first() {
10646                first.table.name == tr.table.name && first.star
10647            } else {
10648                false
10649            };
10650
10651            // PostgreSQL normalizes away the * suffix (it's the default behavior)
10652            let strip_star = matches!(
10653                self.config.dialect,
10654                Some(crate::dialects::DialectType::PostgreSQL)
10655                    | Some(crate::dialects::DialectType::Redshift)
10656            );
10657            if skip_first && !strip_star {
10658                self.write("*");
10659            }
10660
10661            // Generate additional tables
10662            for (i, entry) in tr.extra_tables.iter().enumerate() {
10663                if i == 0 && skip_first {
10664                    continue; // Already handled the star for first table
10665                }
10666                self.write(", ");
10667                self.generate_table(&entry.table)?;
10668                if entry.star && !strip_star {
10669                    self.write("*");
10670                }
10671            }
10672        }
10673
10674        // RESTART/CONTINUE IDENTITY
10675        if let Some(identity) = &tr.identity {
10676            self.write_space();
10677            match identity {
10678                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
10679                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
10680            }
10681        }
10682
10683        if tr.cascade {
10684            self.write_space();
10685            self.write_keyword("CASCADE");
10686        }
10687
10688        if tr.restrict {
10689            self.write_space();
10690            self.write_keyword("RESTRICT");
10691        }
10692
10693        // Output Hive PARTITION clause
10694        if let Some(ref partition) = tr.partition {
10695            self.write_space();
10696            self.generate_expression(partition)?;
10697        }
10698
10699        Ok(())
10700    }
10701
10702    fn generate_use(&mut self, u: &Use) -> Result<()> {
10703        // Teradata uses "DATABASE <name>" instead of "USE <name>"
10704        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
10705            self.write_keyword("DATABASE");
10706            self.write_space();
10707            self.generate_identifier(&u.this)?;
10708            return Ok(());
10709        }
10710
10711        self.write_keyword("USE");
10712
10713        if let Some(kind) = &u.kind {
10714            self.write_space();
10715            match kind {
10716                UseKind::Database => self.write_keyword("DATABASE"),
10717                UseKind::Schema => self.write_keyword("SCHEMA"),
10718                UseKind::Role => self.write_keyword("ROLE"),
10719                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
10720                UseKind::Catalog => self.write_keyword("CATALOG"),
10721                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
10722            }
10723        }
10724
10725        self.write_space();
10726        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
10727        // without quoting, since these are keywords not identifiers
10728        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
10729            self.write(&u.this.name);
10730        } else {
10731            self.generate_identifier(&u.this)?;
10732        }
10733        Ok(())
10734    }
10735
10736    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
10737        self.write_keyword("CACHE");
10738        if c.lazy {
10739            self.write_space();
10740            self.write_keyword("LAZY");
10741        }
10742        self.write_space();
10743        self.write_keyword("TABLE");
10744        self.write_space();
10745        self.generate_identifier(&c.table)?;
10746
10747        // OPTIONS clause
10748        if !c.options.is_empty() {
10749            self.write_space();
10750            self.write_keyword("OPTIONS");
10751            self.write("(");
10752            for (i, (key, value)) in c.options.iter().enumerate() {
10753                if i > 0 {
10754                    self.write(", ");
10755                }
10756                self.generate_expression(key)?;
10757                self.write(" = ");
10758                self.generate_expression(value)?;
10759            }
10760            self.write(")");
10761        }
10762
10763        // AS query
10764        if let Some(query) = &c.query {
10765            self.write_space();
10766            self.write_keyword("AS");
10767            self.write_space();
10768            self.generate_expression(query)?;
10769        }
10770
10771        Ok(())
10772    }
10773
10774    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
10775        self.write_keyword("UNCACHE TABLE");
10776        if u.if_exists {
10777            self.write_space();
10778            self.write_keyword("IF EXISTS");
10779        }
10780        self.write_space();
10781        self.generate_identifier(&u.table)?;
10782        Ok(())
10783    }
10784
10785    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
10786        self.write_keyword("LOAD DATA");
10787        if l.local {
10788            self.write_space();
10789            self.write_keyword("LOCAL");
10790        }
10791        self.write_space();
10792        self.write_keyword("INPATH");
10793        self.write_space();
10794        self.write("'");
10795        self.write(&l.inpath);
10796        self.write("'");
10797
10798        if l.overwrite {
10799            self.write_space();
10800            self.write_keyword("OVERWRITE");
10801        }
10802
10803        self.write_space();
10804        self.write_keyword("INTO TABLE");
10805        self.write_space();
10806        self.generate_expression(&l.table)?;
10807
10808        // PARTITION clause
10809        if !l.partition.is_empty() {
10810            self.write_space();
10811            self.write_keyword("PARTITION");
10812            self.write("(");
10813            for (i, (col, val)) in l.partition.iter().enumerate() {
10814                if i > 0 {
10815                    self.write(", ");
10816                }
10817                self.generate_identifier(col)?;
10818                self.write(" = ");
10819                self.generate_expression(val)?;
10820            }
10821            self.write(")");
10822        }
10823
10824        // INPUTFORMAT clause
10825        if let Some(fmt) = &l.input_format {
10826            self.write_space();
10827            self.write_keyword("INPUTFORMAT");
10828            self.write_space();
10829            self.write("'");
10830            self.write(fmt);
10831            self.write("'");
10832        }
10833
10834        // SERDE clause
10835        if let Some(serde) = &l.serde {
10836            self.write_space();
10837            self.write_keyword("SERDE");
10838            self.write_space();
10839            self.write("'");
10840            self.write(serde);
10841            self.write("'");
10842        }
10843
10844        Ok(())
10845    }
10846
10847    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
10848        self.write_keyword("PRAGMA");
10849        self.write_space();
10850
10851        // Schema prefix if present
10852        if let Some(schema) = &p.schema {
10853            self.generate_identifier(schema)?;
10854            self.write(".");
10855        }
10856
10857        // Pragma name
10858        self.generate_identifier(&p.name)?;
10859
10860        // Value assignment or function call
10861        if let Some(value) = &p.value {
10862            self.write(" = ");
10863            self.generate_expression(value)?;
10864        } else if !p.args.is_empty() {
10865            self.write("(");
10866            for (i, arg) in p.args.iter().enumerate() {
10867                if i > 0 {
10868                    self.write(", ");
10869                }
10870                self.generate_expression(arg)?;
10871            }
10872            self.write(")");
10873        }
10874
10875        Ok(())
10876    }
10877
10878    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
10879        self.write_keyword("GRANT");
10880        self.write_space();
10881
10882        // Privileges (with optional column lists)
10883        for (i, privilege) in g.privileges.iter().enumerate() {
10884            if i > 0 {
10885                self.write(", ");
10886            }
10887            self.write_keyword(&privilege.name);
10888            // Output column list if present: SELECT(col1, col2)
10889            if !privilege.columns.is_empty() {
10890                self.write("(");
10891                for (j, col) in privilege.columns.iter().enumerate() {
10892                    if j > 0 {
10893                        self.write(", ");
10894                    }
10895                    self.write(col);
10896                }
10897                self.write(")");
10898            }
10899        }
10900
10901        self.write_space();
10902        self.write_keyword("ON");
10903        self.write_space();
10904
10905        // Object kind (TABLE, SCHEMA, etc.)
10906        if let Some(kind) = &g.kind {
10907            self.write_keyword(kind);
10908            self.write_space();
10909        }
10910
10911        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
10912        {
10913            use crate::dialects::DialectType;
10914            let should_upper = matches!(
10915                self.config.dialect,
10916                Some(DialectType::PostgreSQL)
10917                    | Some(DialectType::CockroachDB)
10918                    | Some(DialectType::Materialize)
10919                    | Some(DialectType::RisingWave)
10920            ) && (g.kind.as_deref() == Some("FUNCTION")
10921                || g.kind.as_deref() == Some("PROCEDURE"));
10922            if should_upper {
10923                use crate::expressions::Identifier;
10924                let upper_id = Identifier {
10925                    name: g.securable.name.to_uppercase(),
10926                    quoted: g.securable.quoted,
10927                    ..g.securable.clone()
10928                };
10929                self.generate_identifier(&upper_id)?;
10930            } else {
10931                self.generate_identifier(&g.securable)?;
10932            }
10933        }
10934
10935        // Function parameter types (if present)
10936        if !g.function_params.is_empty() {
10937            self.write("(");
10938            for (i, param) in g.function_params.iter().enumerate() {
10939                if i > 0 {
10940                    self.write(", ");
10941                }
10942                self.write(param);
10943            }
10944            self.write(")");
10945        }
10946
10947        self.write_space();
10948        self.write_keyword("TO");
10949        self.write_space();
10950
10951        // Principals
10952        for (i, principal) in g.principals.iter().enumerate() {
10953            if i > 0 {
10954                self.write(", ");
10955            }
10956            if principal.is_role {
10957                self.write_keyword("ROLE");
10958                self.write_space();
10959            } else if principal.is_group {
10960                self.write_keyword("GROUP");
10961                self.write_space();
10962            }
10963            self.generate_identifier(&principal.name)?;
10964        }
10965
10966        // WITH GRANT OPTION
10967        if g.grant_option {
10968            self.write_space();
10969            self.write_keyword("WITH GRANT OPTION");
10970        }
10971
10972        // TSQL: AS principal
10973        if let Some(ref principal) = g.as_principal {
10974            self.write_space();
10975            self.write_keyword("AS");
10976            self.write_space();
10977            self.generate_identifier(principal)?;
10978        }
10979
10980        Ok(())
10981    }
10982
10983    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
10984        self.write_keyword("REVOKE");
10985        self.write_space();
10986
10987        // GRANT OPTION FOR
10988        if r.grant_option {
10989            self.write_keyword("GRANT OPTION FOR");
10990            self.write_space();
10991        }
10992
10993        // Privileges (with optional column lists)
10994        for (i, privilege) in r.privileges.iter().enumerate() {
10995            if i > 0 {
10996                self.write(", ");
10997            }
10998            self.write_keyword(&privilege.name);
10999            // Output column list if present: SELECT(col1, col2)
11000            if !privilege.columns.is_empty() {
11001                self.write("(");
11002                for (j, col) in privilege.columns.iter().enumerate() {
11003                    if j > 0 {
11004                        self.write(", ");
11005                    }
11006                    self.write(col);
11007                }
11008                self.write(")");
11009            }
11010        }
11011
11012        self.write_space();
11013        self.write_keyword("ON");
11014        self.write_space();
11015
11016        // Object kind
11017        if let Some(kind) = &r.kind {
11018            self.write_keyword(kind);
11019            self.write_space();
11020        }
11021
11022        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11023        {
11024            use crate::dialects::DialectType;
11025            let should_upper = matches!(
11026                self.config.dialect,
11027                Some(DialectType::PostgreSQL)
11028                    | Some(DialectType::CockroachDB)
11029                    | Some(DialectType::Materialize)
11030                    | Some(DialectType::RisingWave)
11031            ) && (r.kind.as_deref() == Some("FUNCTION")
11032                || r.kind.as_deref() == Some("PROCEDURE"));
11033            if should_upper {
11034                use crate::expressions::Identifier;
11035                let upper_id = Identifier {
11036                    name: r.securable.name.to_uppercase(),
11037                    quoted: r.securable.quoted,
11038                    ..r.securable.clone()
11039                };
11040                self.generate_identifier(&upper_id)?;
11041            } else {
11042                self.generate_identifier(&r.securable)?;
11043            }
11044        }
11045
11046        // Function parameter types (if present)
11047        if !r.function_params.is_empty() {
11048            self.write("(");
11049            for (i, param) in r.function_params.iter().enumerate() {
11050                if i > 0 {
11051                    self.write(", ");
11052                }
11053                self.write(param);
11054            }
11055            self.write(")");
11056        }
11057
11058        self.write_space();
11059        self.write_keyword("FROM");
11060        self.write_space();
11061
11062        // Principals
11063        for (i, principal) in r.principals.iter().enumerate() {
11064            if i > 0 {
11065                self.write(", ");
11066            }
11067            if principal.is_role {
11068                self.write_keyword("ROLE");
11069                self.write_space();
11070            } else if principal.is_group {
11071                self.write_keyword("GROUP");
11072                self.write_space();
11073            }
11074            self.generate_identifier(&principal.name)?;
11075        }
11076
11077        // CASCADE or RESTRICT
11078        if r.cascade {
11079            self.write_space();
11080            self.write_keyword("CASCADE");
11081        } else if r.restrict {
11082            self.write_space();
11083            self.write_keyword("RESTRICT");
11084        }
11085
11086        Ok(())
11087    }
11088
11089    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
11090        self.write_keyword("COMMENT");
11091
11092        // IF EXISTS
11093        if c.exists {
11094            self.write_space();
11095            self.write_keyword("IF EXISTS");
11096        }
11097
11098        self.write_space();
11099        self.write_keyword("ON");
11100
11101        // MATERIALIZED
11102        if c.materialized {
11103            self.write_space();
11104            self.write_keyword("MATERIALIZED");
11105        }
11106
11107        self.write_space();
11108        self.write_keyword(&c.kind);
11109        self.write_space();
11110
11111        // Object name
11112        self.generate_expression(&c.this)?;
11113
11114        self.write_space();
11115        self.write_keyword("IS");
11116        self.write_space();
11117
11118        // Comment expression
11119        self.generate_expression(&c.expression)?;
11120
11121        Ok(())
11122    }
11123
11124    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
11125        self.write_keyword("SET");
11126
11127        for (i, item) in s.items.iter().enumerate() {
11128            if i > 0 {
11129                self.write(",");
11130            }
11131            self.write_space();
11132
11133            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY)
11134            if let Some(ref kind) = item.kind {
11135                self.write_keyword(kind);
11136                self.write_space();
11137            }
11138
11139            // Check for special SET forms by name
11140            let name_str = match &item.name {
11141                Expression::Identifier(id) => Some(id.name.as_str()),
11142                _ => None,
11143            };
11144
11145            let is_transaction = name_str == Some("TRANSACTION");
11146            let is_character_set = name_str == Some("CHARACTER SET");
11147            let is_names = name_str == Some("NAMES");
11148            let is_collate = name_str == Some("COLLATE");
11149            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
11150            let name_has_variable_prefix = name_str.map_or(false, |n| n.starts_with("VARIABLE "));
11151            let is_variable = has_variable_kind || name_has_variable_prefix;
11152            let is_value_only =
11153                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
11154
11155            if is_transaction {
11156                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
11157                self.write_keyword("TRANSACTION");
11158                if let Expression::Identifier(id) = &item.value {
11159                    if !id.name.is_empty() {
11160                        self.write_space();
11161                        self.write(&id.name);
11162                    }
11163                }
11164            } else if is_character_set {
11165                // Output: SET CHARACTER SET <charset>
11166                self.write_keyword("CHARACTER SET");
11167                self.write_space();
11168                self.generate_set_value(&item.value)?;
11169            } else if is_names {
11170                // Output: SET NAMES <charset>
11171                self.write_keyword("NAMES");
11172                self.write_space();
11173                self.generate_set_value(&item.value)?;
11174            } else if is_collate {
11175                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
11176                self.write_keyword("COLLATE");
11177                self.write_space();
11178                self.generate_set_value(&item.value)?;
11179            } else if is_variable {
11180                // Output: SET [VARIABLE] <name> = <value>
11181                // If kind=VARIABLE, the keyword was already written above.
11182                // If name has VARIABLE prefix, write VARIABLE keyword for DuckDB target only.
11183                if name_has_variable_prefix && !has_variable_kind {
11184                    if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
11185                        self.write_keyword("VARIABLE");
11186                        self.write_space();
11187                    }
11188                }
11189                // Extract actual variable name (strip VARIABLE prefix if present)
11190                if let Some(ns) = name_str {
11191                    let var_name = if name_has_variable_prefix {
11192                        &ns["VARIABLE ".len()..]
11193                    } else {
11194                        ns
11195                    };
11196                    self.write(var_name);
11197                } else {
11198                    self.generate_expression(&item.name)?;
11199                }
11200                self.write(" = ");
11201                self.generate_set_value(&item.value)?;
11202            } else if is_value_only {
11203                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
11204                self.generate_expression(&item.name)?;
11205            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
11206                // SET key value without = (TSQL style)
11207                self.generate_expression(&item.name)?;
11208                self.write_space();
11209                self.generate_set_value(&item.value)?;
11210            } else {
11211                // Standard: variable = value
11212                // SET item names should not be quoted (they are config parameter names, not column refs)
11213                match &item.name {
11214                    Expression::Identifier(id) => {
11215                        self.write(&id.name);
11216                    }
11217                    _ => {
11218                        self.generate_expression(&item.name)?;
11219                    }
11220                }
11221                self.write(" = ");
11222                self.generate_set_value(&item.value)?;
11223            }
11224        }
11225
11226        Ok(())
11227    }
11228
11229    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
11230    /// directly to avoid reserved keyword quoting.
11231    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
11232        if let Expression::Identifier(id) = value {
11233            match id.name.as_str() {
11234                "DEFAULT" | "ON" | "OFF" => {
11235                    self.write_keyword(&id.name);
11236                    return Ok(());
11237                }
11238                _ => {}
11239            }
11240        }
11241        self.generate_expression(value)
11242    }
11243
11244    // ==================== Phase 4: Additional DDL Generation ====================
11245
11246    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
11247        self.write_keyword("ALTER");
11248        // MySQL modifiers before VIEW
11249        if let Some(ref algorithm) = av.algorithm {
11250            self.write_space();
11251            self.write_keyword("ALGORITHM");
11252            self.write(" = ");
11253            self.write_keyword(algorithm);
11254        }
11255        if let Some(ref definer) = av.definer {
11256            self.write_space();
11257            self.write_keyword("DEFINER");
11258            self.write(" = ");
11259            self.write(definer);
11260        }
11261        if let Some(ref sql_security) = av.sql_security {
11262            self.write_space();
11263            self.write_keyword("SQL SECURITY");
11264            self.write(" = ");
11265            self.write_keyword(sql_security);
11266        }
11267        self.write_space();
11268        self.write_keyword("VIEW");
11269        self.write_space();
11270        self.generate_table(&av.name)?;
11271
11272        // Hive: Column aliases with optional COMMENT
11273        if !av.columns.is_empty() {
11274            self.write(" (");
11275            for (i, col) in av.columns.iter().enumerate() {
11276                if i > 0 {
11277                    self.write(", ");
11278                }
11279                self.generate_identifier(&col.name)?;
11280                if let Some(ref comment) = col.comment {
11281                    self.write_space();
11282                    self.write_keyword("COMMENT");
11283                    self.write(" ");
11284                    self.generate_string_literal(comment)?;
11285                }
11286            }
11287            self.write(")");
11288        }
11289
11290        // TSQL: WITH option before actions
11291        if let Some(ref opt) = av.with_option {
11292            self.write_space();
11293            self.write_keyword("WITH");
11294            self.write_space();
11295            self.write_keyword(opt);
11296        }
11297
11298        for action in &av.actions {
11299            self.write_space();
11300            match action {
11301                AlterViewAction::Rename(new_name) => {
11302                    self.write_keyword("RENAME TO");
11303                    self.write_space();
11304                    self.generate_table(new_name)?;
11305                }
11306                AlterViewAction::OwnerTo(owner) => {
11307                    self.write_keyword("OWNER TO");
11308                    self.write_space();
11309                    self.generate_identifier(owner)?;
11310                }
11311                AlterViewAction::SetSchema(schema) => {
11312                    self.write_keyword("SET SCHEMA");
11313                    self.write_space();
11314                    self.generate_identifier(schema)?;
11315                }
11316                AlterViewAction::SetAuthorization(auth) => {
11317                    self.write_keyword("SET AUTHORIZATION");
11318                    self.write_space();
11319                    self.write(auth);
11320                }
11321                AlterViewAction::AlterColumn { name, action } => {
11322                    self.write_keyword("ALTER COLUMN");
11323                    self.write_space();
11324                    self.generate_identifier(name)?;
11325                    self.write_space();
11326                    self.generate_alter_column_action(action)?;
11327                }
11328                AlterViewAction::AsSelect(query) => {
11329                    self.write_keyword("AS");
11330                    self.write_space();
11331                    self.generate_expression(query)?;
11332                }
11333                AlterViewAction::SetTblproperties(props) => {
11334                    self.write_keyword("SET TBLPROPERTIES");
11335                    self.write(" (");
11336                    for (i, (key, value)) in props.iter().enumerate() {
11337                        if i > 0 {
11338                            self.write(", ");
11339                        }
11340                        self.generate_string_literal(key)?;
11341                        self.write("=");
11342                        self.generate_string_literal(value)?;
11343                    }
11344                    self.write(")");
11345                }
11346                AlterViewAction::UnsetTblproperties(keys) => {
11347                    self.write_keyword("UNSET TBLPROPERTIES");
11348                    self.write(" (");
11349                    for (i, key) in keys.iter().enumerate() {
11350                        if i > 0 {
11351                            self.write(", ");
11352                        }
11353                        self.generate_string_literal(key)?;
11354                    }
11355                    self.write(")");
11356                }
11357            }
11358        }
11359
11360        Ok(())
11361    }
11362
11363    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
11364        self.write_keyword("ALTER INDEX");
11365        self.write_space();
11366        self.generate_identifier(&ai.name)?;
11367
11368        if let Some(table) = &ai.table {
11369            self.write_space();
11370            self.write_keyword("ON");
11371            self.write_space();
11372            self.generate_table(table)?;
11373        }
11374
11375        for action in &ai.actions {
11376            self.write_space();
11377            match action {
11378                AlterIndexAction::Rename(new_name) => {
11379                    self.write_keyword("RENAME TO");
11380                    self.write_space();
11381                    self.generate_identifier(new_name)?;
11382                }
11383                AlterIndexAction::SetTablespace(tablespace) => {
11384                    self.write_keyword("SET TABLESPACE");
11385                    self.write_space();
11386                    self.generate_identifier(tablespace)?;
11387                }
11388                AlterIndexAction::Visible(visible) => {
11389                    if *visible {
11390                        self.write_keyword("VISIBLE");
11391                    } else {
11392                        self.write_keyword("INVISIBLE");
11393                    }
11394                }
11395            }
11396        }
11397
11398        Ok(())
11399    }
11400
11401    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
11402        // Output leading comments
11403        for comment in &cs.leading_comments {
11404            self.write_formatted_comment(comment);
11405            self.write_space();
11406        }
11407
11408        // Athena: CREATE SCHEMA uses Hive engine (backticks)
11409        let saved_athena_hive_context = self.athena_hive_context;
11410        if matches!(
11411            self.config.dialect,
11412            Some(crate::dialects::DialectType::Athena)
11413        ) {
11414            self.athena_hive_context = true;
11415        }
11416
11417        self.write_keyword("CREATE SCHEMA");
11418
11419        if cs.if_not_exists {
11420            self.write_space();
11421            self.write_keyword("IF NOT EXISTS");
11422        }
11423
11424        self.write_space();
11425        self.generate_identifier(&cs.name)?;
11426
11427        if let Some(ref clone_src) = cs.clone_from {
11428            self.write_keyword(" CLONE ");
11429            self.generate_identifier(clone_src)?;
11430        }
11431
11432        if let Some(ref at_clause) = cs.at_clause {
11433            self.write_space();
11434            self.generate_expression(at_clause)?;
11435        }
11436
11437        if let Some(auth) = &cs.authorization {
11438            self.write_space();
11439            self.write_keyword("AUTHORIZATION");
11440            self.write_space();
11441            self.generate_identifier(auth)?;
11442        }
11443
11444        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
11445        // Separate WITH properties from other properties
11446        let with_properties: Vec<_> = cs
11447            .properties
11448            .iter()
11449            .filter(|p| matches!(p, Expression::Property(_)))
11450            .collect();
11451        let other_properties: Vec<_> = cs
11452            .properties
11453            .iter()
11454            .filter(|p| !matches!(p, Expression::Property(_)))
11455            .collect();
11456
11457        // Generate WITH (props) if we have Property expressions
11458        if !with_properties.is_empty() {
11459            self.write_space();
11460            self.write_keyword("WITH");
11461            self.write(" (");
11462            for (i, prop) in with_properties.iter().enumerate() {
11463                if i > 0 {
11464                    self.write(", ");
11465                }
11466                self.generate_expression(prop)?;
11467            }
11468            self.write(")");
11469        }
11470
11471        // Generate other properties (like DEFAULT COLLATE)
11472        for prop in other_properties {
11473            self.write_space();
11474            self.generate_expression(prop)?;
11475        }
11476
11477        // Restore Athena Hive context
11478        self.athena_hive_context = saved_athena_hive_context;
11479
11480        Ok(())
11481    }
11482
11483    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
11484        self.write_keyword("DROP SCHEMA");
11485
11486        if ds.if_exists {
11487            self.write_space();
11488            self.write_keyword("IF EXISTS");
11489        }
11490
11491        self.write_space();
11492        self.generate_identifier(&ds.name)?;
11493
11494        if ds.cascade {
11495            self.write_space();
11496            self.write_keyword("CASCADE");
11497        }
11498
11499        Ok(())
11500    }
11501
11502    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
11503        self.write_keyword("DROP NAMESPACE");
11504
11505        if dn.if_exists {
11506            self.write_space();
11507            self.write_keyword("IF EXISTS");
11508        }
11509
11510        self.write_space();
11511        self.generate_identifier(&dn.name)?;
11512
11513        if dn.cascade {
11514            self.write_space();
11515            self.write_keyword("CASCADE");
11516        }
11517
11518        Ok(())
11519    }
11520
11521    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
11522        self.write_keyword("CREATE DATABASE");
11523
11524        if cd.if_not_exists {
11525            self.write_space();
11526            self.write_keyword("IF NOT EXISTS");
11527        }
11528
11529        self.write_space();
11530        self.generate_identifier(&cd.name)?;
11531
11532        if let Some(ref clone_src) = cd.clone_from {
11533            self.write_keyword(" CLONE ");
11534            self.generate_identifier(clone_src)?;
11535        }
11536
11537        // AT/BEFORE clause for time travel (Snowflake)
11538        if let Some(ref at_clause) = cd.at_clause {
11539            self.write_space();
11540            self.generate_expression(at_clause)?;
11541        }
11542
11543        for option in &cd.options {
11544            self.write_space();
11545            match option {
11546                DatabaseOption::CharacterSet(charset) => {
11547                    self.write_keyword("CHARACTER SET");
11548                    self.write(" = ");
11549                    self.write(&format!("'{}'", charset));
11550                }
11551                DatabaseOption::Collate(collate) => {
11552                    self.write_keyword("COLLATE");
11553                    self.write(" = ");
11554                    self.write(&format!("'{}'", collate));
11555                }
11556                DatabaseOption::Owner(owner) => {
11557                    self.write_keyword("OWNER");
11558                    self.write(" = ");
11559                    self.generate_identifier(owner)?;
11560                }
11561                DatabaseOption::Template(template) => {
11562                    self.write_keyword("TEMPLATE");
11563                    self.write(" = ");
11564                    self.generate_identifier(template)?;
11565                }
11566                DatabaseOption::Encoding(encoding) => {
11567                    self.write_keyword("ENCODING");
11568                    self.write(" = ");
11569                    self.write(&format!("'{}'", encoding));
11570                }
11571                DatabaseOption::Location(location) => {
11572                    self.write_keyword("LOCATION");
11573                    self.write(" = ");
11574                    self.write(&format!("'{}'", location));
11575                }
11576            }
11577        }
11578
11579        Ok(())
11580    }
11581
11582    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
11583        self.write_keyword("DROP DATABASE");
11584
11585        if dd.if_exists {
11586            self.write_space();
11587            self.write_keyword("IF EXISTS");
11588        }
11589
11590        self.write_space();
11591        self.generate_identifier(&dd.name)?;
11592
11593        Ok(())
11594    }
11595
11596    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
11597        self.write_keyword("CREATE");
11598
11599        if cf.or_replace {
11600            self.write_space();
11601            self.write_keyword("OR REPLACE");
11602        }
11603
11604        if cf.temporary {
11605            self.write_space();
11606            self.write_keyword("TEMPORARY");
11607        }
11608
11609        self.write_space();
11610        if cf.is_table_function {
11611            self.write_keyword("TABLE FUNCTION");
11612        } else {
11613            self.write_keyword("FUNCTION");
11614        }
11615
11616        if cf.if_not_exists {
11617            self.write_space();
11618            self.write_keyword("IF NOT EXISTS");
11619        }
11620
11621        self.write_space();
11622        self.generate_table(&cf.name)?;
11623        if cf.has_parens {
11624            let func_multiline = self.config.pretty
11625                && matches!(
11626                    self.config.dialect,
11627                    Some(crate::dialects::DialectType::TSQL)
11628                        | Some(crate::dialects::DialectType::Fabric)
11629                )
11630                && !cf.parameters.is_empty();
11631            if func_multiline {
11632                self.write("(\n");
11633                self.indent_level += 2;
11634                self.write_indent();
11635                self.generate_function_parameters(&cf.parameters)?;
11636                self.write("\n");
11637                self.indent_level -= 2;
11638                self.write(")");
11639            } else {
11640                self.write("(");
11641                self.generate_function_parameters(&cf.parameters)?;
11642                self.write(")");
11643            }
11644        }
11645
11646        // Output RETURNS clause (always comes first after parameters)
11647        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
11648        let use_multiline = self.config.pretty
11649            && matches!(
11650                self.config.dialect,
11651                Some(crate::dialects::DialectType::BigQuery)
11652                    | Some(crate::dialects::DialectType::TSQL)
11653                    | Some(crate::dialects::DialectType::Fabric)
11654            );
11655
11656        if cf.language_first {
11657            // LANGUAGE first, then SQL data access, then RETURNS
11658            if let Some(lang) = &cf.language {
11659                if use_multiline {
11660                    self.write_newline();
11661                } else {
11662                    self.write_space();
11663                }
11664                self.write_keyword("LANGUAGE");
11665                self.write_space();
11666                self.write(lang);
11667            }
11668
11669            // SQL data access comes after LANGUAGE in this case
11670            if let Some(sql_data) = &cf.sql_data_access {
11671                self.write_space();
11672                match sql_data {
11673                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
11674                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
11675                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
11676                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
11677                }
11678            }
11679
11680            if let Some(ref rtb) = cf.returns_table_body {
11681                if use_multiline {
11682                    self.write_newline();
11683                } else {
11684                    self.write_space();
11685                }
11686                self.write_keyword("RETURNS");
11687                self.write_space();
11688                self.write(rtb);
11689            } else if let Some(return_type) = &cf.return_type {
11690                if use_multiline {
11691                    self.write_newline();
11692                } else {
11693                    self.write_space();
11694                }
11695                self.write_keyword("RETURNS");
11696                self.write_space();
11697                self.generate_data_type(return_type)?;
11698            }
11699        } else {
11700            // RETURNS first (default)
11701            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
11702            let is_duckdb = matches!(
11703                self.config.dialect,
11704                Some(crate::dialects::DialectType::DuckDB)
11705            );
11706            if let Some(ref rtb) = cf.returns_table_body {
11707                if !(is_duckdb && rtb.is_empty()) {
11708                    if use_multiline {
11709                        self.write_newline();
11710                    } else {
11711                        self.write_space();
11712                    }
11713                    self.write_keyword("RETURNS");
11714                    self.write_space();
11715                    self.write(rtb);
11716                }
11717            } else if let Some(return_type) = &cf.return_type {
11718                if use_multiline {
11719                    self.write_newline();
11720                } else {
11721                    self.write_space();
11722                }
11723                self.write_keyword("RETURNS");
11724                self.write_space();
11725                self.generate_data_type(return_type)?;
11726            }
11727        }
11728
11729        // If we have property_order, use it to output properties in original order
11730        if !cf.property_order.is_empty() {
11731            // For BigQuery, OPTIONS must come before AS - reorder if needed
11732            let is_bigquery = matches!(
11733                self.config.dialect,
11734                Some(crate::dialects::DialectType::BigQuery)
11735            );
11736            let property_order = if is_bigquery {
11737                // Move Options before As if both are present
11738                let mut reordered = Vec::new();
11739                let mut has_as = false;
11740                let mut has_options = false;
11741                for prop in &cf.property_order {
11742                    match prop {
11743                        FunctionPropertyKind::As => has_as = true,
11744                        FunctionPropertyKind::Options => has_options = true,
11745                        _ => {}
11746                    }
11747                }
11748                if has_as && has_options {
11749                    // Output all props except As and Options, then Options, then As
11750                    for prop in &cf.property_order {
11751                        if *prop != FunctionPropertyKind::As
11752                            && *prop != FunctionPropertyKind::Options
11753                        {
11754                            reordered.push(*prop);
11755                        }
11756                    }
11757                    reordered.push(FunctionPropertyKind::Options);
11758                    reordered.push(FunctionPropertyKind::As);
11759                    reordered
11760                } else {
11761                    cf.property_order.clone()
11762                }
11763            } else {
11764                cf.property_order.clone()
11765            };
11766
11767            for prop in &property_order {
11768                match prop {
11769                    FunctionPropertyKind::Set => {
11770                        self.generate_function_set_options(cf)?;
11771                    }
11772                    FunctionPropertyKind::As => {
11773                        self.generate_function_body(cf)?;
11774                    }
11775                    FunctionPropertyKind::Language => {
11776                        if !cf.language_first {
11777                            // Only output here if not already output above
11778                            if let Some(lang) = &cf.language {
11779                                // Only BigQuery uses multiline formatting
11780                                let use_multiline = self.config.pretty
11781                                    && matches!(
11782                                        self.config.dialect,
11783                                        Some(crate::dialects::DialectType::BigQuery)
11784                                    );
11785                                if use_multiline {
11786                                    self.write_newline();
11787                                } else {
11788                                    self.write_space();
11789                                }
11790                                self.write_keyword("LANGUAGE");
11791                                self.write_space();
11792                                self.write(lang);
11793                            }
11794                        }
11795                    }
11796                    FunctionPropertyKind::Determinism => {
11797                        self.generate_function_determinism(cf)?;
11798                    }
11799                    FunctionPropertyKind::NullInput => {
11800                        self.generate_function_null_input(cf)?;
11801                    }
11802                    FunctionPropertyKind::Security => {
11803                        self.generate_function_security(cf)?;
11804                    }
11805                    FunctionPropertyKind::SqlDataAccess => {
11806                        if !cf.language_first {
11807                            // Only output here if not already output above
11808                            self.generate_function_sql_data_access(cf)?;
11809                        }
11810                    }
11811                    FunctionPropertyKind::Options => {
11812                        if !cf.options.is_empty() {
11813                            self.write_space();
11814                            self.generate_options_clause(&cf.options)?;
11815                        }
11816                    }
11817                    FunctionPropertyKind::Environment => {
11818                        if !cf.environment.is_empty() {
11819                            self.write_space();
11820                            self.generate_environment_clause(&cf.environment)?;
11821                        }
11822                    }
11823                }
11824            }
11825
11826            // Output OPTIONS if not tracked in property_order (legacy)
11827            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
11828            {
11829                self.write_space();
11830                self.generate_options_clause(&cf.options)?;
11831            }
11832
11833            // Output ENVIRONMENT if not tracked in property_order (legacy)
11834            if !cf.environment.is_empty()
11835                && !cf
11836                    .property_order
11837                    .contains(&FunctionPropertyKind::Environment)
11838            {
11839                self.write_space();
11840                self.generate_environment_clause(&cf.environment)?;
11841            }
11842        } else {
11843            // Legacy behavior when property_order is empty
11844            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
11845            if matches!(
11846                self.config.dialect,
11847                Some(crate::dialects::DialectType::BigQuery)
11848            ) {
11849                self.generate_function_determinism(cf)?;
11850            }
11851
11852            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
11853            let use_multiline = self.config.pretty
11854                && matches!(
11855                    self.config.dialect,
11856                    Some(crate::dialects::DialectType::BigQuery)
11857                );
11858
11859            if !cf.language_first {
11860                if let Some(lang) = &cf.language {
11861                    if use_multiline {
11862                        self.write_newline();
11863                    } else {
11864                        self.write_space();
11865                    }
11866                    self.write_keyword("LANGUAGE");
11867                    self.write_space();
11868                    self.write(lang);
11869                }
11870
11871                // SQL data access characteristic comes after LANGUAGE
11872                self.generate_function_sql_data_access(cf)?;
11873            }
11874
11875            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
11876            if !matches!(
11877                self.config.dialect,
11878                Some(crate::dialects::DialectType::BigQuery)
11879            ) {
11880                self.generate_function_determinism(cf)?;
11881            }
11882
11883            self.generate_function_null_input(cf)?;
11884            self.generate_function_security(cf)?;
11885            self.generate_function_set_options(cf)?;
11886
11887            // BigQuery: OPTIONS (key=value, ...) - comes before AS
11888            if !cf.options.is_empty() {
11889                self.write_space();
11890                self.generate_options_clause(&cf.options)?;
11891            }
11892
11893            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
11894            if !cf.environment.is_empty() {
11895                self.write_space();
11896                self.generate_environment_clause(&cf.environment)?;
11897            }
11898
11899            self.generate_function_body(cf)?;
11900        }
11901
11902        Ok(())
11903    }
11904
11905    /// Generate SET options for CREATE FUNCTION
11906    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
11907        for opt in &cf.set_options {
11908            self.write_space();
11909            self.write_keyword("SET");
11910            self.write_space();
11911            self.write(&opt.name);
11912            match &opt.value {
11913                FunctionSetValue::Value { value, use_to } => {
11914                    if *use_to {
11915                        self.write(" TO ");
11916                    } else {
11917                        self.write(" = ");
11918                    }
11919                    self.write(value);
11920                }
11921                FunctionSetValue::FromCurrent => {
11922                    self.write_space();
11923                    self.write_keyword("FROM CURRENT");
11924                }
11925            }
11926        }
11927        Ok(())
11928    }
11929
11930    /// Generate function body (AS clause)
11931    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
11932        if let Some(body) = &cf.body {
11933            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
11934            self.write_space();
11935            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
11936            let use_multiline = self.config.pretty
11937                && matches!(
11938                    self.config.dialect,
11939                    Some(crate::dialects::DialectType::BigQuery)
11940                );
11941            match body {
11942                FunctionBody::Block(block) => {
11943                    self.write_keyword("AS");
11944                    if matches!(
11945                        self.config.dialect,
11946                        Some(crate::dialects::DialectType::TSQL)
11947                    ) {
11948                        self.write(" BEGIN ");
11949                        self.write(block);
11950                        self.write(" END");
11951                    } else if matches!(
11952                        self.config.dialect,
11953                        Some(crate::dialects::DialectType::PostgreSQL)
11954                    ) {
11955                        self.write(" $$");
11956                        self.write(block);
11957                        self.write("$$");
11958                    } else {
11959                        // Escape content for single-quoted output
11960                        let escaped = self.escape_block_for_single_quote(block);
11961                        // In BigQuery pretty mode, body content goes on new line
11962                        if use_multiline {
11963                            self.write_newline();
11964                        } else {
11965                            self.write(" ");
11966                        }
11967                        self.write("'");
11968                        self.write(&escaped);
11969                        self.write("'");
11970                    }
11971                }
11972                FunctionBody::StringLiteral(s) => {
11973                    self.write_keyword("AS");
11974                    // In BigQuery pretty mode, body content goes on new line
11975                    if use_multiline {
11976                        self.write_newline();
11977                    } else {
11978                        self.write(" ");
11979                    }
11980                    self.write("'");
11981                    self.write(s);
11982                    self.write("'");
11983                }
11984                FunctionBody::Expression(expr) => {
11985                    self.write_keyword("AS");
11986                    self.write_space();
11987                    self.generate_expression(expr)?;
11988                }
11989                FunctionBody::External(name) => {
11990                    self.write_keyword("EXTERNAL NAME");
11991                    self.write(" '");
11992                    self.write(name);
11993                    self.write("'");
11994                }
11995                FunctionBody::Return(expr) => {
11996                    if matches!(
11997                        self.config.dialect,
11998                        Some(crate::dialects::DialectType::DuckDB)
11999                    ) {
12000                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
12001                        self.write_keyword("AS");
12002                        self.write_space();
12003                        // Empty returns_table_body signals TABLE return
12004                        if cf.returns_table_body.is_some() {
12005                            self.write_keyword("TABLE");
12006                            self.write_space();
12007                        }
12008                        self.generate_expression(expr)?;
12009                    } else {
12010                        if self.config.create_function_return_as {
12011                            self.write_keyword("AS");
12012                            // TSQL pretty: newline between AS and RETURN
12013                            if self.config.pretty
12014                                && matches!(
12015                                    self.config.dialect,
12016                                    Some(crate::dialects::DialectType::TSQL)
12017                                        | Some(crate::dialects::DialectType::Fabric)
12018                                )
12019                            {
12020                                self.write_newline();
12021                            } else {
12022                                self.write_space();
12023                            }
12024                        }
12025                        self.write_keyword("RETURN");
12026                        self.write_space();
12027                        self.generate_expression(expr)?;
12028                    }
12029                }
12030                FunctionBody::Statements(stmts) => {
12031                    self.write_keyword("AS");
12032                    self.write(" BEGIN ");
12033                    for (i, stmt) in stmts.iter().enumerate() {
12034                        if i > 0 {
12035                            self.write(" ");
12036                        }
12037                        self.generate_expression(stmt)?;
12038                    }
12039                    self.write(" END");
12040                }
12041                FunctionBody::DollarQuoted { content, tag } => {
12042                    self.write_keyword("AS");
12043                    self.write(" ");
12044                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
12045                    let supports_dollar_quoting = matches!(
12046                        self.config.dialect,
12047                        Some(crate::dialects::DialectType::PostgreSQL)
12048                            | Some(crate::dialects::DialectType::Databricks)
12049                            | Some(crate::dialects::DialectType::Redshift)
12050                            | Some(crate::dialects::DialectType::DuckDB)
12051                    );
12052                    if supports_dollar_quoting {
12053                        // Output in dollar-quoted format
12054                        self.write("$");
12055                        if let Some(t) = tag {
12056                            self.write(t);
12057                        }
12058                        self.write("$");
12059                        self.write(content);
12060                        self.write("$");
12061                        if let Some(t) = tag {
12062                            self.write(t);
12063                        }
12064                        self.write("$");
12065                    } else {
12066                        // Convert to single-quoted string for other dialects
12067                        let escaped = self.escape_block_for_single_quote(content);
12068                        self.write("'");
12069                        self.write(&escaped);
12070                        self.write("'");
12071                    }
12072                }
12073            }
12074        }
12075        Ok(())
12076    }
12077
12078    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
12079    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
12080        if let Some(det) = cf.deterministic {
12081            self.write_space();
12082            if matches!(
12083                self.config.dialect,
12084                Some(crate::dialects::DialectType::BigQuery)
12085            ) {
12086                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
12087                if det {
12088                    self.write_keyword("DETERMINISTIC");
12089                } else {
12090                    self.write_keyword("NOT DETERMINISTIC");
12091                }
12092            } else {
12093                // PostgreSQL and others use IMMUTABLE/VOLATILE
12094                if det {
12095                    self.write_keyword("IMMUTABLE");
12096                } else {
12097                    self.write_keyword("VOLATILE");
12098                }
12099            }
12100        }
12101        Ok(())
12102    }
12103
12104    /// Generate null input handling clause
12105    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
12106        if let Some(returns_null) = cf.returns_null_on_null_input {
12107            self.write_space();
12108            if returns_null {
12109                if cf.strict {
12110                    self.write_keyword("STRICT");
12111                } else {
12112                    self.write_keyword("RETURNS NULL ON NULL INPUT");
12113                }
12114            } else {
12115                self.write_keyword("CALLED ON NULL INPUT");
12116            }
12117        }
12118        Ok(())
12119    }
12120
12121    /// Generate security clause
12122    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
12123        if let Some(security) = &cf.security {
12124            self.write_space();
12125            self.write_keyword("SECURITY");
12126            self.write_space();
12127            match security {
12128                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12129                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12130                FunctionSecurity::None => self.write_keyword("NONE"),
12131            }
12132        }
12133        Ok(())
12134    }
12135
12136    /// Generate SQL data access clause
12137    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
12138        if let Some(sql_data) = &cf.sql_data_access {
12139            self.write_space();
12140            match sql_data {
12141                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12142                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12143                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12144                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12145            }
12146        }
12147        Ok(())
12148    }
12149
12150    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
12151        for (i, param) in params.iter().enumerate() {
12152            if i > 0 {
12153                self.write(", ");
12154            }
12155
12156            if let Some(mode) = &param.mode {
12157                if let Some(text) = &param.mode_text {
12158                    self.write(text);
12159                } else {
12160                    match mode {
12161                        ParameterMode::In => self.write_keyword("IN"),
12162                        ParameterMode::Out => self.write_keyword("OUT"),
12163                        ParameterMode::InOut => self.write_keyword("INOUT"),
12164                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
12165                    }
12166                }
12167                self.write_space();
12168            }
12169
12170            if let Some(name) = &param.name {
12171                self.generate_identifier(name)?;
12172                // Skip space and type for empty Custom types (e.g., DuckDB macros)
12173                let skip_type =
12174                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
12175                if !skip_type {
12176                    self.write_space();
12177                    self.generate_data_type(&param.data_type)?;
12178                }
12179            } else {
12180                self.generate_data_type(&param.data_type)?;
12181            }
12182
12183            if let Some(default) = &param.default {
12184                if self.config.parameter_default_equals {
12185                    self.write(" = ");
12186                } else {
12187                    self.write(" DEFAULT ");
12188                }
12189                self.generate_expression(default)?;
12190            }
12191        }
12192
12193        Ok(())
12194    }
12195
12196    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
12197        self.write_keyword("DROP FUNCTION");
12198
12199        if df.if_exists {
12200            self.write_space();
12201            self.write_keyword("IF EXISTS");
12202        }
12203
12204        self.write_space();
12205        self.generate_table(&df.name)?;
12206
12207        if let Some(params) = &df.parameters {
12208            self.write(" (");
12209            for (i, dt) in params.iter().enumerate() {
12210                if i > 0 {
12211                    self.write(", ");
12212                }
12213                self.generate_data_type(dt)?;
12214            }
12215            self.write(")");
12216        }
12217
12218        if df.cascade {
12219            self.write_space();
12220            self.write_keyword("CASCADE");
12221        }
12222
12223        Ok(())
12224    }
12225
12226    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
12227        self.write_keyword("CREATE");
12228
12229        if cp.or_replace {
12230            self.write_space();
12231            self.write_keyword("OR REPLACE");
12232        }
12233
12234        self.write_space();
12235        if cp.use_proc_keyword {
12236            self.write_keyword("PROC");
12237        } else {
12238            self.write_keyword("PROCEDURE");
12239        }
12240
12241        if cp.if_not_exists {
12242            self.write_space();
12243            self.write_keyword("IF NOT EXISTS");
12244        }
12245
12246        self.write_space();
12247        self.generate_table(&cp.name)?;
12248        if cp.has_parens {
12249            self.write("(");
12250            self.generate_function_parameters(&cp.parameters)?;
12251            self.write(")");
12252        } else if !cp.parameters.is_empty() {
12253            // TSQL: unparenthesized parameters
12254            self.write_space();
12255            self.generate_function_parameters(&cp.parameters)?;
12256        }
12257
12258        // RETURNS clause (Snowflake)
12259        if let Some(return_type) = &cp.return_type {
12260            self.write_space();
12261            self.write_keyword("RETURNS");
12262            self.write_space();
12263            self.generate_data_type(return_type)?;
12264        }
12265
12266        // EXECUTE AS clause (Snowflake)
12267        if let Some(execute_as) = &cp.execute_as {
12268            self.write_space();
12269            self.write_keyword("EXECUTE AS");
12270            self.write_space();
12271            self.write_keyword(execute_as);
12272        }
12273
12274        if let Some(lang) = &cp.language {
12275            self.write_space();
12276            self.write_keyword("LANGUAGE");
12277            self.write_space();
12278            self.write(lang);
12279        }
12280
12281        if let Some(security) = &cp.security {
12282            self.write_space();
12283            self.write_keyword("SECURITY");
12284            self.write_space();
12285            match security {
12286                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12287                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12288                FunctionSecurity::None => self.write_keyword("NONE"),
12289            }
12290        }
12291
12292        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
12293        if !cp.with_options.is_empty() {
12294            self.write_space();
12295            self.write_keyword("WITH");
12296            self.write_space();
12297            for (i, opt) in cp.with_options.iter().enumerate() {
12298                if i > 0 {
12299                    self.write(", ");
12300                }
12301                self.write(opt);
12302            }
12303        }
12304
12305        if let Some(body) = &cp.body {
12306            self.write_space();
12307            match body {
12308                FunctionBody::Block(block) => {
12309                    self.write_keyword("AS");
12310                    if matches!(
12311                        self.config.dialect,
12312                        Some(crate::dialects::DialectType::TSQL)
12313                    ) {
12314                        self.write(" BEGIN ");
12315                        self.write(block);
12316                        self.write(" END");
12317                    } else if matches!(
12318                        self.config.dialect,
12319                        Some(crate::dialects::DialectType::PostgreSQL)
12320                    ) {
12321                        self.write(" $$");
12322                        self.write(block);
12323                        self.write("$$");
12324                    } else {
12325                        // Escape content for single-quoted output
12326                        let escaped = self.escape_block_for_single_quote(block);
12327                        self.write(" '");
12328                        self.write(&escaped);
12329                        self.write("'");
12330                    }
12331                }
12332                FunctionBody::StringLiteral(s) => {
12333                    self.write_keyword("AS");
12334                    self.write(" '");
12335                    self.write(s);
12336                    self.write("'");
12337                }
12338                FunctionBody::Expression(expr) => {
12339                    self.write_keyword("AS");
12340                    self.write_space();
12341                    self.generate_expression(expr)?;
12342                }
12343                FunctionBody::External(name) => {
12344                    self.write_keyword("EXTERNAL NAME");
12345                    self.write(" '");
12346                    self.write(name);
12347                    self.write("'");
12348                }
12349                FunctionBody::Return(expr) => {
12350                    self.write_keyword("RETURN");
12351                    self.write_space();
12352                    self.generate_expression(expr)?;
12353                }
12354                FunctionBody::Statements(stmts) => {
12355                    self.write_keyword("AS");
12356                    self.write(" BEGIN ");
12357                    for (i, stmt) in stmts.iter().enumerate() {
12358                        if i > 0 {
12359                            self.write(" ");
12360                        }
12361                        self.generate_expression(stmt)?;
12362                    }
12363                    self.write(" END");
12364                }
12365                FunctionBody::DollarQuoted { content, tag } => {
12366                    self.write_keyword("AS");
12367                    self.write(" ");
12368                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
12369                    let supports_dollar_quoting = matches!(
12370                        self.config.dialect,
12371                        Some(crate::dialects::DialectType::PostgreSQL)
12372                            | Some(crate::dialects::DialectType::Databricks)
12373                            | Some(crate::dialects::DialectType::Redshift)
12374                            | Some(crate::dialects::DialectType::DuckDB)
12375                    );
12376                    if supports_dollar_quoting {
12377                        // Output in dollar-quoted format
12378                        self.write("$");
12379                        if let Some(t) = tag {
12380                            self.write(t);
12381                        }
12382                        self.write("$");
12383                        self.write(content);
12384                        self.write("$");
12385                        if let Some(t) = tag {
12386                            self.write(t);
12387                        }
12388                        self.write("$");
12389                    } else {
12390                        // Convert to single-quoted string for other dialects
12391                        let escaped = self.escape_block_for_single_quote(content);
12392                        self.write("'");
12393                        self.write(&escaped);
12394                        self.write("'");
12395                    }
12396                }
12397            }
12398        }
12399
12400        Ok(())
12401    }
12402
12403    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
12404        self.write_keyword("DROP PROCEDURE");
12405
12406        if dp.if_exists {
12407            self.write_space();
12408            self.write_keyword("IF EXISTS");
12409        }
12410
12411        self.write_space();
12412        self.generate_table(&dp.name)?;
12413
12414        if let Some(params) = &dp.parameters {
12415            self.write(" (");
12416            for (i, dt) in params.iter().enumerate() {
12417                if i > 0 {
12418                    self.write(", ");
12419                }
12420                self.generate_data_type(dt)?;
12421            }
12422            self.write(")");
12423        }
12424
12425        if dp.cascade {
12426            self.write_space();
12427            self.write_keyword("CASCADE");
12428        }
12429
12430        Ok(())
12431    }
12432
12433    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
12434        self.write_keyword("CREATE");
12435
12436        if cs.or_replace {
12437            self.write_space();
12438            self.write_keyword("OR REPLACE");
12439        }
12440
12441        if cs.temporary {
12442            self.write_space();
12443            self.write_keyword("TEMPORARY");
12444        }
12445
12446        self.write_space();
12447        self.write_keyword("SEQUENCE");
12448
12449        if cs.if_not_exists {
12450            self.write_space();
12451            self.write_keyword("IF NOT EXISTS");
12452        }
12453
12454        self.write_space();
12455        self.generate_table(&cs.name)?;
12456
12457        // Output AS <type> if present
12458        if let Some(as_type) = &cs.as_type {
12459            self.write_space();
12460            self.write_keyword("AS");
12461            self.write_space();
12462            self.generate_data_type(as_type)?;
12463        }
12464
12465        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
12466        if let Some(comment) = &cs.comment {
12467            self.write_space();
12468            self.write_keyword("COMMENT");
12469            self.write("=");
12470            self.generate_string_literal(comment)?;
12471        }
12472
12473        // If property_order is available, use it to preserve original order
12474        if !cs.property_order.is_empty() {
12475            for prop in &cs.property_order {
12476                match prop {
12477                    SeqPropKind::Start => {
12478                        if let Some(start) = cs.start {
12479                            self.write_space();
12480                            self.write_keyword("START WITH");
12481                            self.write(&format!(" {}", start));
12482                        }
12483                    }
12484                    SeqPropKind::Increment => {
12485                        if let Some(inc) = cs.increment {
12486                            self.write_space();
12487                            self.write_keyword("INCREMENT BY");
12488                            self.write(&format!(" {}", inc));
12489                        }
12490                    }
12491                    SeqPropKind::Minvalue => {
12492                        if let Some(min) = &cs.minvalue {
12493                            self.write_space();
12494                            match min {
12495                                SequenceBound::Value(v) => {
12496                                    self.write_keyword("MINVALUE");
12497                                    self.write(&format!(" {}", v));
12498                                }
12499                                SequenceBound::None => {
12500                                    self.write_keyword("NO MINVALUE");
12501                                }
12502                            }
12503                        }
12504                    }
12505                    SeqPropKind::Maxvalue => {
12506                        if let Some(max) = &cs.maxvalue {
12507                            self.write_space();
12508                            match max {
12509                                SequenceBound::Value(v) => {
12510                                    self.write_keyword("MAXVALUE");
12511                                    self.write(&format!(" {}", v));
12512                                }
12513                                SequenceBound::None => {
12514                                    self.write_keyword("NO MAXVALUE");
12515                                }
12516                            }
12517                        }
12518                    }
12519                    SeqPropKind::Cache => {
12520                        if let Some(cache) = cs.cache {
12521                            self.write_space();
12522                            self.write_keyword("CACHE");
12523                            self.write(&format!(" {}", cache));
12524                        }
12525                    }
12526                    SeqPropKind::NoCache => {
12527                        self.write_space();
12528                        self.write_keyword("NO CACHE");
12529                    }
12530                    SeqPropKind::NoCacheWord => {
12531                        self.write_space();
12532                        self.write_keyword("NOCACHE");
12533                    }
12534                    SeqPropKind::Cycle => {
12535                        self.write_space();
12536                        self.write_keyword("CYCLE");
12537                    }
12538                    SeqPropKind::NoCycle => {
12539                        self.write_space();
12540                        self.write_keyword("NO CYCLE");
12541                    }
12542                    SeqPropKind::NoCycleWord => {
12543                        self.write_space();
12544                        self.write_keyword("NOCYCLE");
12545                    }
12546                    SeqPropKind::OwnedBy => {
12547                        // Skip OWNED BY NONE (it's a no-op)
12548                        if !cs.owned_by_none {
12549                            if let Some(owned) = &cs.owned_by {
12550                                self.write_space();
12551                                self.write_keyword("OWNED BY");
12552                                self.write_space();
12553                                self.generate_table(owned)?;
12554                            }
12555                        }
12556                    }
12557                    SeqPropKind::Order => {
12558                        self.write_space();
12559                        self.write_keyword("ORDER");
12560                    }
12561                    SeqPropKind::NoOrder => {
12562                        self.write_space();
12563                        self.write_keyword("NOORDER");
12564                    }
12565                    SeqPropKind::Comment => {
12566                        // COMMENT is output above, before property_order iteration
12567                    }
12568                    SeqPropKind::Sharing => {
12569                        if let Some(val) = &cs.sharing {
12570                            self.write_space();
12571                            self.write(&format!("SHARING={}", val));
12572                        }
12573                    }
12574                    SeqPropKind::Keep => {
12575                        self.write_space();
12576                        self.write_keyword("KEEP");
12577                    }
12578                    SeqPropKind::NoKeep => {
12579                        self.write_space();
12580                        self.write_keyword("NOKEEP");
12581                    }
12582                    SeqPropKind::Scale => {
12583                        self.write_space();
12584                        self.write_keyword("SCALE");
12585                        if let Some(modifier) = &cs.scale_modifier {
12586                            if !modifier.is_empty() {
12587                                self.write_space();
12588                                self.write_keyword(modifier);
12589                            }
12590                        }
12591                    }
12592                    SeqPropKind::NoScale => {
12593                        self.write_space();
12594                        self.write_keyword("NOSCALE");
12595                    }
12596                    SeqPropKind::Shard => {
12597                        self.write_space();
12598                        self.write_keyword("SHARD");
12599                        if let Some(modifier) = &cs.shard_modifier {
12600                            if !modifier.is_empty() {
12601                                self.write_space();
12602                                self.write_keyword(modifier);
12603                            }
12604                        }
12605                    }
12606                    SeqPropKind::NoShard => {
12607                        self.write_space();
12608                        self.write_keyword("NOSHARD");
12609                    }
12610                    SeqPropKind::Session => {
12611                        self.write_space();
12612                        self.write_keyword("SESSION");
12613                    }
12614                    SeqPropKind::Global => {
12615                        self.write_space();
12616                        self.write_keyword("GLOBAL");
12617                    }
12618                    SeqPropKind::NoMinvalueWord => {
12619                        self.write_space();
12620                        self.write_keyword("NOMINVALUE");
12621                    }
12622                    SeqPropKind::NoMaxvalueWord => {
12623                        self.write_space();
12624                        self.write_keyword("NOMAXVALUE");
12625                    }
12626                }
12627            }
12628        } else {
12629            // Fallback: default order for backwards compatibility
12630            if let Some(inc) = cs.increment {
12631                self.write_space();
12632                self.write_keyword("INCREMENT BY");
12633                self.write(&format!(" {}", inc));
12634            }
12635
12636            if let Some(min) = &cs.minvalue {
12637                self.write_space();
12638                match min {
12639                    SequenceBound::Value(v) => {
12640                        self.write_keyword("MINVALUE");
12641                        self.write(&format!(" {}", v));
12642                    }
12643                    SequenceBound::None => {
12644                        self.write_keyword("NO MINVALUE");
12645                    }
12646                }
12647            }
12648
12649            if let Some(max) = &cs.maxvalue {
12650                self.write_space();
12651                match max {
12652                    SequenceBound::Value(v) => {
12653                        self.write_keyword("MAXVALUE");
12654                        self.write(&format!(" {}", v));
12655                    }
12656                    SequenceBound::None => {
12657                        self.write_keyword("NO MAXVALUE");
12658                    }
12659                }
12660            }
12661
12662            if let Some(start) = cs.start {
12663                self.write_space();
12664                self.write_keyword("START WITH");
12665                self.write(&format!(" {}", start));
12666            }
12667
12668            if let Some(cache) = cs.cache {
12669                self.write_space();
12670                self.write_keyword("CACHE");
12671                self.write(&format!(" {}", cache));
12672            }
12673
12674            if cs.cycle {
12675                self.write_space();
12676                self.write_keyword("CYCLE");
12677            }
12678
12679            if let Some(owned) = &cs.owned_by {
12680                self.write_space();
12681                self.write_keyword("OWNED BY");
12682                self.write_space();
12683                self.generate_table(owned)?;
12684            }
12685        }
12686
12687        Ok(())
12688    }
12689
12690    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
12691        self.write_keyword("DROP SEQUENCE");
12692
12693        if ds.if_exists {
12694            self.write_space();
12695            self.write_keyword("IF EXISTS");
12696        }
12697
12698        self.write_space();
12699        self.generate_table(&ds.name)?;
12700
12701        if ds.cascade {
12702            self.write_space();
12703            self.write_keyword("CASCADE");
12704        }
12705
12706        Ok(())
12707    }
12708
12709    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
12710        self.write_keyword("ALTER SEQUENCE");
12711
12712        if als.if_exists {
12713            self.write_space();
12714            self.write_keyword("IF EXISTS");
12715        }
12716
12717        self.write_space();
12718        self.generate_table(&als.name)?;
12719
12720        if let Some(inc) = als.increment {
12721            self.write_space();
12722            self.write_keyword("INCREMENT BY");
12723            self.write(&format!(" {}", inc));
12724        }
12725
12726        if let Some(min) = &als.minvalue {
12727            self.write_space();
12728            match min {
12729                SequenceBound::Value(v) => {
12730                    self.write_keyword("MINVALUE");
12731                    self.write(&format!(" {}", v));
12732                }
12733                SequenceBound::None => {
12734                    self.write_keyword("NO MINVALUE");
12735                }
12736            }
12737        }
12738
12739        if let Some(max) = &als.maxvalue {
12740            self.write_space();
12741            match max {
12742                SequenceBound::Value(v) => {
12743                    self.write_keyword("MAXVALUE");
12744                    self.write(&format!(" {}", v));
12745                }
12746                SequenceBound::None => {
12747                    self.write_keyword("NO MAXVALUE");
12748                }
12749            }
12750        }
12751
12752        if let Some(start) = als.start {
12753            self.write_space();
12754            self.write_keyword("START WITH");
12755            self.write(&format!(" {}", start));
12756        }
12757
12758        if let Some(restart) = &als.restart {
12759            self.write_space();
12760            self.write_keyword("RESTART");
12761            if let Some(val) = restart {
12762                self.write_keyword(" WITH");
12763                self.write(&format!(" {}", val));
12764            }
12765        }
12766
12767        if let Some(cache) = als.cache {
12768            self.write_space();
12769            self.write_keyword("CACHE");
12770            self.write(&format!(" {}", cache));
12771        }
12772
12773        if let Some(cycle) = als.cycle {
12774            self.write_space();
12775            if cycle {
12776                self.write_keyword("CYCLE");
12777            } else {
12778                self.write_keyword("NO CYCLE");
12779            }
12780        }
12781
12782        if let Some(owned) = &als.owned_by {
12783            self.write_space();
12784            self.write_keyword("OWNED BY");
12785            self.write_space();
12786            if let Some(table) = owned {
12787                self.generate_table(table)?;
12788            } else {
12789                self.write_keyword("NONE");
12790            }
12791        }
12792
12793        Ok(())
12794    }
12795
12796    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
12797        self.write_keyword("CREATE");
12798
12799        if ct.or_replace {
12800            self.write_space();
12801            self.write_keyword("OR REPLACE");
12802        }
12803
12804        if ct.constraint {
12805            self.write_space();
12806            self.write_keyword("CONSTRAINT");
12807        }
12808
12809        self.write_space();
12810        self.write_keyword("TRIGGER");
12811        self.write_space();
12812        self.generate_identifier(&ct.name)?;
12813
12814        self.write_space();
12815        match ct.timing {
12816            TriggerTiming::Before => self.write_keyword("BEFORE"),
12817            TriggerTiming::After => self.write_keyword("AFTER"),
12818            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
12819        }
12820
12821        // Events
12822        for (i, event) in ct.events.iter().enumerate() {
12823            if i > 0 {
12824                self.write_keyword(" OR");
12825            }
12826            self.write_space();
12827            match event {
12828                TriggerEvent::Insert => self.write_keyword("INSERT"),
12829                TriggerEvent::Update(cols) => {
12830                    self.write_keyword("UPDATE");
12831                    if let Some(cols) = cols {
12832                        self.write_space();
12833                        self.write_keyword("OF");
12834                        for (j, col) in cols.iter().enumerate() {
12835                            if j > 0 {
12836                                self.write(",");
12837                            }
12838                            self.write_space();
12839                            self.generate_identifier(col)?;
12840                        }
12841                    }
12842                }
12843                TriggerEvent::Delete => self.write_keyword("DELETE"),
12844                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
12845            }
12846        }
12847
12848        self.write_space();
12849        self.write_keyword("ON");
12850        self.write_space();
12851        self.generate_table(&ct.table)?;
12852
12853        // Referencing clause
12854        if let Some(ref_clause) = &ct.referencing {
12855            self.write_space();
12856            self.write_keyword("REFERENCING");
12857            if let Some(old_table) = &ref_clause.old_table {
12858                self.write_space();
12859                self.write_keyword("OLD TABLE AS");
12860                self.write_space();
12861                self.generate_identifier(old_table)?;
12862            }
12863            if let Some(new_table) = &ref_clause.new_table {
12864                self.write_space();
12865                self.write_keyword("NEW TABLE AS");
12866                self.write_space();
12867                self.generate_identifier(new_table)?;
12868            }
12869            if let Some(old_row) = &ref_clause.old_row {
12870                self.write_space();
12871                self.write_keyword("OLD ROW AS");
12872                self.write_space();
12873                self.generate_identifier(old_row)?;
12874            }
12875            if let Some(new_row) = &ref_clause.new_row {
12876                self.write_space();
12877                self.write_keyword("NEW ROW AS");
12878                self.write_space();
12879                self.generate_identifier(new_row)?;
12880            }
12881        }
12882
12883        // Deferrable options for constraint triggers (must come before FOR EACH)
12884        if let Some(deferrable) = ct.deferrable {
12885            self.write_space();
12886            if deferrable {
12887                self.write_keyword("DEFERRABLE");
12888            } else {
12889                self.write_keyword("NOT DEFERRABLE");
12890            }
12891        }
12892
12893        if let Some(initially) = ct.initially_deferred {
12894            self.write_space();
12895            self.write_keyword("INITIALLY");
12896            self.write_space();
12897            if initially {
12898                self.write_keyword("DEFERRED");
12899            } else {
12900                self.write_keyword("IMMEDIATE");
12901            }
12902        }
12903
12904        self.write_space();
12905        self.write_keyword("FOR EACH");
12906        self.write_space();
12907        match ct.for_each {
12908            TriggerForEach::Row => self.write_keyword("ROW"),
12909            TriggerForEach::Statement => self.write_keyword("STATEMENT"),
12910        }
12911
12912        // When clause
12913        if let Some(when) = &ct.when {
12914            self.write_space();
12915            self.write_keyword("WHEN");
12916            self.write(" (");
12917            self.generate_expression(when)?;
12918            self.write(")");
12919        }
12920
12921        // Body
12922        self.write_space();
12923        match &ct.body {
12924            TriggerBody::Execute { function, args } => {
12925                self.write_keyword("EXECUTE FUNCTION");
12926                self.write_space();
12927                self.generate_table(function)?;
12928                self.write("(");
12929                for (i, arg) in args.iter().enumerate() {
12930                    if i > 0 {
12931                        self.write(", ");
12932                    }
12933                    self.generate_expression(arg)?;
12934                }
12935                self.write(")");
12936            }
12937            TriggerBody::Block(block) => {
12938                self.write_keyword("BEGIN");
12939                self.write_space();
12940                self.write(block);
12941                self.write_space();
12942                self.write_keyword("END");
12943            }
12944        }
12945
12946        Ok(())
12947    }
12948
12949    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
12950        self.write_keyword("DROP TRIGGER");
12951
12952        if dt.if_exists {
12953            self.write_space();
12954            self.write_keyword("IF EXISTS");
12955        }
12956
12957        self.write_space();
12958        self.generate_identifier(&dt.name)?;
12959
12960        if let Some(table) = &dt.table {
12961            self.write_space();
12962            self.write_keyword("ON");
12963            self.write_space();
12964            self.generate_table(table)?;
12965        }
12966
12967        if dt.cascade {
12968            self.write_space();
12969            self.write_keyword("CASCADE");
12970        }
12971
12972        Ok(())
12973    }
12974
12975    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
12976        self.write_keyword("CREATE TYPE");
12977
12978        if ct.if_not_exists {
12979            self.write_space();
12980            self.write_keyword("IF NOT EXISTS");
12981        }
12982
12983        self.write_space();
12984        self.generate_table(&ct.name)?;
12985
12986        self.write_space();
12987        self.write_keyword("AS");
12988        self.write_space();
12989
12990        match &ct.definition {
12991            TypeDefinition::Enum(values) => {
12992                self.write_keyword("ENUM");
12993                self.write(" (");
12994                for (i, val) in values.iter().enumerate() {
12995                    if i > 0 {
12996                        self.write(", ");
12997                    }
12998                    self.write(&format!("'{}'", val));
12999                }
13000                self.write(")");
13001            }
13002            TypeDefinition::Composite(attrs) => {
13003                self.write("(");
13004                for (i, attr) in attrs.iter().enumerate() {
13005                    if i > 0 {
13006                        self.write(", ");
13007                    }
13008                    self.generate_identifier(&attr.name)?;
13009                    self.write_space();
13010                    self.generate_data_type(&attr.data_type)?;
13011                    if let Some(collate) = &attr.collate {
13012                        self.write_space();
13013                        self.write_keyword("COLLATE");
13014                        self.write_space();
13015                        self.generate_identifier(collate)?;
13016                    }
13017                }
13018                self.write(")");
13019            }
13020            TypeDefinition::Range {
13021                subtype,
13022                subtype_diff,
13023                canonical,
13024            } => {
13025                self.write_keyword("RANGE");
13026                self.write(" (");
13027                self.write_keyword("SUBTYPE");
13028                self.write(" = ");
13029                self.generate_data_type(subtype)?;
13030                if let Some(diff) = subtype_diff {
13031                    self.write(", ");
13032                    self.write_keyword("SUBTYPE_DIFF");
13033                    self.write(" = ");
13034                    self.write(diff);
13035                }
13036                if let Some(canon) = canonical {
13037                    self.write(", ");
13038                    self.write_keyword("CANONICAL");
13039                    self.write(" = ");
13040                    self.write(canon);
13041                }
13042                self.write(")");
13043            }
13044            TypeDefinition::Base {
13045                input,
13046                output,
13047                internallength,
13048            } => {
13049                self.write("(");
13050                self.write_keyword("INPUT");
13051                self.write(" = ");
13052                self.write(input);
13053                self.write(", ");
13054                self.write_keyword("OUTPUT");
13055                self.write(" = ");
13056                self.write(output);
13057                if let Some(len) = internallength {
13058                    self.write(", ");
13059                    self.write_keyword("INTERNALLENGTH");
13060                    self.write(" = ");
13061                    self.write(&len.to_string());
13062                }
13063                self.write(")");
13064            }
13065            TypeDefinition::Domain {
13066                base_type,
13067                default,
13068                constraints,
13069            } => {
13070                self.generate_data_type(base_type)?;
13071                if let Some(def) = default {
13072                    self.write_space();
13073                    self.write_keyword("DEFAULT");
13074                    self.write_space();
13075                    self.generate_expression(def)?;
13076                }
13077                for constr in constraints {
13078                    self.write_space();
13079                    if let Some(name) = &constr.name {
13080                        self.write_keyword("CONSTRAINT");
13081                        self.write_space();
13082                        self.generate_identifier(name)?;
13083                        self.write_space();
13084                    }
13085                    self.write_keyword("CHECK");
13086                    self.write(" (");
13087                    self.generate_expression(&constr.check)?;
13088                    self.write(")");
13089                }
13090            }
13091        }
13092
13093        Ok(())
13094    }
13095
13096    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
13097        self.write_keyword("DROP TYPE");
13098
13099        if dt.if_exists {
13100            self.write_space();
13101            self.write_keyword("IF EXISTS");
13102        }
13103
13104        self.write_space();
13105        self.generate_table(&dt.name)?;
13106
13107        if dt.cascade {
13108            self.write_space();
13109            self.write_keyword("CASCADE");
13110        }
13111
13112        Ok(())
13113    }
13114
13115    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
13116        // Athena: DESCRIBE uses Hive engine (backticks)
13117        let saved_athena_hive_context = self.athena_hive_context;
13118        if matches!(
13119            self.config.dialect,
13120            Some(crate::dialects::DialectType::Athena)
13121        ) {
13122            self.athena_hive_context = true;
13123        }
13124
13125        // Output leading comments before DESCRIBE
13126        for comment in &d.leading_comments {
13127            self.write_formatted_comment(comment);
13128            self.write(" ");
13129        }
13130
13131        self.write_keyword("DESCRIBE");
13132
13133        if d.extended {
13134            self.write_space();
13135            self.write_keyword("EXTENDED");
13136        } else if d.formatted {
13137            self.write_space();
13138            self.write_keyword("FORMATTED");
13139        }
13140
13141        // Output style like ANALYZE, HISTORY
13142        if let Some(ref style) = d.style {
13143            self.write_space();
13144            self.write_keyword(style);
13145        }
13146
13147        // Handle object kind (TABLE, VIEW) based on dialect
13148        let should_output_kind = match self.config.dialect {
13149            // Spark doesn't use TABLE/VIEW after DESCRIBE
13150            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
13151                false
13152            }
13153            // Snowflake always includes TABLE
13154            Some(DialectType::Snowflake) => true,
13155            _ => d.kind.is_some(),
13156        };
13157        if should_output_kind {
13158            if let Some(ref kind) = d.kind {
13159                self.write_space();
13160                self.write_keyword(kind);
13161            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
13162                self.write_space();
13163                self.write_keyword("TABLE");
13164            }
13165        }
13166
13167        self.write_space();
13168        self.generate_expression(&d.target)?;
13169
13170        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
13171        if let Some(ref partition) = d.partition {
13172            self.write_space();
13173            self.generate_expression(partition)?;
13174        }
13175
13176        // Databricks: AS JSON
13177        if d.as_json {
13178            self.write_space();
13179            self.write_keyword("AS JSON");
13180        }
13181
13182        // Output properties like type=stage
13183        for (name, value) in &d.properties {
13184            self.write_space();
13185            self.write(name);
13186            self.write("=");
13187            self.write(value);
13188        }
13189
13190        // Restore Athena Hive context
13191        self.athena_hive_context = saved_athena_hive_context;
13192
13193        Ok(())
13194    }
13195
13196    /// Generate SHOW statement (Snowflake, MySQL, etc.)
13197    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
13198    fn generate_show(&mut self, s: &Show) -> Result<()> {
13199        self.write_keyword("SHOW");
13200        self.write_space();
13201
13202        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
13203        // where TERSE is syntactically valid but has no effect on output
13204        let show_terse = s.terse
13205            && !matches!(
13206                s.this.as_str(),
13207                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
13208            );
13209        if show_terse {
13210            self.write_keyword("TERSE");
13211            self.write_space();
13212        }
13213
13214        // Object type (USERS, TABLES, DATABASES, etc.)
13215        self.write_keyword(&s.this);
13216
13217        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
13218        if let Some(ref target_expr) = s.target {
13219            self.write_space();
13220            self.generate_expression(target_expr)?;
13221        }
13222
13223        // HISTORY keyword
13224        if s.history {
13225            self.write_space();
13226            self.write_keyword("HISTORY");
13227        }
13228
13229        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
13230        if let Some(ref for_target) = s.for_target {
13231            self.write_space();
13232            self.write_keyword("FOR");
13233            self.write_space();
13234            self.generate_expression(for_target)?;
13235        }
13236
13237        // Determine ordering based on dialect:
13238        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
13239        // - MySQL: IN, FROM, LIKE (when FROM is present)
13240        use crate::dialects::DialectType;
13241        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
13242
13243        if !is_snowflake && s.from.is_some() {
13244            // MySQL ordering: IN, FROM, LIKE
13245
13246            // IN scope_kind [scope]
13247            if let Some(ref scope_kind) = s.scope_kind {
13248                self.write_space();
13249                self.write_keyword("IN");
13250                self.write_space();
13251                self.write_keyword(scope_kind);
13252                if let Some(ref scope) = s.scope {
13253                    self.write_space();
13254                    self.generate_expression(scope)?;
13255                }
13256            } else if let Some(ref scope) = s.scope {
13257                self.write_space();
13258                self.write_keyword("IN");
13259                self.write_space();
13260                self.generate_expression(scope)?;
13261            }
13262
13263            // FROM clause
13264            if let Some(ref from) = s.from {
13265                self.write_space();
13266                self.write_keyword("FROM");
13267                self.write_space();
13268                self.generate_expression(from)?;
13269            }
13270
13271            // Second FROM clause (db name)
13272            if let Some(ref db) = s.db {
13273                self.write_space();
13274                self.write_keyword("FROM");
13275                self.write_space();
13276                self.generate_expression(db)?;
13277            }
13278
13279            // LIKE pattern
13280            if let Some(ref like) = s.like {
13281                self.write_space();
13282                self.write_keyword("LIKE");
13283                self.write_space();
13284                self.generate_expression(like)?;
13285            }
13286        } else {
13287            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
13288
13289            // LIKE pattern
13290            if let Some(ref like) = s.like {
13291                self.write_space();
13292                self.write_keyword("LIKE");
13293                self.write_space();
13294                self.generate_expression(like)?;
13295            }
13296
13297            // IN scope_kind [scope]
13298            if let Some(ref scope_kind) = s.scope_kind {
13299                self.write_space();
13300                self.write_keyword("IN");
13301                self.write_space();
13302                self.write_keyword(scope_kind);
13303                if let Some(ref scope) = s.scope {
13304                    self.write_space();
13305                    self.generate_expression(scope)?;
13306                }
13307            } else if let Some(ref scope) = s.scope {
13308                self.write_space();
13309                self.write_keyword("IN");
13310                self.write_space();
13311                self.generate_expression(scope)?;
13312            }
13313        }
13314
13315        // STARTS WITH pattern
13316        if let Some(ref starts_with) = s.starts_with {
13317            self.write_space();
13318            self.write_keyword("STARTS WITH");
13319            self.write_space();
13320            self.generate_expression(starts_with)?;
13321        }
13322
13323        // LIMIT clause
13324        if let Some(ref limit) = s.limit {
13325            self.write_space();
13326            self.generate_limit(limit)?;
13327        }
13328
13329        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
13330        if is_snowflake {
13331            if let Some(ref from) = s.from {
13332                self.write_space();
13333                self.write_keyword("FROM");
13334                self.write_space();
13335                self.generate_expression(from)?;
13336            }
13337        }
13338
13339        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
13340        if let Some(ref where_clause) = s.where_clause {
13341            self.write_space();
13342            self.write_keyword("WHERE");
13343            self.write_space();
13344            self.generate_expression(where_clause)?;
13345        }
13346
13347        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
13348        if let Some(is_mutex) = s.mutex {
13349            self.write_space();
13350            if is_mutex {
13351                self.write_keyword("MUTEX");
13352            } else {
13353                self.write_keyword("STATUS");
13354            }
13355        }
13356
13357        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
13358        if !s.privileges.is_empty() {
13359            self.write_space();
13360            self.write_keyword("WITH PRIVILEGES");
13361            self.write_space();
13362            for (i, priv_name) in s.privileges.iter().enumerate() {
13363                if i > 0 {
13364                    self.write(", ");
13365                }
13366                self.write_keyword(priv_name);
13367            }
13368        }
13369
13370        Ok(())
13371    }
13372
13373    // ==================== End DDL Generation ====================
13374
13375    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
13376        use crate::dialects::DialectType;
13377        match lit {
13378            Literal::String(s) => {
13379                self.generate_string_literal(s)?;
13380            }
13381            Literal::Number(n) => {
13382                if matches!(self.config.dialect, Some(DialectType::MySQL))
13383                    && n.len() > 2
13384                    && (n.starts_with("0x") || n.starts_with("0X"))
13385                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
13386                {
13387                    return self.generate_identifier(&Identifier {
13388                        name: n.clone(),
13389                        quoted: true,
13390                        trailing_comments: Vec::new(),
13391                    });
13392                }
13393                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
13394                // for dialects that don't support them (MySQL interprets as identifier).
13395                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
13396                let n = if n.contains('_')
13397                    && !matches!(
13398                        self.config.dialect,
13399                        Some(DialectType::ClickHouse)
13400                            | Some(DialectType::DuckDB)
13401                            | Some(DialectType::PostgreSQL)
13402                            | Some(DialectType::Hive)
13403                            | Some(DialectType::Spark)
13404                            | Some(DialectType::Databricks)
13405                    ) {
13406                    std::borrow::Cow::Owned(n.replace('_', ""))
13407                } else {
13408                    std::borrow::Cow::Borrowed(n.as_str())
13409                };
13410                // Normalize numbers starting with decimal point to have leading zero
13411                // e.g., .25 -> 0.25 (matches sqlglot behavior)
13412                if n.starts_with('.') {
13413                    self.write("0");
13414                    self.write(&n);
13415                } else if n.starts_with("-.") {
13416                    // Handle negative numbers like -.25 -> -0.25
13417                    self.write("-0");
13418                    self.write(&n[1..]);
13419                } else {
13420                    self.write(&n);
13421                }
13422            }
13423            Literal::HexString(h) => {
13424                // Most dialects use lowercase x'...' for hex literals; Spark/Databricks/Teradata use uppercase X'...'
13425                match self.config.dialect {
13426                    Some(DialectType::Spark)
13427                    | Some(DialectType::Databricks)
13428                    | Some(DialectType::Teradata) => self.write("X'"),
13429                    _ => self.write("x'"),
13430                }
13431                self.write(h);
13432                self.write("'");
13433            }
13434            Literal::HexNumber(h) => {
13435                // Hex number (0xA) - integer in hex notation (from BigQuery)
13436                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
13437                // For other dialects, convert to decimal integer
13438                match self.config.dialect {
13439                    Some(DialectType::BigQuery)
13440                    | Some(DialectType::TSQL)
13441                    | Some(DialectType::Fabric) => {
13442                        self.write("0x");
13443                        self.write(h);
13444                    }
13445                    _ => {
13446                        // Convert hex to decimal
13447                        if let Ok(val) = u64::from_str_radix(h, 16) {
13448                            self.write(&val.to_string());
13449                        } else {
13450                            // Fallback: keep as 0x notation
13451                            self.write("0x");
13452                            self.write(h);
13453                        }
13454                    }
13455                }
13456            }
13457            Literal::BitString(b) => {
13458                // Bit string B'0101...'
13459                self.write("B'");
13460                self.write(b);
13461                self.write("'");
13462            }
13463            Literal::ByteString(b) => {
13464                // Byte string b'...' (BigQuery style)
13465                self.write("b'");
13466                // Escape special characters for output
13467                self.write_escaped_byte_string(b);
13468                self.write("'");
13469            }
13470            Literal::NationalString(s) => {
13471                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
13472                // Other dialects strip the N prefix and output as regular string
13473                let keep_n_prefix = matches!(
13474                    self.config.dialect,
13475                    Some(DialectType::TSQL)
13476                        | Some(DialectType::Oracle)
13477                        | Some(DialectType::MySQL)
13478                        | None
13479                );
13480                if keep_n_prefix {
13481                    self.write("N'");
13482                } else {
13483                    self.write("'");
13484                }
13485                self.write(s);
13486                self.write("'");
13487            }
13488            Literal::Date(d) => {
13489                self.generate_date_literal(d)?;
13490            }
13491            Literal::Time(t) => {
13492                self.generate_time_literal(t)?;
13493            }
13494            Literal::Timestamp(ts) => {
13495                self.generate_timestamp_literal(ts)?;
13496            }
13497            Literal::Datetime(dt) => {
13498                self.generate_datetime_literal(dt)?;
13499            }
13500            Literal::TripleQuotedString(s, _quote_char) => {
13501                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
13502                if matches!(
13503                    self.config.dialect,
13504                    Some(crate::dialects::DialectType::BigQuery)
13505                        | Some(crate::dialects::DialectType::DuckDB)
13506                        | Some(crate::dialects::DialectType::Snowflake)
13507                        | Some(crate::dialects::DialectType::Spark)
13508                        | Some(crate::dialects::DialectType::Hive)
13509                        | Some(crate::dialects::DialectType::Presto)
13510                        | Some(crate::dialects::DialectType::Trino)
13511                        | Some(crate::dialects::DialectType::PostgreSQL)
13512                        | Some(crate::dialects::DialectType::MySQL)
13513                        | Some(crate::dialects::DialectType::Redshift)
13514                        | Some(crate::dialects::DialectType::TSQL)
13515                        | Some(crate::dialects::DialectType::Oracle)
13516                        | Some(crate::dialects::DialectType::ClickHouse)
13517                        | Some(crate::dialects::DialectType::Databricks)
13518                        | Some(crate::dialects::DialectType::SQLite)
13519                ) {
13520                    self.generate_string_literal(s)?;
13521                } else {
13522                    // Preserve triple-quoted string syntax for generic/unknown dialects
13523                    let quotes = format!("{0}{0}{0}", _quote_char);
13524                    self.write(&quotes);
13525                    self.write(s);
13526                    self.write(&quotes);
13527                }
13528            }
13529            Literal::EscapeString(s) => {
13530                // PostgreSQL escape string: e'...' or E'...'
13531                // Token text format is "e:content" or "E:content"
13532                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
13533                use crate::dialects::DialectType;
13534                let content = if let Some(c) = s.strip_prefix("e:") {
13535                    c
13536                } else if let Some(c) = s.strip_prefix("E:") {
13537                    c
13538                } else {
13539                    s.as_str()
13540                };
13541
13542                // MySQL: output the content without quotes or prefix
13543                if matches!(
13544                    self.config.dialect,
13545                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
13546                ) {
13547                    self.write(content);
13548                } else {
13549                    // Some dialects use lowercase e' prefix
13550                    let prefix = if matches!(
13551                        self.config.dialect,
13552                        Some(DialectType::SingleStore)
13553                            | Some(DialectType::DuckDB)
13554                            | Some(DialectType::PostgreSQL)
13555                            | Some(DialectType::CockroachDB)
13556                            | Some(DialectType::Materialize)
13557                            | Some(DialectType::RisingWave)
13558                    ) {
13559                        "e'"
13560                    } else {
13561                        "E'"
13562                    };
13563
13564                    // Normalize \' to '' for output
13565                    let normalized = content.replace("\\'", "''");
13566                    self.write(prefix);
13567                    self.write(&normalized);
13568                    self.write("'");
13569                }
13570            }
13571            Literal::DollarString(s) => {
13572                // Convert dollar-quoted strings to single-quoted strings
13573                // (like Python sqlglot's rawstring_sql)
13574                use crate::dialects::DialectType;
13575                // Extract content from tag\x00content format
13576                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
13577                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
13578                let escape_backslash = matches!(self.config.dialect, Some(DialectType::Snowflake));
13579                // Step 2: Determine quote escaping style
13580                // Snowflake: ' -> \' (backslash escape)
13581                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
13582                let use_backslash_quote =
13583                    matches!(self.config.dialect, Some(DialectType::Snowflake));
13584
13585                let mut escaped = String::with_capacity(content.len() + 4);
13586                for ch in content.chars() {
13587                    if escape_backslash && ch == '\\' {
13588                        // Escape backslash first (before quote escaping)
13589                        escaped.push('\\');
13590                        escaped.push('\\');
13591                    } else if ch == '\'' {
13592                        if use_backslash_quote {
13593                            escaped.push('\\');
13594                            escaped.push('\'');
13595                        } else {
13596                            escaped.push('\'');
13597                            escaped.push('\'');
13598                        }
13599                    } else {
13600                        escaped.push(ch);
13601                    }
13602                }
13603                self.write("'");
13604                self.write(&escaped);
13605                self.write("'");
13606            }
13607            Literal::RawString(s) => {
13608                // Raw strings (r"..." or r'...') contain literal backslashes.
13609                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
13610                // 1. If \\ is in STRING_ESCAPES, double all backslashes
13611                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
13612                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
13613                use crate::dialects::DialectType;
13614
13615                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
13616                let escape_backslash = matches!(
13617                    self.config.dialect,
13618                    Some(DialectType::BigQuery)
13619                        | Some(DialectType::MySQL)
13620                        | Some(DialectType::SingleStore)
13621                        | Some(DialectType::TiDB)
13622                        | Some(DialectType::Hive)
13623                        | Some(DialectType::Spark)
13624                        | Some(DialectType::Databricks)
13625                        | Some(DialectType::Drill)
13626                        | Some(DialectType::Snowflake)
13627                        | Some(DialectType::Redshift)
13628                        | Some(DialectType::ClickHouse)
13629                );
13630
13631                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
13632                // These escape quotes as \' instead of ''
13633                let backslash_escapes_quote = matches!(
13634                    self.config.dialect,
13635                    Some(DialectType::BigQuery)
13636                        | Some(DialectType::Hive)
13637                        | Some(DialectType::Spark)
13638                        | Some(DialectType::Databricks)
13639                        | Some(DialectType::Drill)
13640                        | Some(DialectType::Snowflake)
13641                        | Some(DialectType::Redshift)
13642                );
13643
13644                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
13645                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
13646                let supports_escape_sequences = escape_backslash;
13647
13648                let mut escaped = String::with_capacity(s.len() + 4);
13649                for ch in s.chars() {
13650                    if escape_backslash && ch == '\\' {
13651                        // Double the backslash for the target dialect
13652                        escaped.push('\\');
13653                        escaped.push('\\');
13654                    } else if ch == '\'' {
13655                        if backslash_escapes_quote {
13656                            // Use backslash to escape the quote: \'
13657                            escaped.push('\\');
13658                            escaped.push('\'');
13659                        } else {
13660                            // Use SQL standard quote doubling: ''
13661                            escaped.push('\'');
13662                            escaped.push('\'');
13663                        }
13664                    } else if supports_escape_sequences {
13665                        // Apply ESCAPED_SEQUENCES mapping for special chars
13666                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
13667                        match ch {
13668                            '\n' => {
13669                                escaped.push('\\');
13670                                escaped.push('n');
13671                            }
13672                            '\r' => {
13673                                escaped.push('\\');
13674                                escaped.push('r');
13675                            }
13676                            '\t' => {
13677                                escaped.push('\\');
13678                                escaped.push('t');
13679                            }
13680                            '\x07' => {
13681                                escaped.push('\\');
13682                                escaped.push('a');
13683                            }
13684                            '\x08' => {
13685                                escaped.push('\\');
13686                                escaped.push('b');
13687                            }
13688                            '\x0C' => {
13689                                escaped.push('\\');
13690                                escaped.push('f');
13691                            }
13692                            '\x0B' => {
13693                                escaped.push('\\');
13694                                escaped.push('v');
13695                            }
13696                            _ => escaped.push(ch),
13697                        }
13698                    } else {
13699                        escaped.push(ch);
13700                    }
13701                }
13702                self.write("'");
13703                self.write(&escaped);
13704                self.write("'");
13705            }
13706        }
13707        Ok(())
13708    }
13709
13710    /// Generate a DATE literal with dialect-specific formatting
13711    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
13712        use crate::dialects::DialectType;
13713
13714        match self.config.dialect {
13715            // SQL Server uses CONVERT or CAST
13716            Some(DialectType::TSQL) => {
13717                self.write("CAST('");
13718                self.write(d);
13719                self.write("' AS DATE)");
13720            }
13721            // BigQuery uses CAST syntax for type literals
13722            // DATE 'value' -> CAST('value' AS DATE)
13723            Some(DialectType::BigQuery) => {
13724                self.write("CAST('");
13725                self.write(d);
13726                self.write("' AS DATE)");
13727            }
13728            // Exasol uses CAST syntax for DATE literals
13729            // DATE 'value' -> CAST('value' AS DATE)
13730            Some(DialectType::Exasol) => {
13731                self.write("CAST('");
13732                self.write(d);
13733                self.write("' AS DATE)");
13734            }
13735            // Snowflake uses CAST syntax for DATE literals
13736            // DATE 'value' -> CAST('value' AS DATE)
13737            Some(DialectType::Snowflake) => {
13738                self.write("CAST('");
13739                self.write(d);
13740                self.write("' AS DATE)");
13741            }
13742            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
13743            Some(DialectType::PostgreSQL)
13744            | Some(DialectType::MySQL)
13745            | Some(DialectType::SingleStore)
13746            | Some(DialectType::TiDB)
13747            | Some(DialectType::Redshift) => {
13748                self.write("CAST('");
13749                self.write(d);
13750                self.write("' AS DATE)");
13751            }
13752            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
13753            Some(DialectType::DuckDB)
13754            | Some(DialectType::Presto)
13755            | Some(DialectType::Trino)
13756            | Some(DialectType::Athena)
13757            | Some(DialectType::Spark)
13758            | Some(DialectType::Databricks)
13759            | Some(DialectType::Hive) => {
13760                self.write("CAST('");
13761                self.write(d);
13762                self.write("' AS DATE)");
13763            }
13764            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
13765            Some(DialectType::Oracle) => {
13766                self.write("TO_DATE('");
13767                self.write(d);
13768                self.write("', 'YYYY-MM-DD')");
13769            }
13770            // Standard SQL: DATE '...'
13771            _ => {
13772                self.write_keyword("DATE");
13773                self.write(" '");
13774                self.write(d);
13775                self.write("'");
13776            }
13777        }
13778        Ok(())
13779    }
13780
13781    /// Generate a TIME literal with dialect-specific formatting
13782    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
13783        use crate::dialects::DialectType;
13784
13785        match self.config.dialect {
13786            // SQL Server uses CONVERT or CAST
13787            Some(DialectType::TSQL) => {
13788                self.write("CAST('");
13789                self.write(t);
13790                self.write("' AS TIME)");
13791            }
13792            // Standard SQL: TIME '...'
13793            _ => {
13794                self.write_keyword("TIME");
13795                self.write(" '");
13796                self.write(t);
13797                self.write("'");
13798            }
13799        }
13800        Ok(())
13801    }
13802
13803    /// Generate a date expression for Dremio, converting DATE literals to CAST
13804    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
13805        use crate::expressions::Literal;
13806
13807        match expr {
13808            Expression::Literal(Literal::Date(d)) => {
13809                // DATE 'value' -> CAST('value' AS DATE)
13810                self.write("CAST('");
13811                self.write(d);
13812                self.write("' AS DATE)");
13813            }
13814            _ => {
13815                // For all other expressions, generate normally
13816                self.generate_expression(expr)?;
13817            }
13818        }
13819        Ok(())
13820    }
13821
13822    /// Generate a TIMESTAMP literal with dialect-specific formatting
13823    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
13824        use crate::dialects::DialectType;
13825
13826        match self.config.dialect {
13827            // SQL Server uses CONVERT or CAST
13828            Some(DialectType::TSQL) => {
13829                self.write("CAST('");
13830                self.write(ts);
13831                self.write("' AS DATETIME2)");
13832            }
13833            // BigQuery uses CAST syntax for type literals
13834            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13835            Some(DialectType::BigQuery) => {
13836                self.write("CAST('");
13837                self.write(ts);
13838                self.write("' AS TIMESTAMP)");
13839            }
13840            // Snowflake uses CAST syntax for TIMESTAMP literals
13841            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13842            Some(DialectType::Snowflake) => {
13843                self.write("CAST('");
13844                self.write(ts);
13845                self.write("' AS TIMESTAMP)");
13846            }
13847            // Dremio uses CAST syntax for TIMESTAMP literals
13848            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13849            Some(DialectType::Dremio) => {
13850                self.write("CAST('");
13851                self.write(ts);
13852                self.write("' AS TIMESTAMP)");
13853            }
13854            // Exasol uses CAST syntax for TIMESTAMP literals
13855            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
13856            Some(DialectType::Exasol) => {
13857                self.write("CAST('");
13858                self.write(ts);
13859                self.write("' AS TIMESTAMP)");
13860            }
13861            // Oracle prefers TO_TIMESTAMP function call
13862            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
13863            Some(DialectType::Oracle) => {
13864                self.write("TO_TIMESTAMP('");
13865                self.write(ts);
13866                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
13867            }
13868            // Presto/Trino: always use CAST for TIMESTAMP literals
13869            Some(DialectType::Presto) | Some(DialectType::Trino) => {
13870                if Self::timestamp_has_timezone(ts) {
13871                    self.write("CAST('");
13872                    self.write(ts);
13873                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
13874                } else {
13875                    self.write("CAST('");
13876                    self.write(ts);
13877                    self.write("' AS TIMESTAMP)");
13878                }
13879            }
13880            // ClickHouse: CAST('...' AS Nullable(DateTime))
13881            Some(DialectType::ClickHouse) => {
13882                self.write("CAST('");
13883                self.write(ts);
13884                self.write("' AS Nullable(DateTime))");
13885            }
13886            // Spark: CAST('...' AS TIMESTAMP)
13887            Some(DialectType::Spark) => {
13888                self.write("CAST('");
13889                self.write(ts);
13890                self.write("' AS TIMESTAMP)");
13891            }
13892            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
13893            // but TIMESTAMP '...' for special values like 'epoch'
13894            Some(DialectType::Redshift) => {
13895                if ts == "epoch" {
13896                    self.write_keyword("TIMESTAMP");
13897                    self.write(" '");
13898                    self.write(ts);
13899                    self.write("'");
13900                } else {
13901                    self.write("CAST('");
13902                    self.write(ts);
13903                    self.write("' AS TIMESTAMP)");
13904                }
13905            }
13906            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
13907            Some(DialectType::PostgreSQL)
13908            | Some(DialectType::Hive)
13909            | Some(DialectType::SQLite)
13910            | Some(DialectType::DuckDB)
13911            | Some(DialectType::Athena)
13912            | Some(DialectType::Drill)
13913            | Some(DialectType::Teradata) => {
13914                self.write("CAST('");
13915                self.write(ts);
13916                self.write("' AS TIMESTAMP)");
13917            }
13918            // MySQL/StarRocks: CAST('...' AS DATETIME)
13919            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
13920                self.write("CAST('");
13921                self.write(ts);
13922                self.write("' AS DATETIME)");
13923            }
13924            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
13925            Some(DialectType::Databricks) => {
13926                self.write("CAST('");
13927                self.write(ts);
13928                self.write("' AS TIMESTAMP_NTZ)");
13929            }
13930            // Standard SQL: TIMESTAMP '...'
13931            _ => {
13932                self.write_keyword("TIMESTAMP");
13933                self.write(" '");
13934                self.write(ts);
13935                self.write("'");
13936            }
13937        }
13938        Ok(())
13939    }
13940
13941    /// Check if a timestamp string contains a timezone identifier
13942    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
13943    fn timestamp_has_timezone(ts: &str) -> bool {
13944        // Check for common IANA timezone patterns: Continent/City format
13945        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
13946        // Also handles: UTC, GMT, Etc/GMT+0, etc.
13947        let ts_lower = ts.to_lowercase();
13948
13949        // Check for Continent/City pattern (most common)
13950        let continent_prefixes = [
13951            "africa/",
13952            "america/",
13953            "antarctica/",
13954            "arctic/",
13955            "asia/",
13956            "atlantic/",
13957            "australia/",
13958            "europe/",
13959            "indian/",
13960            "pacific/",
13961            "etc/",
13962            "brazil/",
13963            "canada/",
13964            "chile/",
13965            "mexico/",
13966            "us/",
13967        ];
13968
13969        for prefix in &continent_prefixes {
13970            if ts_lower.contains(prefix) {
13971                return true;
13972            }
13973        }
13974
13975        // Check for standalone timezone abbreviations at the end
13976        // These typically appear after the time portion
13977        let tz_abbrevs = [
13978            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
13979            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
13980            " sgt", " aest", " aedt", " acst", " acdt", " awst",
13981        ];
13982
13983        for abbrev in &tz_abbrevs {
13984            if ts_lower.ends_with(abbrev) {
13985                return true;
13986            }
13987        }
13988
13989        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
13990        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
13991        // Look for pattern: space followed by + or - and digits (optionally with :)
13992        let trimmed = ts.trim();
13993        if let Some(last_space) = trimmed.rfind(' ') {
13994            let suffix = &trimmed[last_space + 1..];
13995            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
13996                // Check if rest is numeric (possibly with : for hh:mm format)
13997                let rest = &suffix[1..];
13998                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
13999                    return true;
14000                }
14001            }
14002        }
14003
14004        false
14005    }
14006
14007    /// Generate a DATETIME literal with dialect-specific formatting
14008    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
14009        use crate::dialects::DialectType;
14010
14011        match self.config.dialect {
14012            // BigQuery uses CAST syntax for type literals
14013            // DATETIME 'value' -> CAST('value' AS DATETIME)
14014            Some(DialectType::BigQuery) => {
14015                self.write("CAST('");
14016                self.write(dt);
14017                self.write("' AS DATETIME)");
14018            }
14019            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
14020            Some(DialectType::DuckDB) => {
14021                self.write("CAST('");
14022                self.write(dt);
14023                self.write("' AS TIMESTAMP)");
14024            }
14025            // DATETIME is primarily a BigQuery type
14026            // Output as DATETIME '...' for dialects that support it
14027            _ => {
14028                self.write_keyword("DATETIME");
14029                self.write(" '");
14030                self.write(dt);
14031                self.write("'");
14032            }
14033        }
14034        Ok(())
14035    }
14036
14037    /// Generate a string literal with dialect-specific escaping
14038    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
14039        use crate::dialects::DialectType;
14040
14041        match self.config.dialect {
14042            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
14043            // and backslash escaping for special characters like newlines
14044            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
14045            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
14046                // Hive/Spark use backslash escaping for quotes (\') and special chars
14047                self.write("'");
14048                for c in s.chars() {
14049                    match c {
14050                        '\'' => self.write("\\'"),
14051                        '\\' => self.write("\\\\"),
14052                        '\n' => self.write("\\n"),
14053                        '\r' => self.write("\\r"),
14054                        '\t' => self.write("\\t"),
14055                        '\0' => self.write("\\0"),
14056                        _ => self.output.push(c),
14057                    }
14058                }
14059                self.write("'");
14060            }
14061            Some(DialectType::Drill) => {
14062                // Drill uses SQL-standard quote doubling ('') for quotes,
14063                // but backslash escaping for special characters
14064                self.write("'");
14065                for c in s.chars() {
14066                    match c {
14067                        '\'' => self.write("''"),
14068                        '\\' => self.write("\\\\"),
14069                        '\n' => self.write("\\n"),
14070                        '\r' => self.write("\\r"),
14071                        '\t' => self.write("\\t"),
14072                        '\0' => self.write("\\0"),
14073                        _ => self.output.push(c),
14074                    }
14075                }
14076                self.write("'");
14077            }
14078            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
14079                self.write("'");
14080                for c in s.chars() {
14081                    match c {
14082                        // MySQL uses SQL standard quote doubling
14083                        '\'' => self.write("''"),
14084                        '\\' => self.write("\\\\"),
14085                        '\n' => self.write("\\n"),
14086                        '\r' => self.write("\\r"),
14087                        '\t' => self.write("\\t"),
14088                        // sqlglot writes a literal NUL for this case
14089                        '\0' => self.output.push('\0'),
14090                        _ => self.output.push(c),
14091                    }
14092                }
14093                self.write("'");
14094            }
14095            // BigQuery: Uses backslash escaping
14096            Some(DialectType::BigQuery) => {
14097                self.write("'");
14098                for c in s.chars() {
14099                    match c {
14100                        '\'' => self.write("\\'"),
14101                        '\\' => self.write("\\\\"),
14102                        '\n' => self.write("\\n"),
14103                        '\r' => self.write("\\r"),
14104                        '\t' => self.write("\\t"),
14105                        '\0' => self.write("\\0"),
14106                        '\x07' => self.write("\\a"),
14107                        '\x08' => self.write("\\b"),
14108                        '\x0C' => self.write("\\f"),
14109                        '\x0B' => self.write("\\v"),
14110                        _ => self.output.push(c),
14111                    }
14112                }
14113                self.write("'");
14114            }
14115            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
14116            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
14117            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
14118            Some(DialectType::Athena) => {
14119                if self.athena_hive_context {
14120                    // Hive-style: backslash escaping
14121                    self.write("'");
14122                    for c in s.chars() {
14123                        match c {
14124                            '\'' => self.write("\\'"),
14125                            '\\' => self.write("\\\\"),
14126                            '\n' => self.write("\\n"),
14127                            '\r' => self.write("\\r"),
14128                            '\t' => self.write("\\t"),
14129                            '\0' => self.write("\\0"),
14130                            _ => self.output.push(c),
14131                        }
14132                    }
14133                    self.write("'");
14134                } else {
14135                    // Trino-style: SQL-standard escaping, preserve backslashes
14136                    self.write("'");
14137                    for c in s.chars() {
14138                        match c {
14139                            '\'' => self.write("''"),
14140                            // Preserve backslashes literally (no re-escaping)
14141                            _ => self.output.push(c),
14142                        }
14143                    }
14144                    self.write("'");
14145                }
14146            }
14147            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
14148            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
14149            // becomes string value '\\'), so we should NOT re-escape backslashes.
14150            // We only need to escape single quotes.
14151            Some(DialectType::Snowflake) => {
14152                self.write("'");
14153                for c in s.chars() {
14154                    match c {
14155                        '\'' => self.write("\\'"),
14156                        // Backslashes are already escaped in the tokenized string, don't re-escape
14157                        // Only escape special characters that might not have been escaped
14158                        '\n' => self.write("\\n"),
14159                        '\r' => self.write("\\r"),
14160                        '\t' => self.write("\\t"),
14161                        _ => self.output.push(c),
14162                    }
14163                }
14164                self.write("'");
14165            }
14166            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
14167            Some(DialectType::PostgreSQL) => {
14168                self.write("'");
14169                for c in s.chars() {
14170                    match c {
14171                        '\'' => self.write("''"),
14172                        _ => self.output.push(c),
14173                    }
14174                }
14175                self.write("'");
14176            }
14177            // Redshift: Uses backslash escaping for single quotes
14178            Some(DialectType::Redshift) => {
14179                self.write("'");
14180                for c in s.chars() {
14181                    match c {
14182                        '\'' => self.write("\\'"),
14183                        _ => self.output.push(c),
14184                    }
14185                }
14186                self.write("'");
14187            }
14188            // Oracle: Uses standard double single-quote escaping
14189            Some(DialectType::Oracle) => {
14190                self.write("'");
14191                self.write(&s.replace('\'', "''"));
14192                self.write("'");
14193            }
14194            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
14195            // backslash escaping for backslashes and special characters
14196            Some(DialectType::ClickHouse) => {
14197                self.write("'");
14198                for c in s.chars() {
14199                    match c {
14200                        '\'' => self.write("''"),
14201                        '\\' => self.write("\\\\"),
14202                        '\n' => self.write("\\n"),
14203                        '\r' => self.write("\\r"),
14204                        '\t' => self.write("\\t"),
14205                        '\0' => self.write("\\0"),
14206                        '\x07' => self.write("\\a"),
14207                        '\x08' => self.write("\\b"),
14208                        '\x0C' => self.write("\\f"),
14209                        '\x0B' => self.write("\\v"),
14210                        // Non-printable characters: emit as \xNN hex escapes
14211                        c if c.is_control() || (c as u32) < 0x20 => {
14212                            let byte = c as u32;
14213                            if byte < 256 {
14214                                self.write(&format!("\\x{:02X}", byte));
14215                            } else {
14216                                self.output.push(c);
14217                            }
14218                        }
14219                        _ => self.output.push(c),
14220                    }
14221                }
14222                self.write("'");
14223            }
14224            // Default: SQL standard double single quotes (works for most dialects)
14225            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
14226            _ => {
14227                self.write("'");
14228                self.write(&s.replace('\'', "''"));
14229                self.write("'");
14230            }
14231        }
14232        Ok(())
14233    }
14234
14235    /// Write a byte string with proper escaping for BigQuery-style byte literals
14236    /// Escapes characters as \xNN hex escapes where needed
14237    fn write_escaped_byte_string(&mut self, s: &str) {
14238        for c in s.chars() {
14239            match c {
14240                // Escape single quotes
14241                '\'' => self.write("\\'"),
14242                // Escape backslashes
14243                '\\' => self.write("\\\\"),
14244                // Keep all printable characters (including non-ASCII) as-is
14245                _ if !c.is_control() => self.output.push(c),
14246                // Escape control characters as hex
14247                _ => {
14248                    let byte = c as u32;
14249                    if byte < 256 {
14250                        self.write(&format!("\\x{:02x}", byte));
14251                    } else {
14252                        // For unicode characters, write each UTF-8 byte
14253                        for b in c.to_string().as_bytes() {
14254                            self.write(&format!("\\x{:02x}", b));
14255                        }
14256                    }
14257                }
14258            }
14259        }
14260    }
14261
14262    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
14263        use crate::dialects::DialectType;
14264
14265        // Different dialects have different boolean literal formats
14266        match self.config.dialect {
14267            // SQL Server typically uses 1/0 for boolean literals in many contexts
14268            // However, TRUE/FALSE also works in modern versions
14269            Some(DialectType::TSQL) => {
14270                self.write(if b.value { "1" } else { "0" });
14271            }
14272            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
14273            Some(DialectType::Oracle) => {
14274                self.write(if b.value { "1" } else { "0" });
14275            }
14276            // MySQL accepts TRUE/FALSE as aliases for 1/0
14277            Some(DialectType::MySQL) => {
14278                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
14279            }
14280            // Most other dialects support TRUE/FALSE
14281            _ => {
14282                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
14283            }
14284        }
14285        Ok(())
14286    }
14287
14288    /// Generate an identifier that's used as an alias name
14289    /// This quotes reserved keywords in addition to already-quoted identifiers
14290    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
14291        let name = &id.name;
14292        let quote_style = &self.config.identifier_quote_style;
14293
14294        // For aliases, quote if:
14295        // 1. The identifier was explicitly quoted in the source
14296        // 2. The identifier is a reserved keyword for the current dialect
14297        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
14298
14299        // Normalize identifier if configured
14300        let output_name = if self.config.normalize_identifiers && !id.quoted {
14301            name.to_lowercase()
14302        } else {
14303            name.to_string()
14304        };
14305
14306        if needs_quoting {
14307            // Escape any quote characters within the identifier
14308            let escaped_name = if quote_style.start == quote_style.end {
14309                output_name.replace(
14310                    quote_style.end,
14311                    &format!("{}{}", quote_style.end, quote_style.end),
14312                )
14313            } else {
14314                output_name.replace(
14315                    quote_style.end,
14316                    &format!("{}{}", quote_style.end, quote_style.end),
14317                )
14318            };
14319            self.write(&format!(
14320                "{}{}{}",
14321                quote_style.start, escaped_name, quote_style.end
14322            ));
14323        } else {
14324            self.write(&output_name);
14325        }
14326
14327        // Output trailing comments
14328        for comment in &id.trailing_comments {
14329            self.write(" ");
14330            self.write_formatted_comment(comment);
14331        }
14332        Ok(())
14333    }
14334
14335    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
14336        use crate::dialects::DialectType;
14337
14338        let name = &id.name;
14339
14340        // For Athena, use backticks in Hive context, double quotes in Trino context
14341        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
14342            && self.athena_hive_context
14343        {
14344            &IdentifierQuoteStyle::BACKTICK
14345        } else {
14346            &self.config.identifier_quote_style
14347        };
14348
14349        // Quote if:
14350        // 1. The identifier was explicitly quoted in the source
14351        // 2. The identifier is a reserved keyword for the current dialect
14352        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
14353        // This matches Python sqlglot's identifier_sql behavior
14354        // Also quote identifiers starting with digits if the target dialect doesn't support them
14355        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
14356        let needs_digit_quoting = starts_with_digit
14357            && !self.config.identifiers_can_start_with_digit
14358            && self.config.dialect.is_some();
14359        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
14360            && name.len() > 2
14361            && (name.starts_with("0x") || name.starts_with("0X"))
14362            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
14363        let needs_quoting = id.quoted
14364            || self.is_reserved_keyword(name)
14365            || self.config.always_quote_identifiers
14366            || needs_digit_quoting
14367            || mysql_invalid_hex_identifier;
14368
14369        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
14370        // When quoted, we need to output `name`(16) not `name(16)`
14371        let (base_name, suffix) = if needs_quoting {
14372            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
14373            if let Some(paren_pos) = name.find('(') {
14374                let base = &name[..paren_pos];
14375                let rest = &name[paren_pos..];
14376                // Verify it looks like (digits) or (digits) ASC/DESC
14377                if rest.starts_with('(')
14378                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
14379                {
14380                    // Check if content between parens is all digits
14381                    let close_paren = rest.find(')').unwrap_or(rest.len());
14382                    let inside = &rest[1..close_paren];
14383                    if inside.chars().all(|c| c.is_ascii_digit()) {
14384                        (base.to_string(), rest.to_string())
14385                    } else {
14386                        (name.to_string(), String::new())
14387                    }
14388                } else {
14389                    (name.to_string(), String::new())
14390                }
14391            } else if name.ends_with(" ASC") {
14392                let base = &name[..name.len() - 4];
14393                (base.to_string(), " ASC".to_string())
14394            } else if name.ends_with(" DESC") {
14395                let base = &name[..name.len() - 5];
14396                (base.to_string(), " DESC".to_string())
14397            } else {
14398                (name.to_string(), String::new())
14399            }
14400        } else {
14401            (name.to_string(), String::new())
14402        };
14403
14404        // Normalize identifier if configured, with special handling for Exasol
14405        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
14406        // should be uppercased when not already quoted (to match Python sqlglot behavior)
14407        let output_name = if self.config.normalize_identifiers && !id.quoted {
14408            base_name.to_lowercase()
14409        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
14410            && !id.quoted
14411            && self.is_reserved_keyword(name)
14412        {
14413            // Exasol: uppercase reserved keywords when quoting them
14414            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
14415            base_name.to_uppercase()
14416        } else {
14417            base_name
14418        };
14419
14420        if needs_quoting {
14421            // Escape any quote characters within the identifier
14422            let escaped_name = if quote_style.start == quote_style.end {
14423                // Same start/end char (e.g., " or `) - double the quote char
14424                output_name.replace(
14425                    quote_style.end,
14426                    &format!("{}{}", quote_style.end, quote_style.end),
14427                )
14428            } else {
14429                // Different start/end (e.g., [ and ]) - escape only the end char
14430                output_name.replace(
14431                    quote_style.end,
14432                    &format!("{}{}", quote_style.end, quote_style.end),
14433                )
14434            };
14435            self.write(&format!(
14436                "{}{}{}{}",
14437                quote_style.start, escaped_name, quote_style.end, suffix
14438            ));
14439        } else {
14440            self.write(&output_name);
14441        }
14442
14443        // Output trailing comments
14444        for comment in &id.trailing_comments {
14445            self.write(" ");
14446            self.write_formatted_comment(comment);
14447        }
14448        Ok(())
14449    }
14450
14451    fn generate_column(&mut self, col: &Column) -> Result<()> {
14452        use crate::dialects::DialectType;
14453
14454        if let Some(table) = &col.table {
14455            // Exasol special case: LOCAL as column table prefix should NOT be quoted
14456            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
14457            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
14458            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
14459                && !table.quoted
14460                && table.name.eq_ignore_ascii_case("LOCAL");
14461
14462            if is_exasol_local_prefix {
14463                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
14464                self.write("LOCAL");
14465            } else {
14466                self.generate_identifier(table)?;
14467            }
14468            self.write(".");
14469        }
14470        self.generate_identifier(&col.name)?;
14471        // Oracle-style join marker (+)
14472        // Only output if dialect supports it (Oracle, Exasol)
14473        if col.join_mark && self.config.supports_column_join_marks {
14474            self.write(" (+)");
14475        }
14476        // Output trailing comments
14477        for comment in &col.trailing_comments {
14478            self.write_space();
14479            self.write_formatted_comment(comment);
14480        }
14481        Ok(())
14482    }
14483
14484    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
14485    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
14486    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
14487        use crate::dialects::DialectType;
14488        use crate::expressions::PseudocolumnType;
14489
14490        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
14491        if pc.kind == PseudocolumnType::Sysdate
14492            && !matches!(
14493                self.config.dialect,
14494                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
14495            )
14496        {
14497            self.write_keyword("CURRENT_TIMESTAMP");
14498            // Add () for dialects that expect it
14499            if matches!(
14500                self.config.dialect,
14501                Some(DialectType::MySQL)
14502                    | Some(DialectType::ClickHouse)
14503                    | Some(DialectType::Spark)
14504                    | Some(DialectType::Databricks)
14505                    | Some(DialectType::Hive)
14506            ) {
14507                self.write("()");
14508            }
14509        } else {
14510            self.write(pc.kind.as_str());
14511        }
14512        Ok(())
14513    }
14514
14515    /// Generate CONNECT BY clause (Oracle hierarchical queries)
14516    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
14517        use crate::dialects::DialectType;
14518
14519        // Generate native CONNECT BY for Oracle and Snowflake
14520        // For other dialects, add a comment noting manual conversion needed
14521        let supports_connect_by = matches!(
14522            self.config.dialect,
14523            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
14524        );
14525
14526        if !supports_connect_by && self.config.dialect.is_some() {
14527            // Add comment for unsupported dialects
14528            if self.config.pretty {
14529                self.write_newline();
14530            } else {
14531                self.write_space();
14532            }
14533            self.write("/* CONNECT BY requires manual conversion to recursive CTE */");
14534        }
14535
14536        // Generate START WITH if present (before CONNECT BY)
14537        if let Some(start) = &connect.start {
14538            if self.config.pretty {
14539                self.write_newline();
14540            } else {
14541                self.write_space();
14542            }
14543            self.write_keyword("START WITH");
14544            self.write_space();
14545            self.generate_expression(start)?;
14546        }
14547
14548        // Generate CONNECT BY
14549        if self.config.pretty {
14550            self.write_newline();
14551        } else {
14552            self.write_space();
14553        }
14554        self.write_keyword("CONNECT BY");
14555        if connect.nocycle {
14556            self.write_space();
14557            self.write_keyword("NOCYCLE");
14558        }
14559        self.write_space();
14560        self.generate_expression(&connect.connect)?;
14561
14562        Ok(())
14563    }
14564
14565    /// Generate Connect expression (for Expression::Connect variant)
14566    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
14567        self.generate_connect(connect)
14568    }
14569
14570    /// Generate PRIOR expression
14571    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
14572        self.write_keyword("PRIOR");
14573        self.write_space();
14574        self.generate_expression(&prior.this)?;
14575        Ok(())
14576    }
14577
14578    /// Generate CONNECT_BY_ROOT function
14579    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
14580    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
14581        self.write_keyword("CONNECT_BY_ROOT");
14582        self.write_space();
14583        self.generate_expression(&cbr.this)?;
14584        Ok(())
14585    }
14586
14587    /// Generate MATCH_RECOGNIZE clause
14588    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
14589        use crate::dialects::DialectType;
14590
14591        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
14592        let supports_match_recognize = matches!(
14593            self.config.dialect,
14594            Some(DialectType::Oracle)
14595                | Some(DialectType::Snowflake)
14596                | Some(DialectType::Presto)
14597                | Some(DialectType::Trino)
14598        );
14599
14600        // Generate the source table first
14601        if let Some(source) = &mr.this {
14602            self.generate_expression(source)?;
14603        }
14604
14605        if !supports_match_recognize {
14606            self.write("/* MATCH_RECOGNIZE not supported in this dialect */");
14607            return Ok(());
14608        }
14609
14610        // In pretty mode, MATCH_RECOGNIZE should be on a new line
14611        if self.config.pretty {
14612            self.write_newline();
14613        } else {
14614            self.write_space();
14615        }
14616
14617        self.write_keyword("MATCH_RECOGNIZE");
14618        self.write(" (");
14619
14620        if self.config.pretty {
14621            self.indent_level += 1;
14622        }
14623
14624        let mut needs_separator = false;
14625
14626        // PARTITION BY
14627        if let Some(partition_by) = &mr.partition_by {
14628            if !partition_by.is_empty() {
14629                if self.config.pretty {
14630                    self.write_newline();
14631                    self.write_indent();
14632                }
14633                self.write_keyword("PARTITION BY");
14634                self.write_space();
14635                for (i, expr) in partition_by.iter().enumerate() {
14636                    if i > 0 {
14637                        self.write(", ");
14638                    }
14639                    self.generate_expression(expr)?;
14640                }
14641                needs_separator = true;
14642            }
14643        }
14644
14645        // ORDER BY
14646        if let Some(order_by) = &mr.order_by {
14647            if !order_by.is_empty() {
14648                if needs_separator {
14649                    if self.config.pretty {
14650                        self.write_newline();
14651                        self.write_indent();
14652                    } else {
14653                        self.write_space();
14654                    }
14655                } else if self.config.pretty {
14656                    self.write_newline();
14657                    self.write_indent();
14658                }
14659                self.write_keyword("ORDER BY");
14660                // In pretty mode, put each ORDER BY column on a new indented line
14661                if self.config.pretty {
14662                    self.indent_level += 1;
14663                    for (i, ordered) in order_by.iter().enumerate() {
14664                        if i > 0 {
14665                            self.write(",");
14666                        }
14667                        self.write_newline();
14668                        self.write_indent();
14669                        self.generate_ordered(ordered)?;
14670                    }
14671                    self.indent_level -= 1;
14672                } else {
14673                    self.write_space();
14674                    for (i, ordered) in order_by.iter().enumerate() {
14675                        if i > 0 {
14676                            self.write(", ");
14677                        }
14678                        self.generate_ordered(ordered)?;
14679                    }
14680                }
14681                needs_separator = true;
14682            }
14683        }
14684
14685        // MEASURES
14686        if let Some(measures) = &mr.measures {
14687            if !measures.is_empty() {
14688                if needs_separator {
14689                    if self.config.pretty {
14690                        self.write_newline();
14691                        self.write_indent();
14692                    } else {
14693                        self.write_space();
14694                    }
14695                } else if self.config.pretty {
14696                    self.write_newline();
14697                    self.write_indent();
14698                }
14699                self.write_keyword("MEASURES");
14700                // In pretty mode, put each MEASURE on a new indented line
14701                if self.config.pretty {
14702                    self.indent_level += 1;
14703                    for (i, measure) in measures.iter().enumerate() {
14704                        if i > 0 {
14705                            self.write(",");
14706                        }
14707                        self.write_newline();
14708                        self.write_indent();
14709                        // Handle RUNNING/FINAL prefix
14710                        if let Some(semantics) = &measure.window_frame {
14711                            match semantics {
14712                                MatchRecognizeSemantics::Running => {
14713                                    self.write_keyword("RUNNING");
14714                                    self.write_space();
14715                                }
14716                                MatchRecognizeSemantics::Final => {
14717                                    self.write_keyword("FINAL");
14718                                    self.write_space();
14719                                }
14720                            }
14721                        }
14722                        self.generate_expression(&measure.this)?;
14723                    }
14724                    self.indent_level -= 1;
14725                } else {
14726                    self.write_space();
14727                    for (i, measure) in measures.iter().enumerate() {
14728                        if i > 0 {
14729                            self.write(", ");
14730                        }
14731                        // Handle RUNNING/FINAL prefix
14732                        if let Some(semantics) = &measure.window_frame {
14733                            match semantics {
14734                                MatchRecognizeSemantics::Running => {
14735                                    self.write_keyword("RUNNING");
14736                                    self.write_space();
14737                                }
14738                                MatchRecognizeSemantics::Final => {
14739                                    self.write_keyword("FINAL");
14740                                    self.write_space();
14741                                }
14742                            }
14743                        }
14744                        self.generate_expression(&measure.this)?;
14745                    }
14746                }
14747                needs_separator = true;
14748            }
14749        }
14750
14751        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
14752        if let Some(rows) = &mr.rows {
14753            if needs_separator {
14754                if self.config.pretty {
14755                    self.write_newline();
14756                    self.write_indent();
14757                } else {
14758                    self.write_space();
14759                }
14760            } else if self.config.pretty {
14761                self.write_newline();
14762                self.write_indent();
14763            }
14764            match rows {
14765                MatchRecognizeRows::OneRowPerMatch => {
14766                    self.write_keyword("ONE ROW PER MATCH");
14767                }
14768                MatchRecognizeRows::AllRowsPerMatch => {
14769                    self.write_keyword("ALL ROWS PER MATCH");
14770                }
14771                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
14772                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
14773                }
14774                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
14775                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
14776                }
14777                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
14778                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
14779                }
14780            }
14781            needs_separator = true;
14782        }
14783
14784        // AFTER MATCH SKIP
14785        if let Some(after) = &mr.after {
14786            if needs_separator {
14787                if self.config.pretty {
14788                    self.write_newline();
14789                    self.write_indent();
14790                } else {
14791                    self.write_space();
14792                }
14793            } else if self.config.pretty {
14794                self.write_newline();
14795                self.write_indent();
14796            }
14797            match after {
14798                MatchRecognizeAfter::PastLastRow => {
14799                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
14800                }
14801                MatchRecognizeAfter::ToNextRow => {
14802                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
14803                }
14804                MatchRecognizeAfter::ToFirst(ident) => {
14805                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
14806                    self.write_space();
14807                    self.generate_identifier(ident)?;
14808                }
14809                MatchRecognizeAfter::ToLast(ident) => {
14810                    self.write_keyword("AFTER MATCH SKIP TO LAST");
14811                    self.write_space();
14812                    self.generate_identifier(ident)?;
14813                }
14814            }
14815            needs_separator = true;
14816        }
14817
14818        // PATTERN
14819        if let Some(pattern) = &mr.pattern {
14820            if needs_separator {
14821                if self.config.pretty {
14822                    self.write_newline();
14823                    self.write_indent();
14824                } else {
14825                    self.write_space();
14826                }
14827            } else if self.config.pretty {
14828                self.write_newline();
14829                self.write_indent();
14830            }
14831            self.write_keyword("PATTERN");
14832            self.write_space();
14833            self.write("(");
14834            self.write(pattern);
14835            self.write(")");
14836            needs_separator = true;
14837        }
14838
14839        // DEFINE
14840        if let Some(define) = &mr.define {
14841            if !define.is_empty() {
14842                if needs_separator {
14843                    if self.config.pretty {
14844                        self.write_newline();
14845                        self.write_indent();
14846                    } else {
14847                        self.write_space();
14848                    }
14849                } else if self.config.pretty {
14850                    self.write_newline();
14851                    self.write_indent();
14852                }
14853                self.write_keyword("DEFINE");
14854                // In pretty mode, put each DEFINE on a new indented line
14855                if self.config.pretty {
14856                    self.indent_level += 1;
14857                    for (i, (name, expr)) in define.iter().enumerate() {
14858                        if i > 0 {
14859                            self.write(",");
14860                        }
14861                        self.write_newline();
14862                        self.write_indent();
14863                        self.generate_identifier(name)?;
14864                        self.write(" AS ");
14865                        self.generate_expression(expr)?;
14866                    }
14867                    self.indent_level -= 1;
14868                } else {
14869                    self.write_space();
14870                    for (i, (name, expr)) in define.iter().enumerate() {
14871                        if i > 0 {
14872                            self.write(", ");
14873                        }
14874                        self.generate_identifier(name)?;
14875                        self.write(" AS ");
14876                        self.generate_expression(expr)?;
14877                    }
14878                }
14879            }
14880        }
14881
14882        if self.config.pretty {
14883            self.indent_level -= 1;
14884            self.write_newline();
14885        }
14886        self.write(")");
14887
14888        // Alias - only include AS if it was explicitly present in the input
14889        if let Some(alias) = &mr.alias {
14890            self.write(" ");
14891            if mr.alias_explicit_as {
14892                self.write_keyword("AS");
14893                self.write(" ");
14894            }
14895            self.generate_identifier(alias)?;
14896        }
14897
14898        Ok(())
14899    }
14900
14901    /// Generate a query hint /*+ ... */
14902    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
14903        use crate::dialects::DialectType;
14904
14905        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
14906        let supports_hints = matches!(
14907            self.config.dialect,
14908            None |  // No dialect = preserve everything
14909            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
14910            Some(DialectType::Spark) | Some(DialectType::Hive) |
14911            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
14912        );
14913
14914        if !supports_hints || hint.expressions.is_empty() {
14915            return Ok(());
14916        }
14917
14918        // First, expand raw hint text into individual hint strings
14919        // This handles the case where the parser stored multiple hints as a single raw string
14920        let mut hint_strings: Vec<String> = Vec::new();
14921        for expr in &hint.expressions {
14922            match expr {
14923                HintExpression::Raw(text) => {
14924                    // Parse raw hint text into individual hint function calls
14925                    let parsed = self.parse_raw_hint_text(text);
14926                    hint_strings.extend(parsed);
14927                }
14928                _ => {
14929                    hint_strings.push(self.hint_expression_to_string(expr)?);
14930                }
14931            }
14932        }
14933
14934        // In pretty mode with multiple hints, always use multiline format
14935        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
14936        // always joins with newlines in pretty mode
14937        let use_multiline = self.config.pretty && hint_strings.len() > 1;
14938
14939        if use_multiline {
14940            // Pretty print with each hint on its own line
14941            self.write(" /*+ ");
14942            for (i, hint_str) in hint_strings.iter().enumerate() {
14943                if i > 0 {
14944                    self.write_newline();
14945                    self.write("  "); // 2-space indent within hint block
14946                }
14947                self.write(hint_str);
14948            }
14949            self.write(" */");
14950        } else {
14951            // Single line format
14952            self.write(" /*+ ");
14953            let sep = match self.config.dialect {
14954                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
14955                _ => " ",
14956            };
14957            for (i, hint_str) in hint_strings.iter().enumerate() {
14958                if i > 0 {
14959                    self.write(sep);
14960                }
14961                self.write(hint_str);
14962            }
14963            self.write(" */");
14964        }
14965
14966        Ok(())
14967    }
14968
14969    /// Parse raw hint text into individual hint function calls
14970    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
14971    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
14972    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
14973        let mut results = Vec::new();
14974        let mut chars = text.chars().peekable();
14975        let mut current = String::new();
14976        let mut paren_depth = 0;
14977        let mut has_unparseable_content = false;
14978        let mut position_after_last_function = 0;
14979        let mut char_position = 0;
14980
14981        while let Some(c) = chars.next() {
14982            char_position += c.len_utf8();
14983            match c {
14984                '(' => {
14985                    paren_depth += 1;
14986                    current.push(c);
14987                }
14988                ')' => {
14989                    paren_depth -= 1;
14990                    current.push(c);
14991                    // When we close the outer parenthesis, we've completed a hint function
14992                    if paren_depth == 0 {
14993                        let trimmed = current.trim().to_string();
14994                        if !trimmed.is_empty() {
14995                            // Format this hint for pretty printing if needed
14996                            let formatted = self.format_hint_function(&trimmed);
14997                            results.push(formatted);
14998                        }
14999                        current.clear();
15000                        position_after_last_function = char_position;
15001                    }
15002                }
15003                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
15004                    // Space/comma/whitespace outside parentheses - skip
15005                }
15006                _ if paren_depth == 0 => {
15007                    // Character outside parentheses - accumulate for potential hint name
15008                    current.push(c);
15009                }
15010                _ => {
15011                    current.push(c);
15012                }
15013            }
15014        }
15015
15016        // Check if there's remaining text after the last function call
15017        let remaining_text = text[position_after_last_function..].trim();
15018        if !remaining_text.is_empty() {
15019            // Check if it looks like valid hint function names
15020            // Valid hint identifiers typically are uppercase alphanumeric with underscores
15021            // If we see multiple words without parens, it's likely unparseable
15022            let words: Vec<&str> = remaining_text.split_whitespace().collect();
15023            let looks_like_hint_functions = words.iter().all(|word| {
15024                // A valid hint name followed by opening paren, or a standalone uppercase identifier
15025                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
15026            });
15027
15028            if !looks_like_hint_functions && words.len() > 1 {
15029                has_unparseable_content = true;
15030            }
15031        }
15032
15033        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
15034        if has_unparseable_content {
15035            return vec![text.trim().to_string()];
15036        }
15037
15038        // If we couldn't parse anything, return the original text as a single hint
15039        if results.is_empty() {
15040            results.push(text.trim().to_string());
15041        }
15042
15043        results
15044    }
15045
15046    /// Format a hint function for pretty printing
15047    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
15048    fn format_hint_function(&self, hint: &str) -> String {
15049        if !self.config.pretty {
15050            return hint.to_string();
15051        }
15052
15053        // Try to parse NAME(args) pattern
15054        if let Some(paren_pos) = hint.find('(') {
15055            if hint.ends_with(')') {
15056                let name = &hint[..paren_pos];
15057                let args_str = &hint[paren_pos + 1..hint.len() - 1];
15058
15059                // Parse arguments (space-separated for Oracle hints)
15060                let args: Vec<&str> = args_str.split_whitespace().collect();
15061
15062                // Calculate total width of arguments
15063                let total_args_width: usize =
15064                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
15065
15066                // If too wide, format on multiple lines
15067                if total_args_width > self.config.max_text_width && !args.is_empty() {
15068                    let mut result = format!("{}(\n", name);
15069                    for arg in &args {
15070                        result.push_str("    "); // 4-space indent for args
15071                        result.push_str(arg);
15072                        result.push('\n');
15073                    }
15074                    result.push_str("  )"); // 2-space indent for closing paren
15075                    return result;
15076                }
15077            }
15078        }
15079
15080        hint.to_string()
15081    }
15082
15083    /// Convert a hint expression to a string, handling multiline formatting for long arguments
15084    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
15085        match expr {
15086            HintExpression::Function { name, args } => {
15087                // Generate each argument to a string
15088                let arg_strings: Vec<String> = args
15089                    .iter()
15090                    .map(|arg| {
15091                        let mut gen = Generator::with_config(self.config.clone());
15092                        gen.generate_expression(arg)?;
15093                        Ok(gen.output)
15094                    })
15095                    .collect::<Result<Vec<_>>>()?;
15096
15097                // Oracle hints use space-separated arguments, not comma-separated
15098                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
15099                    + arg_strings.len().saturating_sub(1); // spaces between args
15100
15101                // Check if function args need multiline formatting
15102                // Use too_wide check for argument formatting
15103                let args_multiline =
15104                    self.config.pretty && total_args_width > self.config.max_text_width;
15105
15106                if args_multiline && !arg_strings.is_empty() {
15107                    // Multiline format for long argument lists
15108                    let mut result = format!("{}(\n", name);
15109                    for arg_str in &arg_strings {
15110                        result.push_str("    "); // 4-space indent for args
15111                        result.push_str(arg_str);
15112                        result.push('\n');
15113                    }
15114                    result.push_str("  )"); // 2-space indent for closing paren
15115                    Ok(result)
15116                } else {
15117                    // Single line format with space-separated args (Oracle style)
15118                    let args_str = arg_strings.join(" ");
15119                    Ok(format!("{}({})", name, args_str))
15120                }
15121            }
15122            HintExpression::Identifier(name) => Ok(name.clone()),
15123            HintExpression::Raw(text) => {
15124                // For pretty printing, try to format the raw text
15125                if self.config.pretty {
15126                    Ok(self.format_hint_function(text))
15127                } else {
15128                    Ok(text.clone())
15129                }
15130            }
15131        }
15132    }
15133
15134    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
15135        // PostgreSQL ONLY modifier: prevents scanning child tables
15136        if table.only {
15137            self.write_keyword("ONLY");
15138            self.write_space();
15139        }
15140
15141        // Check for Snowflake IDENTIFIER() function
15142        if let Some(ref identifier_func) = table.identifier_func {
15143            self.generate_expression(identifier_func)?;
15144        } else {
15145            if let Some(catalog) = &table.catalog {
15146                self.generate_identifier(catalog)?;
15147                self.write(".");
15148            }
15149            if let Some(schema) = &table.schema {
15150                self.generate_identifier(schema)?;
15151                self.write(".");
15152            }
15153            self.generate_identifier(&table.name)?;
15154        }
15155
15156        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
15157        if let Some(changes) = &table.changes {
15158            self.write(" ");
15159            self.generate_changes(changes)?;
15160        }
15161
15162        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
15163        if !table.partitions.is_empty() {
15164            self.write_space();
15165            self.write_keyword("PARTITION");
15166            self.write("(");
15167            for (i, partition) in table.partitions.iter().enumerate() {
15168                if i > 0 {
15169                    self.write(", ");
15170                }
15171                self.generate_identifier(partition)?;
15172            }
15173            self.write(")");
15174        }
15175
15176        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
15177        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
15178        if table.changes.is_none() {
15179            if let Some(when) = &table.when {
15180                self.write_space();
15181                self.generate_historical_data(when)?;
15182            }
15183        }
15184
15185        // Output TSQL FOR SYSTEM_TIME temporal clause
15186        if let Some(ref system_time) = table.system_time {
15187            self.write_space();
15188            self.write(system_time);
15189        }
15190
15191        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
15192        if let Some(ref version) = table.version {
15193            self.write_space();
15194            self.generate_version(version)?;
15195        }
15196
15197        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
15198        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
15199        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
15200        let alias_post_tablesample = self.config.alias_post_tablesample;
15201
15202        if alias_post_tablesample {
15203            // TABLESAMPLE before alias (Oracle, Hive, Spark)
15204            self.generate_table_sample_clause(table)?;
15205        }
15206
15207        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
15208        // For SQLite, INDEXED BY hints come after the alias, so skip here
15209        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
15210            && table.hints.iter().any(|h| {
15211                if let Expression::Identifier(id) = h {
15212                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
15213                } else {
15214                    false
15215                }
15216            });
15217        if !table.hints.is_empty() && !is_sqlite_hint {
15218            for hint in &table.hints {
15219                self.write_space();
15220                self.generate_expression(hint)?;
15221            }
15222        }
15223
15224        if let Some(alias) = &table.alias {
15225            self.write_space();
15226            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
15227            // Generic mode and most dialects always use AS for table aliases
15228            let always_use_as = self.config.dialect.is_none()
15229                || matches!(
15230                    self.config.dialect,
15231                    Some(DialectType::Generic)
15232                        | Some(DialectType::PostgreSQL)
15233                        | Some(DialectType::Redshift)
15234                        | Some(DialectType::Snowflake)
15235                        | Some(DialectType::BigQuery)
15236                        | Some(DialectType::Presto)
15237                        | Some(DialectType::Trino)
15238                        | Some(DialectType::TSQL)
15239                        | Some(DialectType::Fabric)
15240                        | Some(DialectType::MySQL)
15241                        | Some(DialectType::Spark)
15242                        | Some(DialectType::Hive)
15243                        | Some(DialectType::SQLite)
15244                        | Some(DialectType::Drill)
15245                );
15246            let is_stage_ref = table.name.name.starts_with('@');
15247            // Oracle never uses AS for table aliases
15248            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
15249            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
15250                self.write_keyword("AS");
15251                self.write_space();
15252            }
15253            self.generate_identifier(alias)?;
15254
15255            // Output column aliases if present: AS t(c1, c2)
15256            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
15257            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
15258                self.write("(");
15259                for (i, col_alias) in table.column_aliases.iter().enumerate() {
15260                    if i > 0 {
15261                        self.write(", ");
15262                    }
15263                    self.generate_identifier(col_alias)?;
15264                }
15265                self.write(")");
15266            }
15267        }
15268
15269        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
15270        if !alias_post_tablesample {
15271            self.generate_table_sample_clause(table)?;
15272        }
15273
15274        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
15275        if is_sqlite_hint {
15276            for hint in &table.hints {
15277                self.write_space();
15278                self.generate_expression(hint)?;
15279            }
15280        }
15281
15282        // ClickHouse FINAL modifier
15283        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
15284            self.write_space();
15285            self.write_keyword("FINAL");
15286        }
15287
15288        // Output trailing comments
15289        for comment in &table.trailing_comments {
15290            self.write_space();
15291            self.write_formatted_comment(comment);
15292        }
15293
15294        Ok(())
15295    }
15296
15297    /// Helper to output TABLESAMPLE clause for a table reference
15298    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
15299        if let Some(ref ts) = table.table_sample {
15300            self.write_space();
15301            if ts.is_using_sample {
15302                self.write_keyword("USING SAMPLE");
15303            } else {
15304                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
15305                self.write_keyword(self.config.tablesample_keywords);
15306            }
15307            self.generate_sample_body(ts)?;
15308            // Seed for table-level sample - use dialect's configured keyword
15309            if let Some(ref seed) = ts.seed {
15310                self.write_space();
15311                self.write_keyword(self.config.tablesample_seed_keyword);
15312                self.write(" (");
15313                self.generate_expression(seed)?;
15314                self.write(")");
15315            }
15316        }
15317        Ok(())
15318    }
15319
15320    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
15321        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
15322        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
15323
15324        if sr.quoted {
15325            self.write("'");
15326        }
15327
15328        self.write(&sr.name);
15329        if let Some(path) = &sr.path {
15330            self.write(path);
15331        }
15332
15333        if sr.quoted {
15334            self.write("'");
15335        }
15336
15337        // Output FILE_FORMAT and PATTERN if present
15338        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
15339        if has_options {
15340            self.write(" (");
15341            let mut first = true;
15342
15343            if let Some(file_format) = &sr.file_format {
15344                if !first {
15345                    self.write(", ");
15346                }
15347                self.write_keyword("FILE_FORMAT");
15348                self.write(" => ");
15349                self.generate_expression(file_format)?;
15350                first = false;
15351            }
15352
15353            if let Some(pattern) = &sr.pattern {
15354                if !first {
15355                    self.write(", ");
15356                }
15357                self.write_keyword("PATTERN");
15358                self.write(" => '");
15359                self.write(pattern);
15360                self.write("'");
15361            }
15362
15363            self.write(")");
15364        }
15365        Ok(())
15366    }
15367
15368    fn generate_star(&mut self, star: &Star) -> Result<()> {
15369        use crate::dialects::DialectType;
15370
15371        if let Some(table) = &star.table {
15372            self.generate_identifier(table)?;
15373            self.write(".");
15374        }
15375        self.write("*");
15376
15377        // Generate EXCLUDE/EXCEPT clause based on dialect
15378        if let Some(except) = &star.except {
15379            if !except.is_empty() {
15380                self.write_space();
15381                // Use dialect-appropriate keyword
15382                match self.config.dialect {
15383                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
15384                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
15385                        self.write_keyword("EXCLUDE")
15386                    }
15387                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
15388                }
15389                self.write(" (");
15390                for (i, col) in except.iter().enumerate() {
15391                    if i > 0 {
15392                        self.write(", ");
15393                    }
15394                    self.generate_identifier(col)?;
15395                }
15396                self.write(")");
15397            }
15398        }
15399
15400        // Generate REPLACE clause
15401        if let Some(replace) = &star.replace {
15402            if !replace.is_empty() {
15403                self.write_space();
15404                self.write_keyword("REPLACE");
15405                self.write(" (");
15406                for (i, alias) in replace.iter().enumerate() {
15407                    if i > 0 {
15408                        self.write(", ");
15409                    }
15410                    self.generate_expression(&alias.this)?;
15411                    self.write_space();
15412                    self.write_keyword("AS");
15413                    self.write_space();
15414                    self.generate_identifier(&alias.alias)?;
15415                }
15416                self.write(")");
15417            }
15418        }
15419
15420        // Generate RENAME clause (Snowflake specific)
15421        if let Some(rename) = &star.rename {
15422            if !rename.is_empty() {
15423                self.write_space();
15424                self.write_keyword("RENAME");
15425                self.write(" (");
15426                for (i, (old_name, new_name)) in rename.iter().enumerate() {
15427                    if i > 0 {
15428                        self.write(", ");
15429                    }
15430                    self.generate_identifier(old_name)?;
15431                    self.write_space();
15432                    self.write_keyword("AS");
15433                    self.write_space();
15434                    self.generate_identifier(new_name)?;
15435                }
15436                self.write(")");
15437            }
15438        }
15439
15440        // Output trailing comments
15441        for comment in &star.trailing_comments {
15442            self.write_space();
15443            self.write_formatted_comment(comment);
15444        }
15445
15446        Ok(())
15447    }
15448
15449    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
15450    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
15451        self.write("{");
15452        match expr {
15453            Expression::Star(star) => {
15454                // Generate the star (table.* or just * with optional EXCLUDE)
15455                self.generate_star(star)?;
15456            }
15457            Expression::ILike(ilike) => {
15458                // {* ILIKE 'pattern'} syntax
15459                self.generate_expression(&ilike.left)?;
15460                self.write_space();
15461                self.write_keyword("ILIKE");
15462                self.write_space();
15463                self.generate_expression(&ilike.right)?;
15464            }
15465            _ => {
15466                self.generate_expression(expr)?;
15467            }
15468        }
15469        self.write("}");
15470        Ok(())
15471    }
15472
15473    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
15474        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
15475        // to avoid duplication (comments are captured as both Column.trailing_comments
15476        // and Alias.pre_alias_comments during parsing)
15477        match &alias.this {
15478            Expression::Column(col) => {
15479                // Generate column without trailing comments - they're in pre_alias_comments
15480                if let Some(table) = &col.table {
15481                    self.generate_identifier(table)?;
15482                    self.write(".");
15483                }
15484                self.generate_identifier(&col.name)?;
15485            }
15486            _ => {
15487                self.generate_expression(&alias.this)?;
15488            }
15489        }
15490
15491        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
15492        // moves pre-alias comments to after the alias. When there are also trailing_comments,
15493        // keep pre-alias comments in their original position (between expression and AS).
15494        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
15495            for comment in &alias.pre_alias_comments {
15496                self.write_space();
15497                self.write_formatted_comment(comment);
15498            }
15499        }
15500
15501        use crate::dialects::DialectType;
15502
15503        // Determine if we should skip AS keyword for table-valued function aliases
15504        // Oracle and some other dialects don't use AS for table aliases
15505        // Note: We specifically use TableFromRows here, NOT Function, because Function
15506        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
15507        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
15508        let is_table_source = matches!(
15509            &alias.this,
15510            Expression::JSONTable(_)
15511                | Expression::XMLTable(_)
15512                | Expression::TableFromRows(_)
15513                | Expression::Unnest(_)
15514                | Expression::MatchRecognize(_)
15515                | Expression::Select(_)
15516                | Expression::Subquery(_)
15517                | Expression::Paren(_)
15518        );
15519        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
15520        let skip_as = is_table_source && dialect_skips_table_alias_as;
15521
15522        self.write_space();
15523        if !skip_as {
15524            self.write_keyword("AS");
15525            self.write_space();
15526        }
15527
15528        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
15529        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
15530
15531        // Check if we have column aliases only (no table alias name)
15532        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
15533            // Generate AS (col1, col2, ...)
15534            self.write("(");
15535            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
15536                if i > 0 {
15537                    self.write(", ");
15538                }
15539                self.generate_alias_identifier(col_alias)?;
15540            }
15541            self.write(")");
15542        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
15543            // Generate AS alias(col1, col2, ...)
15544            self.generate_alias_identifier(&alias.alias)?;
15545            self.write("(");
15546            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
15547                if i > 0 {
15548                    self.write(", ");
15549                }
15550                self.generate_alias_identifier(col_alias)?;
15551            }
15552            self.write(")");
15553        } else {
15554            // Simple alias (or BigQuery without column aliases)
15555            self.generate_alias_identifier(&alias.alias)?;
15556        }
15557
15558        // Output trailing comments (comments after the alias)
15559        for comment in &alias.trailing_comments {
15560            self.write_space();
15561            self.write_formatted_comment(comment);
15562        }
15563
15564        // Output pre-alias comments: when there are no trailing_comments, sqlglot
15565        // moves pre-alias comments to after the alias. When there are trailing_comments,
15566        // the pre-alias comments were already lost (consumed as column trailing comments
15567        // that were then used as pre_alias_comments). We always emit them after alias.
15568        if alias.trailing_comments.is_empty() {
15569            for comment in &alias.pre_alias_comments {
15570                self.write_space();
15571                self.write_formatted_comment(comment);
15572            }
15573        }
15574
15575        Ok(())
15576    }
15577
15578    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
15579        use crate::dialects::DialectType;
15580
15581        // SingleStore uses :> syntax
15582        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
15583            self.generate_expression(&cast.this)?;
15584            self.write(" :> ");
15585            self.generate_data_type(&cast.to)?;
15586            return Ok(());
15587        }
15588
15589        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
15590        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
15591            let is_unknown_type = matches!(cast.to, DataType::Unknown)
15592                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
15593            if is_unknown_type {
15594                if let Some(format) = &cast.format {
15595                    self.write_keyword("CAST");
15596                    self.write("(");
15597                    self.generate_expression(&cast.this)?;
15598                    self.write_space();
15599                    self.write_keyword("AS");
15600                    self.write_space();
15601                    self.write_keyword("FORMAT");
15602                    self.write_space();
15603                    self.generate_expression(format)?;
15604                    self.write(")");
15605                    return Ok(());
15606                }
15607            }
15608        }
15609
15610        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
15611        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
15612        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
15613            if let Some(format) = &cast.format {
15614                // Check if target type is DATE or TIMESTAMP
15615                let is_date = matches!(cast.to, DataType::Date);
15616                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
15617
15618                if is_date || is_timestamp {
15619                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
15620                    self.write_keyword(func_name);
15621                    self.write("(");
15622                    self.generate_expression(&cast.this)?;
15623                    self.write(", ");
15624
15625                    // Normalize format string for Oracle (HH -> HH12)
15626                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
15627                    if let Expression::Literal(Literal::String(fmt_str)) = format.as_ref() {
15628                        let normalized = self.normalize_oracle_format(fmt_str);
15629                        self.write("'");
15630                        self.write(&normalized);
15631                        self.write("'");
15632                    } else {
15633                        self.generate_expression(format)?;
15634                    }
15635
15636                    self.write(")");
15637                    return Ok(());
15638                }
15639            }
15640        }
15641
15642        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
15643        // This preserves sqlglot's typed inline array literal output.
15644        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
15645            if let Expression::Array(arr) = &cast.this {
15646                self.generate_data_type(&cast.to)?;
15647                // Output just the bracket content [values] without the ARRAY prefix
15648                self.write("[");
15649                for (i, expr) in arr.expressions.iter().enumerate() {
15650                    if i > 0 {
15651                        self.write(", ");
15652                    }
15653                    self.generate_expression(expr)?;
15654                }
15655                self.write("]");
15656                return Ok(());
15657            }
15658            if matches!(&cast.this, Expression::ArrayFunc(_)) {
15659                self.generate_data_type(&cast.to)?;
15660                self.generate_expression(&cast.this)?;
15661                return Ok(());
15662            }
15663        }
15664
15665        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
15666        // convert the inner Struct to ROW(values...) format
15667        if matches!(
15668            self.config.dialect,
15669            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
15670        ) {
15671            if let Expression::Struct(ref s) = cast.this {
15672                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
15673                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
15674                    self.write_keyword("CAST");
15675                    self.write("(");
15676                    self.generate_struct_as_row(s)?;
15677                    self.write_space();
15678                    self.write_keyword("AS");
15679                    self.write_space();
15680                    self.generate_data_type(&cast.to)?;
15681                    self.write(")");
15682                    return Ok(());
15683                }
15684            }
15685        }
15686
15687        // Determine if we should use :: syntax based on dialect
15688        // PostgreSQL prefers :: for identity, most others prefer CAST()
15689        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
15690
15691        if use_double_colon {
15692            // PostgreSQL :: syntax: expr::type
15693            self.generate_expression(&cast.this)?;
15694            self.write("::");
15695            self.generate_data_type(&cast.to)?;
15696        } else {
15697            // Standard CAST() syntax
15698            self.write_keyword("CAST");
15699            self.write("(");
15700            self.generate_expression(&cast.this)?;
15701            self.write_space();
15702            self.write_keyword("AS");
15703            self.write_space();
15704            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
15705            // This matches Python sqlglot's CAST_MAPPING behavior
15706            if matches!(
15707                self.config.dialect,
15708                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
15709            ) {
15710                match &cast.to {
15711                    DataType::Custom { ref name } => {
15712                        let upper = name.to_uppercase();
15713                        match upper.as_str() {
15714                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" | "LONGBLOB" | "MEDIUMBLOB"
15715                            | "TINYBLOB" => {
15716                                self.write_keyword("CHAR");
15717                            }
15718                            _ => {
15719                                self.generate_data_type(&cast.to)?;
15720                            }
15721                        }
15722                    }
15723                    DataType::VarChar { length, .. } => {
15724                        // MySQL CAST: VARCHAR -> CHAR
15725                        self.write_keyword("CHAR");
15726                        if let Some(n) = length {
15727                            self.write(&format!("({})", n));
15728                        }
15729                    }
15730                    DataType::Text => {
15731                        // MySQL CAST: TEXT -> CHAR
15732                        self.write_keyword("CHAR");
15733                    }
15734                    DataType::Timestamp {
15735                        precision,
15736                        timezone: false,
15737                    } => {
15738                        // MySQL CAST: TIMESTAMP -> DATETIME
15739                        self.write_keyword("DATETIME");
15740                        if let Some(p) = precision {
15741                            self.write(&format!("({})", p));
15742                        }
15743                    }
15744                    _ => {
15745                        self.generate_data_type(&cast.to)?;
15746                    }
15747                }
15748            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
15749                // Snowflake CAST: STRING -> VARCHAR
15750                match &cast.to {
15751                    DataType::String { length } => {
15752                        self.write_keyword("VARCHAR");
15753                        if let Some(n) = length {
15754                            self.write(&format!("({})", n));
15755                        }
15756                    }
15757                    _ => {
15758                        self.generate_data_type(&cast.to)?;
15759                    }
15760                }
15761            } else {
15762                self.generate_data_type(&cast.to)?;
15763            }
15764
15765            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
15766            if let Some(default) = &cast.default {
15767                self.write_space();
15768                self.write_keyword("DEFAULT");
15769                self.write_space();
15770                self.generate_expression(default)?;
15771                self.write_space();
15772                self.write_keyword("ON");
15773                self.write_space();
15774                self.write_keyword("CONVERSION");
15775                self.write_space();
15776                self.write_keyword("ERROR");
15777            }
15778
15779            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
15780            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
15781            if let Some(format) = &cast.format {
15782                // Check if Oracle dialect - use comma syntax
15783                if matches!(
15784                    self.config.dialect,
15785                    Some(crate::dialects::DialectType::Oracle)
15786                ) {
15787                    self.write(", ");
15788                } else {
15789                    self.write_space();
15790                    self.write_keyword("FORMAT");
15791                    self.write_space();
15792                }
15793                self.generate_expression(format)?;
15794            }
15795
15796            self.write(")");
15797            // Output trailing comments
15798            for comment in &cast.trailing_comments {
15799                self.write_space();
15800                self.write_formatted_comment(comment);
15801            }
15802        }
15803        Ok(())
15804    }
15805
15806    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
15807    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
15808    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
15809        self.write_keyword("ROW");
15810        self.write("(");
15811        for (i, (_, expr)) in s.fields.iter().enumerate() {
15812            if i > 0 {
15813                self.write(", ");
15814            }
15815            // Recursively convert inner Struct to ROW format
15816            if let Expression::Struct(ref inner_s) = expr {
15817                self.generate_struct_as_row(inner_s)?;
15818            } else {
15819                self.generate_expression(expr)?;
15820            }
15821        }
15822        self.write(")");
15823        Ok(())
15824    }
15825
15826    /// Normalize Oracle date/time format strings
15827    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
15828    fn normalize_oracle_format(&self, format: &str) -> String {
15829        // Replace standalone HH with HH12 (but not HH12 or HH24)
15830        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
15831        let mut result = String::new();
15832        let chars: Vec<char> = format.chars().collect();
15833        let mut i = 0;
15834
15835        while i < chars.len() {
15836            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
15837                // Check what follows HH
15838                if i + 2 < chars.len() {
15839                    let next = chars[i + 2];
15840                    if next == '1' || next == '2' {
15841                        // This is HH12 or HH24, keep as is
15842                        result.push('H');
15843                        result.push('H');
15844                        i += 2;
15845                        continue;
15846                    }
15847                }
15848                // Standalone HH -> HH12
15849                result.push_str("HH12");
15850                i += 2;
15851            } else {
15852                result.push(chars[i]);
15853                i += 1;
15854            }
15855        }
15856
15857        result
15858    }
15859
15860    /// Check if the current dialect prefers :: cast syntax
15861    /// Note: Python sqlglot normalizes all :: to CAST() for output, even for PostgreSQL
15862    /// So we return false for all dialects to match Python sqlglot's behavior
15863    fn dialect_prefers_double_colon(&self) -> bool {
15864        // Python sqlglot normalizes :: syntax to CAST() for all dialects
15865        // Even PostgreSQL outputs CAST() not ::
15866        false
15867    }
15868
15869    /// Generate MOD function - uses % operator for Snowflake/MySQL/Presto/Trino, MOD() for others
15870    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
15871        use crate::dialects::DialectType;
15872
15873        // Snowflake, MySQL, Presto, Trino, PostgreSQL, and DuckDB prefer x % y instead of MOD(x, y)
15874        let use_percent_operator = matches!(
15875            self.config.dialect,
15876            Some(DialectType::Snowflake)
15877                | Some(DialectType::MySQL)
15878                | Some(DialectType::Presto)
15879                | Some(DialectType::Trino)
15880                | Some(DialectType::PostgreSQL)
15881                | Some(DialectType::DuckDB)
15882                | Some(DialectType::Hive)
15883                | Some(DialectType::Spark)
15884                | Some(DialectType::Databricks)
15885                | Some(DialectType::Athena)
15886        );
15887
15888        if use_percent_operator {
15889            // Wrap complex expressions in parens to preserve precedence
15890            // Since % has higher precedence than +/-, we need parens for Add/Sub on either side
15891            let needs_paren = |e: &Expression| matches!(e, Expression::Add(_) | Expression::Sub(_));
15892            if needs_paren(&f.this) {
15893                self.write("(");
15894                self.generate_expression(&f.this)?;
15895                self.write(")");
15896            } else {
15897                self.generate_expression(&f.this)?;
15898            }
15899            self.write(" % ");
15900            if needs_paren(&f.expression) {
15901                self.write("(");
15902                self.generate_expression(&f.expression)?;
15903                self.write(")");
15904            } else {
15905                self.generate_expression(&f.expression)?;
15906            }
15907            Ok(())
15908        } else {
15909            self.generate_binary_func("MOD", &f.this, &f.expression)
15910        }
15911    }
15912
15913    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
15914    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
15915        use crate::dialects::DialectType;
15916
15917        // Snowflake normalizes IFNULL to COALESCE
15918        let func_name = match self.config.dialect {
15919            Some(DialectType::Snowflake) => "COALESCE",
15920            _ => "IFNULL",
15921        };
15922
15923        self.generate_binary_func(func_name, &f.this, &f.expression)
15924    }
15925
15926    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
15927    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
15928        // Use original function name if preserved (for identity tests)
15929        if let Some(ref original_name) = f.original_name {
15930            return self.generate_binary_func(original_name, &f.this, &f.expression);
15931        }
15932
15933        // Otherwise, use dialect-specific function names
15934        use crate::dialects::DialectType;
15935        let func_name = match self.config.dialect {
15936            Some(DialectType::Snowflake)
15937            | Some(DialectType::ClickHouse)
15938            | Some(DialectType::PostgreSQL)
15939            | Some(DialectType::Presto)
15940            | Some(DialectType::Trino)
15941            | Some(DialectType::Athena)
15942            | Some(DialectType::DuckDB)
15943            | Some(DialectType::BigQuery)
15944            | Some(DialectType::Spark)
15945            | Some(DialectType::Databricks)
15946            | Some(DialectType::Hive) => "COALESCE",
15947            Some(DialectType::MySQL)
15948            | Some(DialectType::Doris)
15949            | Some(DialectType::StarRocks)
15950            | Some(DialectType::SingleStore)
15951            | Some(DialectType::TiDB) => "IFNULL",
15952            _ => "NVL",
15953        };
15954
15955        self.generate_binary_func(func_name, &f.this, &f.expression)
15956    }
15957
15958    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
15959    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
15960        use crate::dialects::DialectType;
15961
15962        // Snowflake normalizes STDDEV_SAMP to STDDEV
15963        let func_name = match self.config.dialect {
15964            Some(DialectType::Snowflake) => "STDDEV",
15965            _ => "STDDEV_SAMP",
15966        };
15967
15968        self.generate_agg_func(func_name, f)
15969    }
15970
15971    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
15972        self.generate_expression(&coll.this)?;
15973        self.write_space();
15974        self.write_keyword("COLLATE");
15975        self.write_space();
15976        if coll.quoted {
15977            // Single-quoted string: COLLATE 'de_DE'
15978            self.write("'");
15979            self.write(&coll.collation);
15980            self.write("'");
15981        } else if coll.double_quoted {
15982            // Double-quoted identifier: COLLATE "de_DE"
15983            self.write("\"");
15984            self.write(&coll.collation);
15985            self.write("\"");
15986        } else {
15987            // Unquoted identifier: COLLATE de_DE
15988            self.write(&coll.collation);
15989        }
15990        Ok(())
15991    }
15992
15993    fn generate_case(&mut self, case: &Case) -> Result<()> {
15994        // In pretty mode, decide whether to expand based on total text width
15995        let multiline_case = if self.config.pretty {
15996            // Build the flat representation to check width
15997            let mut statements: Vec<String> = Vec::new();
15998            let operand_str = if let Some(operand) = &case.operand {
15999                let s = self.generate_to_string(operand)?;
16000                statements.push(format!("CASE {}", s));
16001                s
16002            } else {
16003                statements.push("CASE".to_string());
16004                String::new()
16005            };
16006            let _ = operand_str;
16007            for (condition, result) in &case.whens {
16008                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
16009                statements.push(format!("THEN {}", self.generate_to_string(result)?));
16010            }
16011            if let Some(else_) = &case.else_ {
16012                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
16013            }
16014            statements.push("END".to_string());
16015            self.too_wide(&statements)
16016        } else {
16017            false
16018        };
16019
16020        self.write_keyword("CASE");
16021        if let Some(operand) = &case.operand {
16022            self.write_space();
16023            self.generate_expression(operand)?;
16024        }
16025        if multiline_case {
16026            self.indent_level += 1;
16027        }
16028        for (condition, result) in &case.whens {
16029            if multiline_case {
16030                self.write_newline();
16031                self.write_indent();
16032            } else {
16033                self.write_space();
16034            }
16035            self.write_keyword("WHEN");
16036            self.write_space();
16037            self.generate_expression(condition)?;
16038            if multiline_case {
16039                self.write_newline();
16040                self.write_indent();
16041            } else {
16042                self.write_space();
16043            }
16044            self.write_keyword("THEN");
16045            self.write_space();
16046            self.generate_expression(result)?;
16047        }
16048        if let Some(else_) = &case.else_ {
16049            if multiline_case {
16050                self.write_newline();
16051                self.write_indent();
16052            } else {
16053                self.write_space();
16054            }
16055            self.write_keyword("ELSE");
16056            self.write_space();
16057            self.generate_expression(else_)?;
16058        }
16059        if multiline_case {
16060            self.indent_level -= 1;
16061            self.write_newline();
16062            self.write_indent();
16063        } else {
16064            self.write_space();
16065        }
16066        self.write_keyword("END");
16067        // Emit any comments that were attached to the CASE keyword
16068        for comment in &case.comments {
16069            self.write(" ");
16070            self.write_formatted_comment(comment);
16071        }
16072        Ok(())
16073    }
16074
16075    fn generate_function(&mut self, func: &Function) -> Result<()> {
16076        // Normalize function name based on dialect settings
16077        let normalized_name = self.normalize_func_name(&func.name);
16078        let upper_name = func.name.to_uppercase();
16079
16080        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
16081        if matches!(self.config.dialect, Some(DialectType::DuckDB))
16082            && upper_name == "ARRAY_CONSTRUCT_COMPACT"
16083        {
16084            self.write("LIST_FILTER(");
16085            self.write("[");
16086            for (i, arg) in func.args.iter().enumerate() {
16087                if i > 0 {
16088                    self.write(", ");
16089                }
16090                self.generate_expression(arg)?;
16091            }
16092            self.write("], _u -> NOT _u IS NULL)");
16093            return Ok(());
16094        }
16095
16096        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
16097        if upper_name == "STRUCT"
16098            && !matches!(
16099                self.config.dialect,
16100                Some(DialectType::BigQuery)
16101                    | Some(DialectType::Spark)
16102                    | Some(DialectType::Databricks)
16103                    | Some(DialectType::Hive)
16104                    | None
16105            )
16106        {
16107            return self.generate_struct_function_cross_dialect(func);
16108        }
16109
16110        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
16111        // This is an internal marker function for ::? JSON path syntax
16112        if upper_name == "__SS_JSON_PATH_QMARK__" && func.args.len() == 2 {
16113            self.generate_expression(&func.args[0])?;
16114            self.write("::?");
16115            // Extract the key from the string literal
16116            if let Expression::Literal(crate::expressions::Literal::String(key)) = &func.args[1] {
16117                self.write(key);
16118            } else {
16119                self.generate_expression(&func.args[1])?;
16120            }
16121            return Ok(());
16122        }
16123
16124        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
16125        if upper_name == "__PG_BITWISE_XOR__" && func.args.len() == 2 {
16126            self.generate_expression(&func.args[0])?;
16127            self.write(" # ");
16128            self.generate_expression(&func.args[1])?;
16129            return Ok(());
16130        }
16131
16132        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
16133        if matches!(
16134            self.config.dialect,
16135            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
16136        ) && upper_name == "TRY"
16137            && func.args.len() == 1
16138        {
16139            self.generate_expression(&func.args[0])?;
16140            return Ok(());
16141        }
16142
16143        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
16144        if self.config.dialect == Some(DialectType::ClickHouse)
16145            && upper_name == "TOSTARTOFDAY"
16146            && func.args.len() == 1
16147        {
16148            self.write("dateTrunc('DAY', ");
16149            self.generate_expression(&func.args[0])?;
16150            self.write(")");
16151            return Ok(());
16152        }
16153
16154        // Redshift: CONCAT(a, b, ...) -> a || b || ...
16155        if self.config.dialect == Some(DialectType::Redshift)
16156            && upper_name == "CONCAT"
16157            && func.args.len() >= 2
16158        {
16159            for (i, arg) in func.args.iter().enumerate() {
16160                if i > 0 {
16161                    self.write(" || ");
16162                }
16163                self.generate_expression(arg)?;
16164            }
16165            return Ok(());
16166        }
16167
16168        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
16169        if self.config.dialect == Some(DialectType::Redshift)
16170            && upper_name == "CONCAT_WS"
16171            && func.args.len() >= 2
16172        {
16173            let sep = &func.args[0];
16174            for (i, arg) in func.args.iter().skip(1).enumerate() {
16175                if i > 0 {
16176                    self.write(" || ");
16177                    self.generate_expression(sep)?;
16178                    self.write(" || ");
16179                }
16180                self.generate_expression(arg)?;
16181            }
16182            return Ok(());
16183        }
16184
16185        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
16186        // Unit should be unquoted uppercase identifier
16187        if self.config.dialect == Some(DialectType::Redshift)
16188            && (upper_name == "DATEDIFF" || upper_name == "DATE_DIFF")
16189            && func.args.len() == 3
16190        {
16191            self.write_keyword("DATEDIFF");
16192            self.write("(");
16193            // First arg is unit - normalize to unquoted uppercase
16194            self.write_redshift_date_part(&func.args[0]);
16195            self.write(", ");
16196            self.generate_expression(&func.args[1])?;
16197            self.write(", ");
16198            self.generate_expression(&func.args[2])?;
16199            self.write(")");
16200            return Ok(());
16201        }
16202
16203        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
16204        // Unit should be unquoted uppercase identifier
16205        if self.config.dialect == Some(DialectType::Redshift)
16206            && (upper_name == "DATEADD" || upper_name == "DATE_ADD")
16207            && func.args.len() == 3
16208        {
16209            self.write_keyword("DATEADD");
16210            self.write("(");
16211            // First arg is unit - normalize to unquoted uppercase
16212            self.write_redshift_date_part(&func.args[0]);
16213            self.write(", ");
16214            self.generate_expression(&func.args[1])?;
16215            self.write(", ");
16216            self.generate_expression(&func.args[2])?;
16217            self.write(")");
16218            return Ok(());
16219        }
16220
16221        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function (dropping args)
16222        if upper_name == "UUID_STRING"
16223            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
16224        {
16225            let func_name = match self.config.dialect {
16226                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
16227                Some(DialectType::BigQuery) => "GENERATE_UUID",
16228                _ => "UUID",
16229            };
16230            self.write_keyword(func_name);
16231            self.write("()");
16232            return Ok(());
16233        }
16234
16235        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
16236        // Unit should be quoted uppercase string
16237        if self.config.dialect == Some(DialectType::Redshift)
16238            && upper_name == "DATE_TRUNC"
16239            && func.args.len() == 2
16240        {
16241            self.write_keyword("DATE_TRUNC");
16242            self.write("(");
16243            // First arg is unit - normalize to quoted uppercase
16244            self.write_redshift_date_part_quoted(&func.args[0]);
16245            self.write(", ");
16246            self.generate_expression(&func.args[1])?;
16247            self.write(")");
16248            return Ok(());
16249        }
16250
16251        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
16252        if matches!(
16253            self.config.dialect,
16254            Some(DialectType::TSQL) | Some(DialectType::Fabric)
16255        ) && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16256            && func.args.len() == 2
16257        {
16258            self.write_keyword("DATEPART");
16259            self.write("(");
16260            self.generate_expression(&func.args[0])?;
16261            self.write(", ");
16262            self.generate_expression(&func.args[1])?;
16263            self.write(")");
16264            return Ok(());
16265        }
16266
16267        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
16268        if matches!(
16269            self.config.dialect,
16270            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
16271        ) && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16272            && func.args.len() == 2
16273        {
16274            self.write_keyword("EXTRACT");
16275            self.write("(");
16276            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
16277            match &func.args[0] {
16278                Expression::Literal(crate::expressions::Literal::String(s)) => {
16279                    self.write(&s.to_lowercase());
16280                }
16281                _ => self.generate_expression(&func.args[0])?,
16282            }
16283            self.write_space();
16284            self.write_keyword("FROM");
16285            self.write_space();
16286            self.generate_expression(&func.args[1])?;
16287            self.write(")");
16288            return Ok(());
16289        }
16290
16291        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
16292        // Also DATE literals in Dremio should be CAST(...AS DATE)
16293        if self.config.dialect == Some(DialectType::Dremio)
16294            && (upper_name == "DATE_PART" || upper_name == "DATEPART")
16295            && func.args.len() == 2
16296        {
16297            self.write_keyword("EXTRACT");
16298            self.write("(");
16299            self.generate_expression(&func.args[0])?;
16300            self.write_space();
16301            self.write_keyword("FROM");
16302            self.write_space();
16303            // For Dremio, DATE literals should become CAST('value' AS DATE)
16304            self.generate_dremio_date_expression(&func.args[1])?;
16305            self.write(")");
16306            return Ok(());
16307        }
16308
16309        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
16310        if self.config.dialect == Some(DialectType::Dremio)
16311            && upper_name == "CURRENT_DATE_UTC"
16312            && func.args.is_empty()
16313        {
16314            self.write_keyword("CURRENT_DATE_UTC");
16315            return Ok(());
16316        }
16317
16318        // Dremio: DATETYPE(year, month, day) transformation
16319        // - If all args are integer literals: DATE('YYYY-MM-DD')
16320        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
16321        if self.config.dialect == Some(DialectType::Dremio)
16322            && upper_name == "DATETYPE"
16323            && func.args.len() == 3
16324        {
16325            // Helper function to extract integer from number literal
16326            fn get_int_literal(expr: &Expression) -> Option<i64> {
16327                if let Expression::Literal(crate::expressions::Literal::Number(s)) = expr {
16328                    s.parse::<i64>().ok()
16329                } else {
16330                    None
16331                }
16332            }
16333
16334            // Check if all arguments are integer literals
16335            if let (Some(year), Some(month), Some(day)) = (
16336                get_int_literal(&func.args[0]),
16337                get_int_literal(&func.args[1]),
16338                get_int_literal(&func.args[2]),
16339            ) {
16340                // All are integer literals: DATE('YYYY-MM-DD')
16341                self.write_keyword("DATE");
16342                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
16343                return Ok(());
16344            }
16345
16346            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
16347            self.write_keyword("CAST");
16348            self.write("(");
16349            self.write_keyword("CONCAT");
16350            self.write("(");
16351            self.generate_expression(&func.args[0])?;
16352            self.write(", '-', ");
16353            self.generate_expression(&func.args[1])?;
16354            self.write(", '-', ");
16355            self.generate_expression(&func.args[2])?;
16356            self.write(")");
16357            self.write_space();
16358            self.write_keyword("AS");
16359            self.write_space();
16360            self.write_keyword("DATE");
16361            self.write(")");
16362            return Ok(());
16363        }
16364
16365        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
16366        // when it's not an integer literal
16367        let is_presto_like = matches!(
16368            self.config.dialect,
16369            Some(DialectType::Presto) | Some(DialectType::Trino)
16370        );
16371        if is_presto_like && upper_name == "DATE_ADD" && func.args.len() == 3 {
16372            self.write_keyword("DATE_ADD");
16373            self.write("(");
16374            // First arg: unit (pass through as-is, e.g., 'DAY')
16375            self.generate_expression(&func.args[0])?;
16376            self.write(", ");
16377            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
16378            let interval = &func.args[1];
16379            let needs_cast = !self.returns_integer_type(interval);
16380            if needs_cast {
16381                self.write_keyword("CAST");
16382                self.write("(");
16383            }
16384            self.generate_expression(interval)?;
16385            if needs_cast {
16386                self.write_space();
16387                self.write_keyword("AS");
16388                self.write_space();
16389                self.write_keyword("BIGINT");
16390                self.write(")");
16391            }
16392            self.write(", ");
16393            // Third arg: date
16394            self.generate_expression(&func.args[2])?;
16395            self.write(")");
16396            return Ok(());
16397        }
16398
16399        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
16400        let use_brackets = func.use_bracket_syntax;
16401
16402        // Special case: functions WITH ORDINALITY need special output order
16403        // Input: FUNC(args) WITH ORDINALITY
16404        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
16405        // Output must be: FUNC(args) WITH ORDINALITY
16406        let has_ordinality = upper_name.ends_with(" WITH ORDINALITY");
16407        let output_name = if has_ordinality {
16408            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
16409            self.normalize_func_name(base_name)
16410        } else {
16411            normalized_name.clone()
16412        };
16413
16414        // For qualified names (schema.function or object.method), preserve original case
16415        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
16416        if func.name.contains('.') && !has_ordinality {
16417            // Don't normalize qualified functions - preserve original case
16418            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
16419            if func.quoted {
16420                self.write("`");
16421                self.write(&func.name);
16422                self.write("`");
16423            } else {
16424                self.write(&func.name);
16425            }
16426        } else {
16427            self.write(&output_name);
16428        }
16429
16430        // If no_parens is true and there are no args, output just the function name
16431        // Unless the target dialect requires parens for this function
16432        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
16433            let needs_parens = match upper_name.as_str() {
16434                "CURRENT_USER" | "SESSION_USER" | "SYSTEM_USER" => matches!(
16435                    self.config.dialect,
16436                    Some(DialectType::Snowflake)
16437                        | Some(DialectType::Spark)
16438                        | Some(DialectType::Databricks)
16439                        | Some(DialectType::Hive)
16440                ),
16441                _ => false,
16442            };
16443            !needs_parens
16444        };
16445        if force_parens {
16446            // Output trailing comments
16447            for comment in &func.trailing_comments {
16448                self.write_space();
16449                self.write_formatted_comment(comment);
16450            }
16451            return Ok(());
16452        }
16453
16454        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
16455        if upper_name == "CUBE" || upper_name == "ROLLUP" || upper_name == "GROUPING SETS" {
16456            self.write(" (");
16457        } else if use_brackets {
16458            self.write("[");
16459        } else {
16460            self.write("(");
16461        }
16462        if func.distinct {
16463            self.write_keyword("DISTINCT");
16464            self.write_space();
16465        }
16466
16467        // Check if arguments should be split onto multiple lines (pretty + too wide)
16468        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
16469            && (upper_name == "TABLE" || upper_name == "FLATTEN");
16470        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
16471        let is_grouping_func =
16472            upper_name == "GROUPING SETS" || upper_name == "CUBE" || upper_name == "ROLLUP";
16473        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
16474            if is_grouping_func {
16475                true
16476            } else {
16477                // Pre-render arguments to check total width
16478                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
16479                for arg in &func.args {
16480                    let mut temp_gen = Generator::with_config(self.config.clone());
16481                    temp_gen.config.pretty = false; // Don't recurse into pretty
16482                    temp_gen.generate_expression(arg)?;
16483                    expr_strings.push(temp_gen.output);
16484                }
16485                self.too_wide(&expr_strings)
16486            }
16487        } else {
16488            false
16489        };
16490
16491        if should_split {
16492            // Split onto multiple lines
16493            self.write_newline();
16494            self.indent_level += 1;
16495            for (i, arg) in func.args.iter().enumerate() {
16496                self.write_indent();
16497                self.generate_expression(arg)?;
16498                if i + 1 < func.args.len() {
16499                    self.write(",");
16500                }
16501                self.write_newline();
16502            }
16503            self.indent_level -= 1;
16504            self.write_indent();
16505        } else {
16506            // All on one line
16507            for (i, arg) in func.args.iter().enumerate() {
16508                if i > 0 {
16509                    self.write(", ");
16510                }
16511                self.generate_expression(arg)?;
16512            }
16513        }
16514
16515        if use_brackets {
16516            self.write("]");
16517        } else {
16518            self.write(")");
16519        }
16520        // Append WITH ORDINALITY after closing paren for table-valued functions
16521        if has_ordinality {
16522            self.write_space();
16523            self.write_keyword("WITH ORDINALITY");
16524        }
16525        // Output trailing comments
16526        for comment in &func.trailing_comments {
16527            self.write_space();
16528            self.write_formatted_comment(comment);
16529        }
16530        Ok(())
16531    }
16532
16533    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
16534        // Normalize function name based on dialect settings
16535        let mut normalized_name = self.normalize_func_name(&func.name);
16536
16537        // Dialect-specific name mappings for aggregate functions
16538        let upper = normalized_name.to_uppercase();
16539        if upper == "MAX_BY" || upper == "MIN_BY" {
16540            let is_max = upper == "MAX_BY";
16541            match self.config.dialect {
16542                Some(DialectType::ClickHouse) => {
16543                    normalized_name = if is_max {
16544                        "argMax".to_string()
16545                    } else {
16546                        "argMin".to_string()
16547                    };
16548                }
16549                Some(DialectType::DuckDB) => {
16550                    normalized_name = if is_max {
16551                        "ARG_MAX".to_string()
16552                    } else {
16553                        "ARG_MIN".to_string()
16554                    };
16555                }
16556                _ => {}
16557            }
16558        }
16559        self.write(&normalized_name);
16560        self.write("(");
16561        if func.distinct {
16562            self.write_keyword("DISTINCT");
16563            self.write_space();
16564        }
16565
16566        // Check if we need to transform multi-arg COUNT DISTINCT
16567        // When dialect doesn't support multi_arg_distinct, transform:
16568        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
16569        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
16570        let needs_multi_arg_transform =
16571            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
16572
16573        if needs_multi_arg_transform {
16574            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
16575            self.write_keyword("CASE");
16576            for arg in &func.args {
16577                self.write_space();
16578                self.write_keyword("WHEN");
16579                self.write_space();
16580                self.generate_expression(arg)?;
16581                self.write_space();
16582                self.write_keyword("IS NULL THEN NULL");
16583            }
16584            self.write_space();
16585            self.write_keyword("ELSE");
16586            self.write(" (");
16587            for (i, arg) in func.args.iter().enumerate() {
16588                if i > 0 {
16589                    self.write(", ");
16590                }
16591                self.generate_expression(arg)?;
16592            }
16593            self.write(")");
16594            self.write_space();
16595            self.write_keyword("END");
16596        } else {
16597            for (i, arg) in func.args.iter().enumerate() {
16598                if i > 0 {
16599                    self.write(", ");
16600                }
16601                self.generate_expression(arg)?;
16602            }
16603        }
16604
16605        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
16606        if self.config.ignore_nulls_in_func
16607            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
16608        {
16609            if let Some(ignore) = func.ignore_nulls {
16610                self.write_space();
16611                if ignore {
16612                    self.write_keyword("IGNORE NULLS");
16613                } else {
16614                    self.write_keyword("RESPECT NULLS");
16615                }
16616            }
16617        }
16618
16619        // ORDER BY inside aggregate
16620        if !func.order_by.is_empty() {
16621            self.write_space();
16622            self.write_keyword("ORDER BY");
16623            self.write_space();
16624            for (i, ord) in func.order_by.iter().enumerate() {
16625                if i > 0 {
16626                    self.write(", ");
16627                }
16628                self.generate_ordered(ord)?;
16629            }
16630        }
16631
16632        // LIMIT inside aggregate
16633        if let Some(limit) = &func.limit {
16634            self.write_space();
16635            self.write_keyword("LIMIT");
16636            self.write_space();
16637            // Check if this is a Tuple representing LIMIT offset, count
16638            if let Expression::Tuple(t) = limit.as_ref() {
16639                if t.expressions.len() == 2 {
16640                    self.generate_expression(&t.expressions[0])?;
16641                    self.write(", ");
16642                    self.generate_expression(&t.expressions[1])?;
16643                } else {
16644                    self.generate_expression(limit)?;
16645                }
16646            } else {
16647                self.generate_expression(limit)?;
16648            }
16649        }
16650
16651        self.write(")");
16652
16653        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
16654        if !self.config.ignore_nulls_in_func
16655            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
16656        {
16657            if let Some(ignore) = func.ignore_nulls {
16658                self.write_space();
16659                if ignore {
16660                    self.write_keyword("IGNORE NULLS");
16661                } else {
16662                    self.write_keyword("RESPECT NULLS");
16663                }
16664            }
16665        }
16666
16667        if let Some(filter) = &func.filter {
16668            self.write_space();
16669            self.write_keyword("FILTER");
16670            self.write("(");
16671            self.write_keyword("WHERE");
16672            self.write_space();
16673            self.generate_expression(filter)?;
16674            self.write(")");
16675        }
16676
16677        Ok(())
16678    }
16679
16680    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
16681        self.generate_expression(&wf.this)?;
16682
16683        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
16684        if let Some(keep) = &wf.keep {
16685            self.write_space();
16686            self.write_keyword("KEEP");
16687            self.write(" (");
16688            self.write_keyword("DENSE_RANK");
16689            self.write_space();
16690            if keep.first {
16691                self.write_keyword("FIRST");
16692            } else {
16693                self.write_keyword("LAST");
16694            }
16695            self.write_space();
16696            self.write_keyword("ORDER BY");
16697            self.write_space();
16698            for (i, ord) in keep.order_by.iter().enumerate() {
16699                if i > 0 {
16700                    self.write(", ");
16701                }
16702                self.generate_ordered(ord)?;
16703            }
16704            self.write(")");
16705        }
16706
16707        // Check if there's any OVER clause content
16708        let has_over = !wf.over.partition_by.is_empty()
16709            || !wf.over.order_by.is_empty()
16710            || wf.over.frame.is_some()
16711            || wf.over.window_name.is_some();
16712
16713        // Only output OVER if there's actual window specification (not just KEEP alone)
16714        if has_over {
16715            self.write_space();
16716            self.write_keyword("OVER");
16717
16718            // Check if this is just a bare named window reference (no parens needed)
16719            let has_specs = !wf.over.partition_by.is_empty()
16720                || !wf.over.order_by.is_empty()
16721                || wf.over.frame.is_some();
16722
16723            if wf.over.window_name.is_some() && !has_specs {
16724                // OVER window_name (without parentheses)
16725                self.write_space();
16726                self.write(&wf.over.window_name.as_ref().unwrap().name);
16727            } else {
16728                // OVER (...) or OVER (window_name ...)
16729                self.write(" (");
16730                self.generate_over(&wf.over)?;
16731                self.write(")");
16732            }
16733        } else if wf.keep.is_none() {
16734            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
16735            self.write_space();
16736            self.write_keyword("OVER");
16737            self.write(" ()");
16738        }
16739
16740        Ok(())
16741    }
16742
16743    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
16744    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
16745        self.generate_expression(&wg.this)?;
16746        self.write_space();
16747        self.write_keyword("WITHIN GROUP");
16748        self.write(" (");
16749        self.write_keyword("ORDER BY");
16750        self.write_space();
16751        for (i, ord) in wg.order_by.iter().enumerate() {
16752            if i > 0 {
16753                self.write(", ");
16754            }
16755            self.generate_ordered(ord)?;
16756        }
16757        self.write(")");
16758        Ok(())
16759    }
16760
16761    /// Generate the contents of an OVER clause (without parentheses)
16762    fn generate_over(&mut self, over: &Over) -> Result<()> {
16763        let mut has_content = false;
16764
16765        // Named window reference
16766        if let Some(name) = &over.window_name {
16767            self.write(&name.name);
16768            has_content = true;
16769        }
16770
16771        // PARTITION BY
16772        if !over.partition_by.is_empty() {
16773            if has_content {
16774                self.write_space();
16775            }
16776            self.write_keyword("PARTITION BY");
16777            self.write_space();
16778            for (i, expr) in over.partition_by.iter().enumerate() {
16779                if i > 0 {
16780                    self.write(", ");
16781                }
16782                self.generate_expression(expr)?;
16783            }
16784            has_content = true;
16785        }
16786
16787        // ORDER BY
16788        if !over.order_by.is_empty() {
16789            if has_content {
16790                self.write_space();
16791            }
16792            self.write_keyword("ORDER BY");
16793            self.write_space();
16794            for (i, ordered) in over.order_by.iter().enumerate() {
16795                if i > 0 {
16796                    self.write(", ");
16797                }
16798                self.generate_ordered(ordered)?;
16799            }
16800            has_content = true;
16801        }
16802
16803        // Window frame
16804        if let Some(frame) = &over.frame {
16805            if has_content {
16806                self.write_space();
16807            }
16808            self.generate_window_frame(frame)?;
16809        }
16810
16811        Ok(())
16812    }
16813
16814    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
16815        // Exasol uses lowercase for frame kind (rows/range/groups)
16816        let lowercase_frame = self.config.lowercase_window_frame_keywords;
16817
16818        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
16819        if !lowercase_frame {
16820            if let Some(kind_text) = &frame.kind_text {
16821                self.write(kind_text);
16822            } else {
16823                match frame.kind {
16824                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
16825                    WindowFrameKind::Range => self.write_keyword("RANGE"),
16826                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
16827                }
16828            }
16829        } else {
16830            match frame.kind {
16831                WindowFrameKind::Rows => self.write("rows"),
16832                WindowFrameKind::Range => self.write("range"),
16833                WindowFrameKind::Groups => self.write("groups"),
16834            }
16835        }
16836
16837        // Use BETWEEN format only when there's an explicit end bound,
16838        // or when normalize_window_frame_between is enabled and the start is a directional bound
16839        self.write_space();
16840        let should_normalize = self.config.normalize_window_frame_between
16841            && frame.end.is_none()
16842            && matches!(
16843                frame.start,
16844                WindowFrameBound::Preceding(_)
16845                    | WindowFrameBound::Following(_)
16846                    | WindowFrameBound::UnboundedPreceding
16847                    | WindowFrameBound::UnboundedFollowing
16848            );
16849
16850        if let Some(end) = &frame.end {
16851            // BETWEEN format: RANGE BETWEEN start AND end
16852            self.write_keyword("BETWEEN");
16853            self.write_space();
16854            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16855            self.write_space();
16856            self.write_keyword("AND");
16857            self.write_space();
16858            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
16859        } else if should_normalize {
16860            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
16861            self.write_keyword("BETWEEN");
16862            self.write_space();
16863            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16864            self.write_space();
16865            self.write_keyword("AND");
16866            self.write_space();
16867            self.write_keyword("CURRENT ROW");
16868        } else {
16869            // Single bound format: RANGE CURRENT ROW
16870            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
16871        }
16872
16873        // EXCLUDE clause
16874        if let Some(exclude) = &frame.exclude {
16875            self.write_space();
16876            self.write_keyword("EXCLUDE");
16877            self.write_space();
16878            match exclude {
16879                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
16880                WindowFrameExclude::Group => self.write_keyword("GROUP"),
16881                WindowFrameExclude::Ties => self.write_keyword("TIES"),
16882                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
16883            }
16884        }
16885
16886        Ok(())
16887    }
16888
16889    fn generate_window_frame_bound(
16890        &mut self,
16891        bound: &WindowFrameBound,
16892        side_text: Option<&str>,
16893    ) -> Result<()> {
16894        // Exasol uses lowercase for preceding/following
16895        let lowercase_frame = self.config.lowercase_window_frame_keywords;
16896
16897        match bound {
16898            WindowFrameBound::CurrentRow => {
16899                self.write_keyword("CURRENT ROW");
16900            }
16901            WindowFrameBound::UnboundedPreceding => {
16902                self.write_keyword("UNBOUNDED");
16903                self.write_space();
16904                if lowercase_frame {
16905                    self.write("preceding");
16906                } else if let Some(text) = side_text {
16907                    self.write(text);
16908                } else {
16909                    self.write_keyword("PRECEDING");
16910                }
16911            }
16912            WindowFrameBound::UnboundedFollowing => {
16913                self.write_keyword("UNBOUNDED");
16914                self.write_space();
16915                if lowercase_frame {
16916                    self.write("following");
16917                } else if let Some(text) = side_text {
16918                    self.write(text);
16919                } else {
16920                    self.write_keyword("FOLLOWING");
16921                }
16922            }
16923            WindowFrameBound::Preceding(expr) => {
16924                self.generate_expression(expr)?;
16925                self.write_space();
16926                if lowercase_frame {
16927                    self.write("preceding");
16928                } else if let Some(text) = side_text {
16929                    self.write(text);
16930                } else {
16931                    self.write_keyword("PRECEDING");
16932                }
16933            }
16934            WindowFrameBound::Following(expr) => {
16935                self.generate_expression(expr)?;
16936                self.write_space();
16937                if lowercase_frame {
16938                    self.write("following");
16939                } else if let Some(text) = side_text {
16940                    self.write(text);
16941                } else {
16942                    self.write_keyword("FOLLOWING");
16943                }
16944            }
16945            WindowFrameBound::BarePreceding => {
16946                if lowercase_frame {
16947                    self.write("preceding");
16948                } else if let Some(text) = side_text {
16949                    self.write(text);
16950                } else {
16951                    self.write_keyword("PRECEDING");
16952                }
16953            }
16954            WindowFrameBound::BareFollowing => {
16955                if lowercase_frame {
16956                    self.write("following");
16957                } else if let Some(text) = side_text {
16958                    self.write(text);
16959                } else {
16960                    self.write_keyword("FOLLOWING");
16961                }
16962            }
16963            WindowFrameBound::Value(expr) => {
16964                // Bare numeric bound without PRECEDING/FOLLOWING
16965                self.generate_expression(expr)?;
16966            }
16967        }
16968        Ok(())
16969    }
16970
16971    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
16972        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
16973        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
16974        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
16975            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
16976            && !matches!(&interval.this, Some(Expression::Literal(_)));
16977
16978        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
16979        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
16980        if self.config.single_string_interval {
16981            if let (
16982                Some(Expression::Literal(Literal::String(ref val))),
16983                Some(IntervalUnitSpec::Simple {
16984                    ref unit,
16985                    ref use_plural,
16986                }),
16987            ) = (&interval.this, &interval.unit)
16988            {
16989                self.write_keyword("INTERVAL");
16990                self.write_space();
16991                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
16992                let unit_str = self.interval_unit_str(unit, effective_plural);
16993                self.write("'");
16994                self.write(val);
16995                self.write(" ");
16996                self.write(&unit_str);
16997                self.write("'");
16998                return Ok(());
16999            }
17000        }
17001
17002        if !skip_interval_keyword {
17003            self.write_keyword("INTERVAL");
17004        }
17005
17006        // Generate value if present
17007        if let Some(ref value) = interval.this {
17008            if !skip_interval_keyword {
17009                self.write_space();
17010            }
17011            // If the value is a complex expression (not a literal/column/function call)
17012            // and there's a unit, wrap it in parentheses
17013            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
17014            let needs_parens = interval.unit.is_some()
17015                && matches!(
17016                    value,
17017                    Expression::Add(_)
17018                        | Expression::Sub(_)
17019                        | Expression::Mul(_)
17020                        | Expression::Div(_)
17021                        | Expression::Mod(_)
17022                        | Expression::BitwiseAnd(_)
17023                        | Expression::BitwiseOr(_)
17024                        | Expression::BitwiseXor(_)
17025                );
17026            if needs_parens {
17027                self.write("(");
17028            }
17029            self.generate_expression(value)?;
17030            if needs_parens {
17031                self.write(")");
17032            }
17033        }
17034
17035        // Generate unit if present
17036        if let Some(ref unit_spec) = interval.unit {
17037            self.write_space();
17038            self.write_interval_unit_spec(unit_spec)?;
17039        }
17040
17041        Ok(())
17042    }
17043
17044    /// Return the string representation of an interval unit
17045    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
17046        match (unit, use_plural) {
17047            (IntervalUnit::Year, false) => "YEAR",
17048            (IntervalUnit::Year, true) => "YEARS",
17049            (IntervalUnit::Quarter, false) => "QUARTER",
17050            (IntervalUnit::Quarter, true) => "QUARTERS",
17051            (IntervalUnit::Month, false) => "MONTH",
17052            (IntervalUnit::Month, true) => "MONTHS",
17053            (IntervalUnit::Week, false) => "WEEK",
17054            (IntervalUnit::Week, true) => "WEEKS",
17055            (IntervalUnit::Day, false) => "DAY",
17056            (IntervalUnit::Day, true) => "DAYS",
17057            (IntervalUnit::Hour, false) => "HOUR",
17058            (IntervalUnit::Hour, true) => "HOURS",
17059            (IntervalUnit::Minute, false) => "MINUTE",
17060            (IntervalUnit::Minute, true) => "MINUTES",
17061            (IntervalUnit::Second, false) => "SECOND",
17062            (IntervalUnit::Second, true) => "SECONDS",
17063            (IntervalUnit::Millisecond, false) => "MILLISECOND",
17064            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
17065            (IntervalUnit::Microsecond, false) => "MICROSECOND",
17066            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
17067            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
17068            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
17069        }
17070    }
17071
17072    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
17073        match unit_spec {
17074            IntervalUnitSpec::Simple { unit, use_plural } => {
17075                // If dialect doesn't allow plural forms, force singular
17076                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
17077                self.write_simple_interval_unit(unit, effective_plural);
17078            }
17079            IntervalUnitSpec::Span(span) => {
17080                self.write_simple_interval_unit(&span.this, false);
17081                self.write_space();
17082                self.write_keyword("TO");
17083                self.write_space();
17084                self.write_simple_interval_unit(&span.expression, false);
17085            }
17086            IntervalUnitSpec::ExprSpan(span) => {
17087                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
17088                self.generate_expression(&span.this)?;
17089                self.write_space();
17090                self.write_keyword("TO");
17091                self.write_space();
17092                self.generate_expression(&span.expression)?;
17093            }
17094            IntervalUnitSpec::Expr(expr) => {
17095                self.generate_expression(expr)?;
17096            }
17097        }
17098        Ok(())
17099    }
17100
17101    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
17102        // Output interval unit, respecting plural preference
17103        match (unit, use_plural) {
17104            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
17105            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
17106            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
17107            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
17108            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
17109            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
17110            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
17111            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
17112            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
17113            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
17114            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
17115            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
17116            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
17117            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
17118            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
17119            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
17120            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
17121            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
17122            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
17123            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
17124            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
17125            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
17126        }
17127    }
17128
17129    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
17130    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
17131    fn write_redshift_date_part(&mut self, expr: &Expression) {
17132        let part_str = self.extract_date_part_string(expr);
17133        if let Some(part) = part_str {
17134            let normalized = self.normalize_date_part(&part);
17135            self.write_keyword(&normalized);
17136        } else {
17137            // If we can't extract a date part string, fall back to generating the expression
17138            let _ = self.generate_expression(expr);
17139        }
17140    }
17141
17142    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
17143    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
17144    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
17145        let part_str = self.extract_date_part_string(expr);
17146        if let Some(part) = part_str {
17147            let normalized = self.normalize_date_part(&part);
17148            self.write("'");
17149            self.write(&normalized);
17150            self.write("'");
17151        } else {
17152            // If we can't extract a date part string, fall back to generating the expression
17153            let _ = self.generate_expression(expr);
17154        }
17155    }
17156
17157    /// Extract date part string from expression (handles string literals and identifiers)
17158    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
17159        match expr {
17160            Expression::Literal(crate::expressions::Literal::String(s)) => Some(s.clone()),
17161            Expression::Identifier(id) => Some(id.name.clone()),
17162            Expression::Column(col) if col.table.is_none() => {
17163                // Simple column reference without table prefix, treat as identifier
17164                Some(col.name.name.clone())
17165            }
17166            _ => None,
17167        }
17168    }
17169
17170    /// Normalize date part to uppercase singular form
17171    /// days -> DAY, months -> MONTH, etc.
17172    fn normalize_date_part(&self, part: &str) -> String {
17173        let lower = part.to_lowercase();
17174        match lower.as_str() {
17175            "day" | "days" | "d" => "DAY".to_string(),
17176            "month" | "months" | "mon" | "mm" => "MONTH".to_string(),
17177            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
17178            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
17179            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
17180            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
17181            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
17182            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
17183            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
17184            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
17185            _ => part.to_uppercase(),
17186        }
17187    }
17188
17189    fn write_datetime_field(&mut self, field: &DateTimeField) {
17190        match field {
17191            DateTimeField::Year => self.write_keyword("YEAR"),
17192            DateTimeField::Month => self.write_keyword("MONTH"),
17193            DateTimeField::Day => self.write_keyword("DAY"),
17194            DateTimeField::Hour => self.write_keyword("HOUR"),
17195            DateTimeField::Minute => self.write_keyword("MINUTE"),
17196            DateTimeField::Second => self.write_keyword("SECOND"),
17197            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
17198            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
17199            DateTimeField::DayOfWeek => {
17200                let name = match self.config.dialect {
17201                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
17202                    _ => "DOW",
17203                };
17204                self.write_keyword(name);
17205            }
17206            DateTimeField::DayOfYear => {
17207                let name = match self.config.dialect {
17208                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
17209                    _ => "DOY",
17210                };
17211                self.write_keyword(name);
17212            }
17213            DateTimeField::Week => self.write_keyword("WEEK"),
17214            DateTimeField::WeekWithModifier(modifier) => {
17215                self.write_keyword("WEEK");
17216                self.write("(");
17217                self.write(modifier);
17218                self.write(")");
17219            }
17220            DateTimeField::Quarter => self.write_keyword("QUARTER"),
17221            DateTimeField::Epoch => self.write_keyword("EPOCH"),
17222            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
17223            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
17224            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
17225            DateTimeField::Date => self.write_keyword("DATE"),
17226            DateTimeField::Time => self.write_keyword("TIME"),
17227            DateTimeField::Custom(name) => self.write(name),
17228        }
17229    }
17230
17231    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
17232    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
17233        match field {
17234            DateTimeField::Year => self.write("year"),
17235            DateTimeField::Month => self.write("month"),
17236            DateTimeField::Day => self.write("day"),
17237            DateTimeField::Hour => self.write("hour"),
17238            DateTimeField::Minute => self.write("minute"),
17239            DateTimeField::Second => self.write("second"),
17240            DateTimeField::Millisecond => self.write("millisecond"),
17241            DateTimeField::Microsecond => self.write("microsecond"),
17242            DateTimeField::DayOfWeek => self.write("dow"),
17243            DateTimeField::DayOfYear => self.write("doy"),
17244            DateTimeField::Week => self.write("week"),
17245            DateTimeField::WeekWithModifier(modifier) => {
17246                self.write("week(");
17247                self.write(modifier);
17248                self.write(")");
17249            }
17250            DateTimeField::Quarter => self.write("quarter"),
17251            DateTimeField::Epoch => self.write("epoch"),
17252            DateTimeField::Timezone => self.write("timezone"),
17253            DateTimeField::TimezoneHour => self.write("timezone_hour"),
17254            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
17255            DateTimeField::Date => self.write("date"),
17256            DateTimeField::Time => self.write("time"),
17257            DateTimeField::Custom(name) => self.write(name),
17258        }
17259    }
17260
17261    // Helper function generators
17262
17263    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
17264        self.write_keyword(name);
17265        self.write("(");
17266        self.generate_expression(arg)?;
17267        self.write(")");
17268        Ok(())
17269    }
17270
17271    /// Generate a unary function, using the original name if available for round-trip preservation
17272    fn generate_unary_func(
17273        &mut self,
17274        default_name: &str,
17275        f: &crate::expressions::UnaryFunc,
17276    ) -> Result<()> {
17277        let name = f.original_name.as_deref().unwrap_or(default_name);
17278        self.write_keyword(name);
17279        self.write("(");
17280        self.generate_expression(&f.this)?;
17281        self.write(")");
17282        Ok(())
17283    }
17284
17285    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
17286    fn generate_sqrt_cbrt(
17287        &mut self,
17288        f: &crate::expressions::UnaryFunc,
17289        func_name: &str,
17290        _op: &str,
17291    ) -> Result<()> {
17292        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
17293        // Always use function syntax for consistency
17294        self.write_keyword(func_name);
17295        self.write("(");
17296        self.generate_expression(&f.this)?;
17297        self.write(")");
17298        Ok(())
17299    }
17300
17301    fn generate_binary_func(
17302        &mut self,
17303        name: &str,
17304        arg1: &Expression,
17305        arg2: &Expression,
17306    ) -> Result<()> {
17307        self.write_keyword(name);
17308        self.write("(");
17309        self.generate_expression(arg1)?;
17310        self.write(", ");
17311        self.generate_expression(arg2)?;
17312        self.write(")");
17313        Ok(())
17314    }
17315
17316    /// Generate CHAR/CHR function with optional USING charset
17317    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
17318    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
17319    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
17320        // Use stored name if available, otherwise default to CHAR
17321        let func_name = f.name.as_deref().unwrap_or("CHAR");
17322        self.write_keyword(func_name);
17323        self.write("(");
17324        for (i, arg) in f.args.iter().enumerate() {
17325            if i > 0 {
17326                self.write(", ");
17327            }
17328            self.generate_expression(arg)?;
17329        }
17330        if let Some(ref charset) = f.charset {
17331            self.write(" ");
17332            self.write_keyword("USING");
17333            self.write(" ");
17334            self.write(charset);
17335        }
17336        self.write(")");
17337        Ok(())
17338    }
17339
17340    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
17341        use crate::dialects::DialectType;
17342
17343        match self.config.dialect {
17344            Some(DialectType::Teradata) => {
17345                // Teradata uses ** operator for exponentiation
17346                self.generate_expression(&f.this)?;
17347                self.write(" ** ");
17348                self.generate_expression(&f.expression)?;
17349                Ok(())
17350            }
17351            _ => {
17352                // Other dialects use POWER function
17353                self.generate_binary_func("POWER", &f.this, &f.expression)
17354            }
17355        }
17356    }
17357
17358    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
17359        self.write_func_name(name);
17360        self.write("(");
17361        for (i, arg) in args.iter().enumerate() {
17362            if i > 0 {
17363                self.write(", ");
17364            }
17365            self.generate_expression(arg)?;
17366        }
17367        self.write(")");
17368        Ok(())
17369    }
17370
17371    // String function generators
17372
17373    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
17374        self.write_keyword("CONCAT_WS");
17375        self.write("(");
17376        self.generate_expression(&f.separator)?;
17377        for expr in &f.expressions {
17378            self.write(", ");
17379            self.generate_expression(expr)?;
17380        }
17381        self.write(")");
17382        Ok(())
17383    }
17384
17385    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
17386        // Oracle uses SUBSTR; most others use SUBSTRING
17387        let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
17388        if is_oracle {
17389            self.write_keyword("SUBSTR");
17390        } else {
17391            self.write_keyword("SUBSTRING");
17392        }
17393        self.write("(");
17394        self.generate_expression(&f.this)?;
17395        // PostgreSQL always uses FROM/FOR syntax
17396        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
17397        // Spark/Hive use comma syntax, not FROM/FOR syntax
17398        let use_comma_syntax = matches!(
17399            self.config.dialect,
17400            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
17401        );
17402        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
17403            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
17404            self.write_space();
17405            self.write_keyword("FROM");
17406            self.write_space();
17407            self.generate_expression(&f.start)?;
17408            if let Some(length) = &f.length {
17409                self.write_space();
17410                self.write_keyword("FOR");
17411                self.write_space();
17412                self.generate_expression(length)?;
17413            }
17414        } else {
17415            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
17416            self.write(", ");
17417            self.generate_expression(&f.start)?;
17418            if let Some(length) = &f.length {
17419                self.write(", ");
17420                self.generate_expression(length)?;
17421            }
17422        }
17423        self.write(")");
17424        Ok(())
17425    }
17426
17427    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
17428        self.write_keyword("OVERLAY");
17429        self.write("(");
17430        self.generate_expression(&f.this)?;
17431        self.write_space();
17432        self.write_keyword("PLACING");
17433        self.write_space();
17434        self.generate_expression(&f.replacement)?;
17435        self.write_space();
17436        self.write_keyword("FROM");
17437        self.write_space();
17438        self.generate_expression(&f.from)?;
17439        if let Some(length) = &f.length {
17440            self.write_space();
17441            self.write_keyword("FOR");
17442            self.write_space();
17443            self.generate_expression(length)?;
17444        }
17445        self.write(")");
17446        Ok(())
17447    }
17448
17449    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
17450        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
17451        // when no characters are specified (PostgreSQL style)
17452        if f.position_explicit && f.characters.is_none() {
17453            match f.position {
17454                TrimPosition::Leading => {
17455                    self.write_keyword("LTRIM");
17456                    self.write("(");
17457                    self.generate_expression(&f.this)?;
17458                    self.write(")");
17459                    return Ok(());
17460                }
17461                TrimPosition::Trailing => {
17462                    self.write_keyword("RTRIM");
17463                    self.write("(");
17464                    self.generate_expression(&f.this)?;
17465                    self.write(")");
17466                    return Ok(());
17467                }
17468                TrimPosition::Both => {
17469                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
17470                    // Fall through to standard TRIM handling
17471                }
17472            }
17473        }
17474
17475        self.write_keyword("TRIM");
17476        self.write("(");
17477        // When BOTH is specified without trim characters, simplify to just TRIM(str)
17478        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
17479        let force_standard = f.characters.is_some()
17480            && !f.sql_standard_syntax
17481            && matches!(
17482                self.config.dialect,
17483                Some(DialectType::Hive)
17484                    | Some(DialectType::Spark)
17485                    | Some(DialectType::Databricks)
17486                    | Some(DialectType::ClickHouse)
17487            );
17488        let use_standard = (f.sql_standard_syntax || force_standard)
17489            && !(f.position_explicit
17490                && f.characters.is_none()
17491                && matches!(f.position, TrimPosition::Both));
17492        if use_standard {
17493            // SQL standard syntax: TRIM(BOTH chars FROM str)
17494            // Only output position if it was explicitly specified
17495            if f.position_explicit {
17496                match f.position {
17497                    TrimPosition::Both => self.write_keyword("BOTH"),
17498                    TrimPosition::Leading => self.write_keyword("LEADING"),
17499                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
17500                }
17501                self.write_space();
17502            }
17503            if let Some(chars) = &f.characters {
17504                self.generate_expression(chars)?;
17505                self.write_space();
17506            }
17507            self.write_keyword("FROM");
17508            self.write_space();
17509            self.generate_expression(&f.this)?;
17510        } else {
17511            // Simple function syntax: TRIM(str) or TRIM(str, chars)
17512            self.generate_expression(&f.this)?;
17513            if let Some(chars) = &f.characters {
17514                self.write(", ");
17515                self.generate_expression(chars)?;
17516            }
17517        }
17518        self.write(")");
17519        Ok(())
17520    }
17521
17522    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
17523        self.write_keyword("REPLACE");
17524        self.write("(");
17525        self.generate_expression(&f.this)?;
17526        self.write(", ");
17527        self.generate_expression(&f.old)?;
17528        self.write(", ");
17529        self.generate_expression(&f.new)?;
17530        self.write(")");
17531        Ok(())
17532    }
17533
17534    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
17535        self.write_keyword(name);
17536        self.write("(");
17537        self.generate_expression(&f.this)?;
17538        self.write(", ");
17539        self.generate_expression(&f.length)?;
17540        self.write(")");
17541        Ok(())
17542    }
17543
17544    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
17545        self.write_keyword("REPEAT");
17546        self.write("(");
17547        self.generate_expression(&f.this)?;
17548        self.write(", ");
17549        self.generate_expression(&f.times)?;
17550        self.write(")");
17551        Ok(())
17552    }
17553
17554    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
17555        self.write_keyword(name);
17556        self.write("(");
17557        self.generate_expression(&f.this)?;
17558        self.write(", ");
17559        self.generate_expression(&f.length)?;
17560        if let Some(fill) = &f.fill {
17561            self.write(", ");
17562            self.generate_expression(fill)?;
17563        }
17564        self.write(")");
17565        Ok(())
17566    }
17567
17568    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
17569        self.write_keyword("SPLIT");
17570        self.write("(");
17571        self.generate_expression(&f.this)?;
17572        self.write(", ");
17573        self.generate_expression(&f.delimiter)?;
17574        self.write(")");
17575        Ok(())
17576    }
17577
17578    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
17579        use crate::dialects::DialectType;
17580        // PostgreSQL uses ~ operator for regex matching
17581        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
17582            self.generate_expression(&f.this)?;
17583            self.write(" ~ ");
17584            self.generate_expression(&f.pattern)?;
17585        } else if matches!(
17586            self.config.dialect,
17587            Some(DialectType::SingleStore)
17588                | Some(DialectType::Spark)
17589                | Some(DialectType::Hive)
17590                | Some(DialectType::Databricks)
17591        ) && f.flags.is_none()
17592        {
17593            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
17594            self.generate_expression(&f.this)?;
17595            self.write_keyword(" RLIKE ");
17596            self.generate_expression(&f.pattern)?;
17597        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
17598            // StarRocks uses REGEXP function syntax
17599            self.write_keyword("REGEXP");
17600            self.write("(");
17601            self.generate_expression(&f.this)?;
17602            self.write(", ");
17603            self.generate_expression(&f.pattern)?;
17604            if let Some(flags) = &f.flags {
17605                self.write(", ");
17606                self.generate_expression(flags)?;
17607            }
17608            self.write(")");
17609        } else {
17610            self.write_keyword("REGEXP_LIKE");
17611            self.write("(");
17612            self.generate_expression(&f.this)?;
17613            self.write(", ");
17614            self.generate_expression(&f.pattern)?;
17615            if let Some(flags) = &f.flags {
17616                self.write(", ");
17617                self.generate_expression(flags)?;
17618            }
17619            self.write(")");
17620        }
17621        Ok(())
17622    }
17623
17624    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
17625        self.write_keyword("REGEXP_REPLACE");
17626        self.write("(");
17627        self.generate_expression(&f.this)?;
17628        self.write(", ");
17629        self.generate_expression(&f.pattern)?;
17630        self.write(", ");
17631        self.generate_expression(&f.replacement)?;
17632        if let Some(flags) = &f.flags {
17633            self.write(", ");
17634            self.generate_expression(flags)?;
17635        }
17636        self.write(")");
17637        Ok(())
17638    }
17639
17640    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
17641        self.write_keyword("REGEXP_EXTRACT");
17642        self.write("(");
17643        self.generate_expression(&f.this)?;
17644        self.write(", ");
17645        self.generate_expression(&f.pattern)?;
17646        if let Some(group) = &f.group {
17647            self.write(", ");
17648            self.generate_expression(group)?;
17649        }
17650        self.write(")");
17651        Ok(())
17652    }
17653
17654    // Math function generators
17655
17656    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
17657        self.write_keyword("ROUND");
17658        self.write("(");
17659        self.generate_expression(&f.this)?;
17660        if let Some(decimals) = &f.decimals {
17661            self.write(", ");
17662            self.generate_expression(decimals)?;
17663        }
17664        self.write(")");
17665        Ok(())
17666    }
17667
17668    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
17669        self.write_keyword("FLOOR");
17670        self.write("(");
17671        self.generate_expression(&f.this)?;
17672        // Handle Druid-style FLOOR(time TO unit) syntax
17673        if let Some(to) = &f.to {
17674            self.write(" ");
17675            self.write_keyword("TO");
17676            self.write(" ");
17677            self.generate_expression(to)?;
17678        } else if let Some(scale) = &f.scale {
17679            self.write(", ");
17680            self.generate_expression(scale)?;
17681        }
17682        self.write(")");
17683        Ok(())
17684    }
17685
17686    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
17687        self.write_keyword("CEIL");
17688        self.write("(");
17689        self.generate_expression(&f.this)?;
17690        // Handle Druid-style CEIL(time TO unit) syntax
17691        if let Some(to) = &f.to {
17692            self.write(" ");
17693            self.write_keyword("TO");
17694            self.write(" ");
17695            self.generate_expression(to)?;
17696        } else if let Some(decimals) = &f.decimals {
17697            self.write(", ");
17698            self.generate_expression(decimals)?;
17699        }
17700        self.write(")");
17701        Ok(())
17702    }
17703
17704    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
17705        use crate::expressions::Literal;
17706
17707        if let Some(base) = &f.base {
17708            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
17709            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
17710            if self.is_log_base_none() {
17711                if matches!(base, Expression::Literal(Literal::Number(s)) if s == "2") {
17712                    self.write_func_name("LOG2");
17713                    self.write("(");
17714                    self.generate_expression(&f.this)?;
17715                    self.write(")");
17716                    return Ok(());
17717                } else if matches!(base, Expression::Literal(Literal::Number(s)) if s == "10") {
17718                    self.write_func_name("LOG10");
17719                    self.write("(");
17720                    self.generate_expression(&f.this)?;
17721                    self.write(")");
17722                    return Ok(());
17723                }
17724                // Other bases: fall through to LOG(base, value) — best effort
17725            }
17726
17727            self.write_func_name("LOG");
17728            self.write("(");
17729            if self.is_log_value_first() {
17730                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
17731                self.generate_expression(&f.this)?;
17732                self.write(", ");
17733                self.generate_expression(base)?;
17734            } else {
17735                // Default (PostgreSQL, etc.): LOG(base, value)
17736                self.generate_expression(base)?;
17737                self.write(", ");
17738                self.generate_expression(&f.this)?;
17739            }
17740            self.write(")");
17741        } else {
17742            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
17743            self.write_func_name("LOG");
17744            self.write("(");
17745            self.generate_expression(&f.this)?;
17746            self.write(")");
17747        }
17748        Ok(())
17749    }
17750
17751    /// Whether the target dialect uses LOG(value, base) order (value first).
17752    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
17753    fn is_log_value_first(&self) -> bool {
17754        use crate::dialects::DialectType;
17755        matches!(
17756            self.config.dialect,
17757            Some(DialectType::BigQuery)
17758                | Some(DialectType::TSQL)
17759                | Some(DialectType::Tableau)
17760                | Some(DialectType::Fabric)
17761        )
17762    }
17763
17764    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
17765    /// Presto, Trino, ClickHouse, Athena.
17766    fn is_log_base_none(&self) -> bool {
17767        use crate::dialects::DialectType;
17768        matches!(
17769            self.config.dialect,
17770            Some(DialectType::Presto)
17771                | Some(DialectType::Trino)
17772                | Some(DialectType::ClickHouse)
17773                | Some(DialectType::Athena)
17774        )
17775    }
17776
17777    // Date/time function generators
17778
17779    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
17780        self.write_keyword("CURRENT_TIME");
17781        if let Some(precision) = f.precision {
17782            self.write(&format!("({})", precision));
17783        }
17784        Ok(())
17785    }
17786
17787    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
17788        use crate::dialects::DialectType;
17789
17790        // Oracle/Redshift SYSDATE handling
17791        if f.sysdate {
17792            match self.config.dialect {
17793                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
17794                    self.write_keyword("SYSDATE");
17795                    return Ok(());
17796                }
17797                Some(DialectType::Snowflake) => {
17798                    // Snowflake uses SYSDATE() function
17799                    self.write_keyword("SYSDATE");
17800                    self.write("()");
17801                    return Ok(());
17802                }
17803                _ => {
17804                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
17805                }
17806            }
17807        }
17808
17809        self.write_keyword("CURRENT_TIMESTAMP");
17810        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
17811        if let Some(precision) = f.precision {
17812            self.write(&format!("({})", precision));
17813        } else if matches!(
17814            self.config.dialect,
17815            Some(crate::dialects::DialectType::MySQL)
17816                | Some(crate::dialects::DialectType::SingleStore)
17817                | Some(crate::dialects::DialectType::TiDB)
17818                | Some(crate::dialects::DialectType::Spark)
17819                | Some(crate::dialects::DialectType::Hive)
17820                | Some(crate::dialects::DialectType::Databricks)
17821                | Some(crate::dialects::DialectType::ClickHouse)
17822                | Some(crate::dialects::DialectType::BigQuery)
17823                | Some(crate::dialects::DialectType::Snowflake)
17824        ) {
17825            self.write("()");
17826        }
17827        Ok(())
17828    }
17829
17830    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
17831        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
17832        if self.config.dialect == Some(DialectType::Exasol) {
17833            self.write_keyword("CONVERT_TZ");
17834            self.write("(");
17835            self.generate_expression(&f.this)?;
17836            self.write(", 'UTC', ");
17837            self.generate_expression(&f.zone)?;
17838            self.write(")");
17839            return Ok(());
17840        }
17841
17842        self.generate_expression(&f.this)?;
17843        self.write_space();
17844        self.write_keyword("AT TIME ZONE");
17845        self.write_space();
17846        self.generate_expression(&f.zone)?;
17847        Ok(())
17848    }
17849
17850    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
17851        use crate::dialects::DialectType;
17852
17853        // Presto/Trino use DATE_ADD('unit', interval, date) format
17854        // with the interval cast to BIGINT when needed
17855        let is_presto_like = matches!(
17856            self.config.dialect,
17857            Some(DialectType::Presto) | Some(DialectType::Trino)
17858        );
17859
17860        if is_presto_like {
17861            self.write_keyword(name);
17862            self.write("(");
17863            // Unit as string literal
17864            self.write("'");
17865            self.write_simple_interval_unit(&f.unit, false);
17866            self.write("'");
17867            self.write(", ");
17868            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
17869            let needs_cast = !self.returns_integer_type(&f.interval);
17870            if needs_cast {
17871                self.write_keyword("CAST");
17872                self.write("(");
17873            }
17874            self.generate_expression(&f.interval)?;
17875            if needs_cast {
17876                self.write_space();
17877                self.write_keyword("AS");
17878                self.write_space();
17879                self.write_keyword("BIGINT");
17880                self.write(")");
17881            }
17882            self.write(", ");
17883            self.generate_expression(&f.this)?;
17884            self.write(")");
17885        } else {
17886            self.write_keyword(name);
17887            self.write("(");
17888            self.generate_expression(&f.this)?;
17889            self.write(", ");
17890            self.write_keyword("INTERVAL");
17891            self.write_space();
17892            self.generate_expression(&f.interval)?;
17893            self.write_space();
17894            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
17895            self.write(")");
17896        }
17897        Ok(())
17898    }
17899
17900    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
17901    /// This is a heuristic to avoid full type inference
17902    fn returns_integer_type(&self, expr: &Expression) -> bool {
17903        use crate::expressions::{DataType, Literal};
17904        match expr {
17905            // Integer literals (no decimal point)
17906            Expression::Literal(Literal::Number(n)) => !n.contains('.'),
17907
17908            // FLOOR(x) returns integer if x is integer
17909            Expression::Floor(f) => self.returns_integer_type(&f.this),
17910
17911            // ROUND(x) returns integer if x is integer
17912            Expression::Round(f) => {
17913                // Only if no decimals arg or it's returning an integer
17914                f.decimals.is_none() && self.returns_integer_type(&f.this)
17915            }
17916
17917            // SIGN returns integer if input is integer
17918            Expression::Sign(f) => self.returns_integer_type(&f.this),
17919
17920            // ABS returns the same type as input
17921            Expression::Abs(f) => self.returns_integer_type(&f.this),
17922
17923            // Arithmetic operations on integers return integers
17924            Expression::Mul(op) => {
17925                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
17926            }
17927            Expression::Add(op) => {
17928                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
17929            }
17930            Expression::Sub(op) => {
17931                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
17932            }
17933            Expression::Mod(op) => self.returns_integer_type(&op.left),
17934
17935            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
17936            Expression::Cast(c) => matches!(
17937                &c.to,
17938                DataType::BigInt { .. }
17939                    | DataType::Int { .. }
17940                    | DataType::SmallInt { .. }
17941                    | DataType::TinyInt { .. }
17942            ),
17943
17944            // Negation: -x returns integer if x is integer
17945            Expression::Neg(op) => self.returns_integer_type(&op.this),
17946
17947            // Parenthesized expression
17948            Expression::Paren(p) => self.returns_integer_type(&p.this),
17949
17950            // Column references and most expressions are assumed to need casting
17951            // since we don't have full type information
17952            _ => false,
17953        }
17954    }
17955
17956    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
17957        self.write_keyword("DATEDIFF");
17958        self.write("(");
17959        if let Some(unit) = &f.unit {
17960            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
17961            self.write(", ");
17962        }
17963        self.generate_expression(&f.this)?;
17964        self.write(", ");
17965        self.generate_expression(&f.expression)?;
17966        self.write(")");
17967        Ok(())
17968    }
17969
17970    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
17971        self.write_keyword("DATE_TRUNC");
17972        self.write("('");
17973        self.write_datetime_field(&f.unit);
17974        self.write("', ");
17975        self.generate_expression(&f.this)?;
17976        self.write(")");
17977        Ok(())
17978    }
17979
17980    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
17981        use crate::dialects::DialectType;
17982        use crate::expressions::DateTimeField;
17983
17984        self.write_keyword("LAST_DAY");
17985        self.write("(");
17986        self.generate_expression(&f.this)?;
17987        if let Some(unit) = &f.unit {
17988            self.write(", ");
17989            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
17990            // WEEK(SUNDAY) -> WEEK
17991            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
17992                if let DateTimeField::WeekWithModifier(_) = unit {
17993                    self.write_keyword("WEEK");
17994                } else {
17995                    self.write_datetime_field(unit);
17996                }
17997            } else {
17998                self.write_datetime_field(unit);
17999            }
18000        }
18001        self.write(")");
18002        Ok(())
18003    }
18004
18005    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
18006        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
18007        if matches!(
18008            self.config.dialect,
18009            Some(DialectType::TSQL) | Some(DialectType::Fabric)
18010        ) {
18011            self.write_keyword("DATEPART");
18012            self.write("(");
18013            self.write_datetime_field(&f.field);
18014            self.write(", ");
18015            self.generate_expression(&f.this)?;
18016            self.write(")");
18017            return Ok(());
18018        }
18019        self.write_keyword("EXTRACT");
18020        self.write("(");
18021        // Hive/Spark use lowercase datetime fields in EXTRACT
18022        if matches!(
18023            self.config.dialect,
18024            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
18025        ) {
18026            self.write_datetime_field_lower(&f.field);
18027        } else {
18028            self.write_datetime_field(&f.field);
18029        }
18030        self.write_space();
18031        self.write_keyword("FROM");
18032        self.write_space();
18033        self.generate_expression(&f.this)?;
18034        self.write(")");
18035        Ok(())
18036    }
18037
18038    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
18039        self.write_keyword("TO_DATE");
18040        self.write("(");
18041        self.generate_expression(&f.this)?;
18042        if let Some(format) = &f.format {
18043            self.write(", ");
18044            self.generate_expression(format)?;
18045        }
18046        self.write(")");
18047        Ok(())
18048    }
18049
18050    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
18051        self.write_keyword("TO_TIMESTAMP");
18052        self.write("(");
18053        self.generate_expression(&f.this)?;
18054        if let Some(format) = &f.format {
18055            self.write(", ");
18056            self.generate_expression(format)?;
18057        }
18058        self.write(")");
18059        Ok(())
18060    }
18061
18062    // Control flow function generators
18063
18064    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
18065        use crate::dialects::DialectType;
18066
18067        // Generic mode: normalize IF to CASE WHEN
18068        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
18069            self.write_keyword("CASE WHEN");
18070            self.write_space();
18071            self.generate_expression(&f.condition)?;
18072            self.write_space();
18073            self.write_keyword("THEN");
18074            self.write_space();
18075            self.generate_expression(&f.true_value)?;
18076            if let Some(false_val) = &f.false_value {
18077                self.write_space();
18078                self.write_keyword("ELSE");
18079                self.write_space();
18080                self.generate_expression(false_val)?;
18081            }
18082            self.write_space();
18083            self.write_keyword("END");
18084            return Ok(());
18085        }
18086
18087        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
18088        if self.config.dialect == Some(DialectType::Exasol) {
18089            self.write_keyword("IF");
18090            self.write_space();
18091            self.generate_expression(&f.condition)?;
18092            self.write_space();
18093            self.write_keyword("THEN");
18094            self.write_space();
18095            self.generate_expression(&f.true_value)?;
18096            if let Some(false_val) = &f.false_value {
18097                self.write_space();
18098                self.write_keyword("ELSE");
18099                self.write_space();
18100                self.generate_expression(false_val)?;
18101            }
18102            self.write_space();
18103            self.write_keyword("ENDIF");
18104            return Ok(());
18105        }
18106
18107        // Choose function name based on target dialect
18108        let func_name = match self.config.dialect {
18109            Some(DialectType::Snowflake) => "IFF",
18110            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
18111            Some(DialectType::Drill) => "`IF`",
18112            _ => "IF",
18113        };
18114        self.write(func_name);
18115        self.write("(");
18116        self.generate_expression(&f.condition)?;
18117        self.write(", ");
18118        self.generate_expression(&f.true_value)?;
18119        if let Some(false_val) = &f.false_value {
18120            self.write(", ");
18121            self.generate_expression(false_val)?;
18122        }
18123        self.write(")");
18124        Ok(())
18125    }
18126
18127    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
18128        self.write_keyword("NVL2");
18129        self.write("(");
18130        self.generate_expression(&f.this)?;
18131        self.write(", ");
18132        self.generate_expression(&f.true_value)?;
18133        self.write(", ");
18134        self.generate_expression(&f.false_value)?;
18135        self.write(")");
18136        Ok(())
18137    }
18138
18139    // Typed aggregate function generators
18140
18141    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
18142        // Use normalize_functions for COUNT to respect ClickHouse case preservation
18143        let count_name = match self.config.normalize_functions {
18144            NormalizeFunctions::Upper => "COUNT".to_string(),
18145            NormalizeFunctions::Lower => "count".to_string(),
18146            NormalizeFunctions::None => f
18147                .original_name
18148                .clone()
18149                .unwrap_or_else(|| "COUNT".to_string()),
18150        };
18151        self.write(&count_name);
18152        self.write("(");
18153        if f.distinct {
18154            self.write_keyword("DISTINCT");
18155            self.write_space();
18156        }
18157        if f.star {
18158            self.write("*");
18159        } else if let Some(ref expr) = f.this {
18160            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
18161            if let Expression::Tuple(tuple) = expr {
18162                // Check if we need to transform multi-arg COUNT DISTINCT
18163                // When dialect doesn't support multi_arg_distinct, transform:
18164                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
18165                let needs_transform =
18166                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
18167
18168                if needs_transform {
18169                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
18170                    self.write_keyword("CASE");
18171                    for e in &tuple.expressions {
18172                        self.write_space();
18173                        self.write_keyword("WHEN");
18174                        self.write_space();
18175                        self.generate_expression(e)?;
18176                        self.write_space();
18177                        self.write_keyword("IS NULL THEN NULL");
18178                    }
18179                    self.write_space();
18180                    self.write_keyword("ELSE");
18181                    self.write(" (");
18182                    for (i, e) in tuple.expressions.iter().enumerate() {
18183                        if i > 0 {
18184                            self.write(", ");
18185                        }
18186                        self.generate_expression(e)?;
18187                    }
18188                    self.write(")");
18189                    self.write_space();
18190                    self.write_keyword("END");
18191                } else {
18192                    for (i, e) in tuple.expressions.iter().enumerate() {
18193                        if i > 0 {
18194                            self.write(", ");
18195                        }
18196                        self.generate_expression(e)?;
18197                    }
18198                }
18199            } else {
18200                self.generate_expression(expr)?;
18201            }
18202        }
18203        // RESPECT NULLS / IGNORE NULLS
18204        if let Some(ignore) = f.ignore_nulls {
18205            self.write_space();
18206            if ignore {
18207                self.write_keyword("IGNORE NULLS");
18208            } else {
18209                self.write_keyword("RESPECT NULLS");
18210            }
18211        }
18212        self.write(")");
18213        if let Some(ref filter) = f.filter {
18214            self.write_space();
18215            self.write_keyword("FILTER");
18216            self.write("(");
18217            self.write_keyword("WHERE");
18218            self.write_space();
18219            self.generate_expression(filter)?;
18220            self.write(")");
18221        }
18222        Ok(())
18223    }
18224
18225    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
18226        // Apply function name normalization based on config
18227        let func_name = match self.config.normalize_functions {
18228            NormalizeFunctions::Upper => name.to_uppercase(),
18229            NormalizeFunctions::Lower => name.to_lowercase(),
18230            NormalizeFunctions::None => {
18231                // Use the original function name from parsing if available,
18232                // otherwise fall back to lowercase of the hardcoded constant
18233                if let Some(ref original) = f.name {
18234                    original.clone()
18235                } else {
18236                    name.to_lowercase()
18237                }
18238            }
18239        };
18240        self.write(&func_name);
18241        self.write("(");
18242        if f.distinct {
18243            self.write_keyword("DISTINCT");
18244            self.write_space();
18245        }
18246        // Skip generating the expression if it's a NULL placeholder for zero-arg aggregates like MODE()
18247        if !matches!(f.this, Expression::Null(_)) {
18248            self.generate_expression(&f.this)?;
18249        }
18250        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
18251        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
18252        if self.config.ignore_nulls_in_func
18253            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18254        {
18255            match f.ignore_nulls {
18256                Some(true) => {
18257                    self.write_space();
18258                    self.write_keyword("IGNORE NULLS");
18259                }
18260                Some(false) => {
18261                    self.write_space();
18262                    self.write_keyword("RESPECT NULLS");
18263                }
18264                None => {}
18265            }
18266        }
18267        // Generate HAVING MAX/MIN if present (BigQuery syntax)
18268        // e.g., ANY_VALUE(fruit HAVING MAX sold)
18269        if let Some((ref expr, is_max)) = f.having_max {
18270            self.write_space();
18271            self.write_keyword("HAVING");
18272            self.write_space();
18273            if is_max {
18274                self.write_keyword("MAX");
18275            } else {
18276                self.write_keyword("MIN");
18277            }
18278            self.write_space();
18279            self.generate_expression(expr)?;
18280        }
18281        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
18282        if !f.order_by.is_empty() {
18283            self.write_space();
18284            self.write_keyword("ORDER BY");
18285            self.write_space();
18286            for (i, ord) in f.order_by.iter().enumerate() {
18287                if i > 0 {
18288                    self.write(", ");
18289                }
18290                self.generate_ordered(ord)?;
18291            }
18292        }
18293        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
18294        if let Some(ref limit) = f.limit {
18295            self.write_space();
18296            self.write_keyword("LIMIT");
18297            self.write_space();
18298            // Check if this is a Tuple representing LIMIT offset, count
18299            if let Expression::Tuple(t) = limit.as_ref() {
18300                if t.expressions.len() == 2 {
18301                    self.generate_expression(&t.expressions[0])?;
18302                    self.write(", ");
18303                    self.generate_expression(&t.expressions[1])?;
18304                } else {
18305                    self.generate_expression(limit)?;
18306                }
18307            } else {
18308                self.generate_expression(limit)?;
18309            }
18310        }
18311        self.write(")");
18312        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
18313        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
18314        if !self.config.ignore_nulls_in_func
18315            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18316        {
18317            match f.ignore_nulls {
18318                Some(true) => {
18319                    self.write_space();
18320                    self.write_keyword("IGNORE NULLS");
18321                }
18322                Some(false) => {
18323                    self.write_space();
18324                    self.write_keyword("RESPECT NULLS");
18325                }
18326                None => {}
18327            }
18328        }
18329        if let Some(ref filter) = f.filter {
18330            self.write_space();
18331            self.write_keyword("FILTER");
18332            self.write("(");
18333            self.write_keyword("WHERE");
18334            self.write_space();
18335            self.generate_expression(filter)?;
18336            self.write(")");
18337        }
18338        Ok(())
18339    }
18340
18341    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
18342        self.write_keyword("GROUP_CONCAT");
18343        self.write("(");
18344        if f.distinct {
18345            self.write_keyword("DISTINCT");
18346            self.write_space();
18347        }
18348        self.generate_expression(&f.this)?;
18349        if let Some(ref order_by) = f.order_by {
18350            self.write_space();
18351            self.write_keyword("ORDER BY");
18352            self.write_space();
18353            for (i, ord) in order_by.iter().enumerate() {
18354                if i > 0 {
18355                    self.write(", ");
18356                }
18357                self.generate_ordered(ord)?;
18358            }
18359        }
18360        if let Some(ref sep) = f.separator {
18361            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
18362            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
18363            if matches!(
18364                self.config.dialect,
18365                Some(crate::dialects::DialectType::SQLite)
18366            ) {
18367                self.write(", ");
18368                self.generate_expression(sep)?;
18369            } else {
18370                self.write_space();
18371                self.write_keyword("SEPARATOR");
18372                self.write_space();
18373                self.generate_expression(sep)?;
18374            }
18375        }
18376        self.write(")");
18377        if let Some(ref filter) = f.filter {
18378            self.write_space();
18379            self.write_keyword("FILTER");
18380            self.write("(");
18381            self.write_keyword("WHERE");
18382            self.write_space();
18383            self.generate_expression(filter)?;
18384            self.write(")");
18385        }
18386        Ok(())
18387    }
18388
18389    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
18390        let is_tsql = matches!(
18391            self.config.dialect,
18392            Some(crate::dialects::DialectType::TSQL)
18393        );
18394        self.write_keyword("STRING_AGG");
18395        self.write("(");
18396        if f.distinct {
18397            self.write_keyword("DISTINCT");
18398            self.write_space();
18399        }
18400        self.generate_expression(&f.this)?;
18401        if let Some(ref separator) = f.separator {
18402            self.write(", ");
18403            self.generate_expression(separator)?;
18404        }
18405        // For TSQL, ORDER BY goes in WITHIN GROUP clause after the closing paren
18406        if !is_tsql {
18407            if let Some(ref order_by) = f.order_by {
18408                self.write_space();
18409                self.write_keyword("ORDER BY");
18410                self.write_space();
18411                for (i, ord) in order_by.iter().enumerate() {
18412                    if i > 0 {
18413                        self.write(", ");
18414                    }
18415                    self.generate_ordered(ord)?;
18416                }
18417            }
18418        }
18419        if let Some(ref limit) = f.limit {
18420            self.write_space();
18421            self.write_keyword("LIMIT");
18422            self.write_space();
18423            self.generate_expression(limit)?;
18424        }
18425        self.write(")");
18426        // TSQL uses WITHIN GROUP (ORDER BY ...) after the function call
18427        if is_tsql {
18428            if let Some(ref order_by) = f.order_by {
18429                self.write_space();
18430                self.write_keyword("WITHIN GROUP");
18431                self.write(" (");
18432                self.write_keyword("ORDER BY");
18433                self.write_space();
18434                for (i, ord) in order_by.iter().enumerate() {
18435                    if i > 0 {
18436                        self.write(", ");
18437                    }
18438                    self.generate_ordered(ord)?;
18439                }
18440                self.write(")");
18441            }
18442        }
18443        if let Some(ref filter) = f.filter {
18444            self.write_space();
18445            self.write_keyword("FILTER");
18446            self.write("(");
18447            self.write_keyword("WHERE");
18448            self.write_space();
18449            self.generate_expression(filter)?;
18450            self.write(")");
18451        }
18452        Ok(())
18453    }
18454
18455    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
18456        use crate::dialects::DialectType;
18457        self.write_keyword("LISTAGG");
18458        self.write("(");
18459        if f.distinct {
18460            self.write_keyword("DISTINCT");
18461            self.write_space();
18462        }
18463        self.generate_expression(&f.this)?;
18464        if let Some(ref sep) = f.separator {
18465            self.write(", ");
18466            self.generate_expression(sep)?;
18467        } else if matches!(
18468            self.config.dialect,
18469            Some(DialectType::Trino) | Some(DialectType::Presto)
18470        ) {
18471            // Trino/Presto require explicit separator; default to ','
18472            self.write(", ','");
18473        }
18474        if let Some(ref overflow) = f.on_overflow {
18475            self.write_space();
18476            self.write_keyword("ON OVERFLOW");
18477            self.write_space();
18478            match overflow {
18479                ListAggOverflow::Error => self.write_keyword("ERROR"),
18480                ListAggOverflow::Truncate { filler, with_count } => {
18481                    self.write_keyword("TRUNCATE");
18482                    if let Some(ref fill) = filler {
18483                        self.write_space();
18484                        self.generate_expression(fill)?;
18485                    }
18486                    if *with_count {
18487                        self.write_space();
18488                        self.write_keyword("WITH COUNT");
18489                    } else {
18490                        self.write_space();
18491                        self.write_keyword("WITHOUT COUNT");
18492                    }
18493                }
18494            }
18495        }
18496        self.write(")");
18497        if let Some(ref order_by) = f.order_by {
18498            self.write_space();
18499            self.write_keyword("WITHIN GROUP");
18500            self.write(" (");
18501            self.write_keyword("ORDER BY");
18502            self.write_space();
18503            for (i, ord) in order_by.iter().enumerate() {
18504                if i > 0 {
18505                    self.write(", ");
18506                }
18507                self.generate_ordered(ord)?;
18508            }
18509            self.write(")");
18510        }
18511        if let Some(ref filter) = f.filter {
18512            self.write_space();
18513            self.write_keyword("FILTER");
18514            self.write("(");
18515            self.write_keyword("WHERE");
18516            self.write_space();
18517            self.generate_expression(filter)?;
18518            self.write(")");
18519        }
18520        Ok(())
18521    }
18522
18523    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
18524        self.write_keyword("SUM_IF");
18525        self.write("(");
18526        self.generate_expression(&f.this)?;
18527        self.write(", ");
18528        self.generate_expression(&f.condition)?;
18529        self.write(")");
18530        if let Some(ref filter) = f.filter {
18531            self.write_space();
18532            self.write_keyword("FILTER");
18533            self.write("(");
18534            self.write_keyword("WHERE");
18535            self.write_space();
18536            self.generate_expression(filter)?;
18537            self.write(")");
18538        }
18539        Ok(())
18540    }
18541
18542    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
18543        self.write_keyword("APPROX_PERCENTILE");
18544        self.write("(");
18545        self.generate_expression(&f.this)?;
18546        self.write(", ");
18547        self.generate_expression(&f.percentile)?;
18548        if let Some(ref acc) = f.accuracy {
18549            self.write(", ");
18550            self.generate_expression(acc)?;
18551        }
18552        self.write(")");
18553        if let Some(ref filter) = f.filter {
18554            self.write_space();
18555            self.write_keyword("FILTER");
18556            self.write("(");
18557            self.write_keyword("WHERE");
18558            self.write_space();
18559            self.generate_expression(filter)?;
18560            self.write(")");
18561        }
18562        Ok(())
18563    }
18564
18565    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
18566        self.write_keyword(name);
18567        self.write("(");
18568        self.generate_expression(&f.percentile)?;
18569        self.write(")");
18570        if let Some(ref order_by) = f.order_by {
18571            self.write_space();
18572            self.write_keyword("WITHIN GROUP");
18573            self.write(" (");
18574            self.write_keyword("ORDER BY");
18575            self.write_space();
18576            self.generate_expression(&f.this)?;
18577            for ord in order_by.iter() {
18578                if ord.desc {
18579                    self.write_space();
18580                    self.write_keyword("DESC");
18581                }
18582            }
18583            self.write(")");
18584        }
18585        if let Some(ref filter) = f.filter {
18586            self.write_space();
18587            self.write_keyword("FILTER");
18588            self.write("(");
18589            self.write_keyword("WHERE");
18590            self.write_space();
18591            self.generate_expression(filter)?;
18592            self.write(")");
18593        }
18594        Ok(())
18595    }
18596
18597    // Window function generators
18598
18599    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
18600        self.write_keyword("NTILE");
18601        self.write("(");
18602        if let Some(num_buckets) = &f.num_buckets {
18603            self.generate_expression(num_buckets)?;
18604        }
18605        if let Some(order_by) = &f.order_by {
18606            self.write_keyword(" ORDER BY ");
18607            for (i, ob) in order_by.iter().enumerate() {
18608                if i > 0 {
18609                    self.write(", ");
18610                }
18611                self.generate_ordered(ob)?;
18612            }
18613        }
18614        self.write(")");
18615        Ok(())
18616    }
18617
18618    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
18619        self.write_keyword(name);
18620        self.write("(");
18621        self.generate_expression(&f.this)?;
18622        if let Some(ref offset) = f.offset {
18623            self.write(", ");
18624            self.generate_expression(offset)?;
18625            if let Some(ref default) = f.default {
18626                self.write(", ");
18627                self.generate_expression(default)?;
18628            }
18629        }
18630        // IGNORE NULLS inside parens for dialects like BigQuery
18631        if f.ignore_nulls && self.config.ignore_nulls_in_func {
18632            self.write_space();
18633            self.write_keyword("IGNORE NULLS");
18634        }
18635        self.write(")");
18636        // IGNORE NULLS outside parens for other dialects
18637        if f.ignore_nulls && !self.config.ignore_nulls_in_func {
18638            self.write_space();
18639            self.write_keyword("IGNORE NULLS");
18640        }
18641        Ok(())
18642    }
18643
18644    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
18645        self.write_keyword(name);
18646        self.write("(");
18647        self.generate_expression(&f.this)?;
18648        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
18649        if self.config.ignore_nulls_in_func {
18650            match f.ignore_nulls {
18651                Some(true) => {
18652                    self.write_space();
18653                    self.write_keyword("IGNORE NULLS");
18654                }
18655                Some(false) => {
18656                    self.write_space();
18657                    self.write_keyword("RESPECT NULLS");
18658                }
18659                None => {}
18660            }
18661        }
18662        self.write(")");
18663        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
18664        if !self.config.ignore_nulls_in_func {
18665            match f.ignore_nulls {
18666                Some(true) => {
18667                    self.write_space();
18668                    self.write_keyword("IGNORE NULLS");
18669                }
18670                Some(false) => {
18671                    self.write_space();
18672                    self.write_keyword("RESPECT NULLS");
18673                }
18674                None => {}
18675            }
18676        }
18677        Ok(())
18678    }
18679
18680    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
18681        self.write_keyword("NTH_VALUE");
18682        self.write("(");
18683        self.generate_expression(&f.this)?;
18684        self.write(", ");
18685        self.generate_expression(&f.offset)?;
18686        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
18687        if self.config.ignore_nulls_in_func {
18688            match f.ignore_nulls {
18689                Some(true) => {
18690                    self.write_space();
18691                    self.write_keyword("IGNORE NULLS");
18692                }
18693                Some(false) => {
18694                    self.write_space();
18695                    self.write_keyword("RESPECT NULLS");
18696                }
18697                None => {}
18698            }
18699        }
18700        self.write(")");
18701        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
18702        if matches!(
18703            self.config.dialect,
18704            Some(crate::dialects::DialectType::Snowflake)
18705        ) {
18706            match f.from_first {
18707                Some(true) => {
18708                    self.write_space();
18709                    self.write_keyword("FROM FIRST");
18710                }
18711                Some(false) => {
18712                    self.write_space();
18713                    self.write_keyword("FROM LAST");
18714                }
18715                None => {}
18716            }
18717        }
18718        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
18719        if !self.config.ignore_nulls_in_func {
18720            match f.ignore_nulls {
18721                Some(true) => {
18722                    self.write_space();
18723                    self.write_keyword("IGNORE NULLS");
18724                }
18725                Some(false) => {
18726                    self.write_space();
18727                    self.write_keyword("RESPECT NULLS");
18728                }
18729                None => {}
18730            }
18731        }
18732        Ok(())
18733    }
18734
18735    // Additional string function generators
18736
18737    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
18738        // Standard syntax: POSITION(substr IN str)
18739        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
18740        if matches!(
18741            self.config.dialect,
18742            Some(crate::dialects::DialectType::ClickHouse)
18743        ) {
18744            self.write_keyword("POSITION");
18745            self.write("(");
18746            self.generate_expression(&f.string)?;
18747            self.write(", ");
18748            self.generate_expression(&f.substring)?;
18749            if let Some(ref start) = f.start {
18750                self.write(", ");
18751                self.generate_expression(start)?;
18752            }
18753            self.write(")");
18754            return Ok(());
18755        }
18756
18757        self.write_keyword("POSITION");
18758        self.write("(");
18759        self.generate_expression(&f.substring)?;
18760        self.write_space();
18761        self.write_keyword("IN");
18762        self.write_space();
18763        self.generate_expression(&f.string)?;
18764        if let Some(ref start) = f.start {
18765            self.write(", ");
18766            self.generate_expression(start)?;
18767        }
18768        self.write(")");
18769        Ok(())
18770    }
18771
18772    // Additional math function generators
18773
18774    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
18775        // Teradata RANDOM(lower, upper)
18776        if f.lower.is_some() || f.upper.is_some() {
18777            self.write_keyword("RANDOM");
18778            self.write("(");
18779            if let Some(ref lower) = f.lower {
18780                self.generate_expression(lower)?;
18781            }
18782            if let Some(ref upper) = f.upper {
18783                self.write(", ");
18784                self.generate_expression(upper)?;
18785            }
18786            self.write(")");
18787            return Ok(());
18788        }
18789        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
18790        let func_name = match self.config.dialect {
18791            Some(crate::dialects::DialectType::Snowflake)
18792            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
18793            _ => "RAND",
18794        };
18795        self.write_keyword(func_name);
18796        self.write("(");
18797        // DuckDB doesn't support seeded RANDOM, so skip the seed
18798        if !matches!(
18799            self.config.dialect,
18800            Some(crate::dialects::DialectType::DuckDB)
18801        ) {
18802            if let Some(ref seed) = f.seed {
18803                self.generate_expression(seed)?;
18804            }
18805        }
18806        self.write(")");
18807        Ok(())
18808    }
18809
18810    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
18811        self.write_keyword("TRUNCATE");
18812        self.write("(");
18813        self.generate_expression(&f.this)?;
18814        if let Some(ref decimals) = f.decimals {
18815            self.write(", ");
18816            self.generate_expression(decimals)?;
18817        }
18818        self.write(")");
18819        Ok(())
18820    }
18821
18822    // Control flow generators
18823
18824    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
18825        self.write_keyword("DECODE");
18826        self.write("(");
18827        self.generate_expression(&f.this)?;
18828        for (search, result) in &f.search_results {
18829            self.write(", ");
18830            self.generate_expression(search)?;
18831            self.write(", ");
18832            self.generate_expression(result)?;
18833        }
18834        if let Some(ref default) = f.default {
18835            self.write(", ");
18836            self.generate_expression(default)?;
18837        }
18838        self.write(")");
18839        Ok(())
18840    }
18841
18842    // Date/time function generators
18843
18844    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
18845        self.write_keyword(name);
18846        self.write("(");
18847        self.generate_expression(&f.this)?;
18848        self.write(", ");
18849        self.generate_expression(&f.format)?;
18850        self.write(")");
18851        Ok(())
18852    }
18853
18854    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
18855        self.write_keyword("FROM_UNIXTIME");
18856        self.write("(");
18857        self.generate_expression(&f.this)?;
18858        if let Some(ref format) = f.format {
18859            self.write(", ");
18860            self.generate_expression(format)?;
18861        }
18862        self.write(")");
18863        Ok(())
18864    }
18865
18866    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
18867        self.write_keyword("UNIX_TIMESTAMP");
18868        self.write("(");
18869        if let Some(ref expr) = f.this {
18870            self.generate_expression(expr)?;
18871            if let Some(ref format) = f.format {
18872                self.write(", ");
18873                self.generate_expression(format)?;
18874            }
18875        } else if matches!(
18876            self.config.dialect,
18877            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
18878        ) {
18879            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
18880            self.write_keyword("CURRENT_TIMESTAMP");
18881            self.write("()");
18882        }
18883        self.write(")");
18884        Ok(())
18885    }
18886
18887    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
18888        self.write_keyword("MAKE_DATE");
18889        self.write("(");
18890        self.generate_expression(&f.year)?;
18891        self.write(", ");
18892        self.generate_expression(&f.month)?;
18893        self.write(", ");
18894        self.generate_expression(&f.day)?;
18895        self.write(")");
18896        Ok(())
18897    }
18898
18899    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
18900        self.write_keyword("MAKE_TIMESTAMP");
18901        self.write("(");
18902        self.generate_expression(&f.year)?;
18903        self.write(", ");
18904        self.generate_expression(&f.month)?;
18905        self.write(", ");
18906        self.generate_expression(&f.day)?;
18907        self.write(", ");
18908        self.generate_expression(&f.hour)?;
18909        self.write(", ");
18910        self.generate_expression(&f.minute)?;
18911        self.write(", ");
18912        self.generate_expression(&f.second)?;
18913        if let Some(ref tz) = f.timezone {
18914            self.write(", ");
18915            self.generate_expression(tz)?;
18916        }
18917        self.write(")");
18918        Ok(())
18919    }
18920
18921    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
18922    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
18923        match expr {
18924            Expression::Struct(s) => {
18925                if s.fields.iter().all(|(name, _)| name.is_some()) {
18926                    Some(
18927                        s.fields
18928                            .iter()
18929                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
18930                            .collect(),
18931                    )
18932                } else {
18933                    None
18934                }
18935            }
18936            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
18937                // Check if all args are Alias (named fields)
18938                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
18939                    Some(
18940                        f.args
18941                            .iter()
18942                            .filter_map(|a| {
18943                                if let Expression::Alias(alias) = a {
18944                                    Some(alias.alias.name.clone())
18945                                } else {
18946                                    None
18947                                }
18948                            })
18949                            .collect(),
18950                    )
18951                } else {
18952                    None
18953                }
18954            }
18955            _ => None,
18956        }
18957    }
18958
18959    /// Check if a struct expression has any unnamed fields
18960    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
18961        match expr {
18962            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
18963            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
18964                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
18965            }
18966            _ => false,
18967        }
18968    }
18969
18970    /// Get the field count of a struct expression
18971    fn struct_field_count(expr: &Expression) -> usize {
18972        match expr {
18973            Expression::Struct(s) => s.fields.len(),
18974            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => f.args.len(),
18975            _ => 0,
18976        }
18977    }
18978
18979    /// Apply field names to an unnamed struct expression, producing a new expression with names
18980    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
18981        match expr {
18982            Expression::Struct(s) => {
18983                let mut new_fields = Vec::with_capacity(s.fields.len());
18984                for (i, (name, value)) in s.fields.iter().enumerate() {
18985                    if name.is_none() && i < field_names.len() {
18986                        new_fields.push((Some(field_names[i].clone()), value.clone()));
18987                    } else {
18988                        new_fields.push((name.clone(), value.clone()));
18989                    }
18990                }
18991                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
18992            }
18993            Expression::Function(f) if f.name.to_uppercase() == "STRUCT" => {
18994                let mut new_args = Vec::with_capacity(f.args.len());
18995                for (i, arg) in f.args.iter().enumerate() {
18996                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
18997                        // Wrap the value in an Alias with the inherited name
18998                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
18999                            this: arg.clone(),
19000                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
19001                            column_aliases: Vec::new(),
19002                            pre_alias_comments: Vec::new(),
19003                            trailing_comments: Vec::new(),
19004                        })));
19005                    } else {
19006                        new_args.push(arg.clone());
19007                    }
19008                }
19009                Expression::Function(Box::new(crate::expressions::Function {
19010                    name: f.name.clone(),
19011                    args: new_args,
19012                    distinct: f.distinct,
19013                    trailing_comments: f.trailing_comments.clone(),
19014                    use_bracket_syntax: f.use_bracket_syntax,
19015                    no_parens: f.no_parens,
19016                    quoted: f.quoted,
19017                }))
19018            }
19019            _ => expr.clone(),
19020        }
19021    }
19022
19023    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
19024    /// This implements BigQuery's implicit field name inheritance for struct arrays.
19025    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
19026    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
19027        let first = match expressions.first() {
19028            Some(e) => e,
19029            None => return expressions.to_vec(),
19030        };
19031
19032        let field_names = match Self::extract_struct_field_names(first) {
19033            Some(names) if !names.is_empty() => names,
19034            _ => return expressions.to_vec(),
19035        };
19036
19037        let mut result = Vec::with_capacity(expressions.len());
19038        for (idx, expr) in expressions.iter().enumerate() {
19039            if idx == 0 {
19040                result.push(expr.clone());
19041                continue;
19042            }
19043            // Check if this is a struct with unnamed fields that needs name propagation
19044            if Self::struct_field_count(expr) == field_names.len()
19045                && Self::struct_has_unnamed_fields(expr)
19046            {
19047                result.push(Self::apply_struct_field_names(expr, &field_names));
19048            } else {
19049                result.push(expr.clone());
19050            }
19051        }
19052        result
19053    }
19054
19055    // Array function generators
19056
19057    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
19058        // Apply struct name inheritance for target dialects that need it
19059        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
19060        let needs_inheritance = matches!(
19061            self.config.dialect,
19062            Some(DialectType::DuckDB)
19063                | Some(DialectType::Spark)
19064                | Some(DialectType::Databricks)
19065                | Some(DialectType::Hive)
19066                | Some(DialectType::Snowflake)
19067                | Some(DialectType::Presto)
19068                | Some(DialectType::Trino)
19069        );
19070        let propagated: Vec<Expression>;
19071        let expressions = if needs_inheritance && f.expressions.len() > 1 {
19072            propagated = Self::inherit_struct_field_names(&f.expressions);
19073            &propagated
19074        } else {
19075            &f.expressions
19076        };
19077
19078        // Check if elements should be split onto multiple lines (pretty + too wide)
19079        let should_split = if self.config.pretty && !expressions.is_empty() {
19080            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
19081            for expr in expressions {
19082                let mut temp_gen = Generator::with_config(self.config.clone());
19083                temp_gen.config.pretty = false;
19084                temp_gen.generate_expression(expr)?;
19085                expr_strings.push(temp_gen.output);
19086            }
19087            self.too_wide(&expr_strings)
19088        } else {
19089            false
19090        };
19091
19092        if f.bracket_notation {
19093            // For Spark/Databricks, use ARRAY(...) with parens
19094            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
19095            // For others (DuckDB, Snowflake), use bare [...]
19096            let (open, close) = match self.config.dialect {
19097                None
19098                | Some(DialectType::Generic)
19099                | Some(DialectType::Spark)
19100                | Some(DialectType::Databricks)
19101                | Some(DialectType::Hive) => {
19102                    self.write_keyword("ARRAY");
19103                    ("(", ")")
19104                }
19105                Some(DialectType::Presto)
19106                | Some(DialectType::Trino)
19107                | Some(DialectType::PostgreSQL)
19108                | Some(DialectType::Redshift)
19109                | Some(DialectType::Materialize)
19110                | Some(DialectType::RisingWave)
19111                | Some(DialectType::CockroachDB) => {
19112                    self.write_keyword("ARRAY");
19113                    ("[", "]")
19114                }
19115                _ => ("[", "]"),
19116            };
19117            self.write(open);
19118            if should_split {
19119                self.write_newline();
19120                self.indent_level += 1;
19121                for (i, expr) in expressions.iter().enumerate() {
19122                    self.write_indent();
19123                    self.generate_expression(expr)?;
19124                    if i + 1 < expressions.len() {
19125                        self.write(",");
19126                    }
19127                    self.write_newline();
19128                }
19129                self.indent_level -= 1;
19130                self.write_indent();
19131            } else {
19132                for (i, expr) in expressions.iter().enumerate() {
19133                    if i > 0 {
19134                        self.write(", ");
19135                    }
19136                    self.generate_expression(expr)?;
19137                }
19138            }
19139            self.write(close);
19140        } else {
19141            // Use LIST keyword if that was the original syntax (DuckDB)
19142            if f.use_list_keyword {
19143                self.write_keyword("LIST");
19144            } else {
19145                self.write_keyword("ARRAY");
19146            }
19147            // For Spark/Hive, always use ARRAY(...) with parens
19148            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
19149            let has_subquery = expressions
19150                .iter()
19151                .any(|e| matches!(e, Expression::Select(_)));
19152            let (open, close) = if matches!(
19153                self.config.dialect,
19154                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
19155            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
19156                && has_subquery)
19157            {
19158                ("(", ")")
19159            } else {
19160                ("[", "]")
19161            };
19162            self.write(open);
19163            if should_split {
19164                self.write_newline();
19165                self.indent_level += 1;
19166                for (i, expr) in expressions.iter().enumerate() {
19167                    self.write_indent();
19168                    self.generate_expression(expr)?;
19169                    if i + 1 < expressions.len() {
19170                        self.write(",");
19171                    }
19172                    self.write_newline();
19173                }
19174                self.indent_level -= 1;
19175                self.write_indent();
19176            } else {
19177                for (i, expr) in expressions.iter().enumerate() {
19178                    if i > 0 {
19179                        self.write(", ");
19180                    }
19181                    self.generate_expression(expr)?;
19182                }
19183            }
19184            self.write(close);
19185        }
19186        Ok(())
19187    }
19188
19189    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
19190        self.write_keyword("ARRAY_SORT");
19191        self.write("(");
19192        self.generate_expression(&f.this)?;
19193        if let Some(ref comp) = f.comparator {
19194            self.write(", ");
19195            self.generate_expression(comp)?;
19196        }
19197        self.write(")");
19198        Ok(())
19199    }
19200
19201    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
19202        self.write_keyword(name);
19203        self.write("(");
19204        self.generate_expression(&f.this)?;
19205        self.write(", ");
19206        self.generate_expression(&f.separator)?;
19207        if let Some(ref null_rep) = f.null_replacement {
19208            self.write(", ");
19209            self.generate_expression(null_rep)?;
19210        }
19211        self.write(")");
19212        Ok(())
19213    }
19214
19215    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
19216        self.write_keyword("UNNEST");
19217        self.write("(");
19218        self.generate_expression(&f.this)?;
19219        for extra in &f.expressions {
19220            self.write(", ");
19221            self.generate_expression(extra)?;
19222        }
19223        self.write(")");
19224        if f.with_ordinality {
19225            self.write_space();
19226            if self.config.unnest_with_ordinality {
19227                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
19228                self.write_keyword("WITH ORDINALITY");
19229            } else if f.offset_alias.is_some() {
19230                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
19231                // Alias (if any) comes BEFORE WITH OFFSET
19232                if let Some(ref alias) = f.alias {
19233                    self.write_keyword("AS");
19234                    self.write_space();
19235                    self.generate_identifier(alias)?;
19236                    self.write_space();
19237                }
19238                self.write_keyword("WITH OFFSET");
19239                if let Some(ref offset_alias) = f.offset_alias {
19240                    self.write_space();
19241                    self.write_keyword("AS");
19242                    self.write_space();
19243                    self.generate_identifier(offset_alias)?;
19244                }
19245            } else {
19246                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
19247                self.write_keyword("WITH OFFSET");
19248                if f.alias.is_none() {
19249                    self.write(" AS offset");
19250                }
19251            }
19252        }
19253        if let Some(ref alias) = f.alias {
19254            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
19255            let should_add_alias = if !f.with_ordinality {
19256                true
19257            } else if self.config.unnest_with_ordinality {
19258                // Presto/Trino: alias comes after WITH ORDINALITY
19259                true
19260            } else if f.offset_alias.is_some() {
19261                // BigQuery expansion: alias already handled above
19262                false
19263            } else {
19264                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
19265                true
19266            };
19267            if should_add_alias {
19268                self.write_space();
19269                self.write_keyword("AS");
19270                self.write_space();
19271                self.generate_identifier(alias)?;
19272            }
19273        }
19274        Ok(())
19275    }
19276
19277    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
19278        self.write_keyword("FILTER");
19279        self.write("(");
19280        self.generate_expression(&f.this)?;
19281        self.write(", ");
19282        self.generate_expression(&f.filter)?;
19283        self.write(")");
19284        Ok(())
19285    }
19286
19287    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
19288        self.write_keyword("TRANSFORM");
19289        self.write("(");
19290        self.generate_expression(&f.this)?;
19291        self.write(", ");
19292        self.generate_expression(&f.transform)?;
19293        self.write(")");
19294        Ok(())
19295    }
19296
19297    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
19298        self.write_keyword(name);
19299        self.write("(");
19300        self.generate_expression(&f.start)?;
19301        self.write(", ");
19302        self.generate_expression(&f.stop)?;
19303        if let Some(ref step) = f.step {
19304            self.write(", ");
19305            self.generate_expression(step)?;
19306        }
19307        self.write(")");
19308        Ok(())
19309    }
19310
19311    // Struct function generators
19312
19313    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
19314        self.write_keyword("STRUCT");
19315        self.write("(");
19316        for (i, (name, expr)) in f.fields.iter().enumerate() {
19317            if i > 0 {
19318                self.write(", ");
19319            }
19320            if let Some(ref id) = name {
19321                self.generate_identifier(id)?;
19322                self.write(" ");
19323                self.write_keyword("AS");
19324                self.write(" ");
19325            }
19326            self.generate_expression(expr)?;
19327        }
19328        self.write(")");
19329        Ok(())
19330    }
19331
19332    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
19333    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
19334        // Extract named/unnamed fields from function args
19335        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
19336        let mut names: Vec<Option<String>> = Vec::new();
19337        let mut values: Vec<&Expression> = Vec::new();
19338        let mut all_named = true;
19339
19340        for arg in &func.args {
19341            match arg {
19342                Expression::Alias(a) => {
19343                    names.push(Some(a.alias.name.clone()));
19344                    values.push(&a.this);
19345                }
19346                _ => {
19347                    names.push(None);
19348                    values.push(arg);
19349                    all_named = false;
19350                }
19351            }
19352        }
19353
19354        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
19355            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
19356            self.write("{");
19357            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19358                if i > 0 {
19359                    self.write(", ");
19360                }
19361                if let Some(n) = name {
19362                    self.write("'");
19363                    self.write(n);
19364                    self.write("'");
19365                } else {
19366                    self.write("'_");
19367                    self.write(&i.to_string());
19368                    self.write("'");
19369                }
19370                self.write(": ");
19371                self.generate_expression(value)?;
19372            }
19373            self.write("}");
19374            return Ok(());
19375        }
19376
19377        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
19378            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
19379            self.write_keyword("OBJECT_CONSTRUCT");
19380            self.write("(");
19381            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19382                if i > 0 {
19383                    self.write(", ");
19384                }
19385                if let Some(n) = name {
19386                    self.write("'");
19387                    self.write(n);
19388                    self.write("'");
19389                } else {
19390                    self.write("'_");
19391                    self.write(&i.to_string());
19392                    self.write("'");
19393                }
19394                self.write(", ");
19395                self.generate_expression(value)?;
19396            }
19397            self.write(")");
19398            return Ok(());
19399        }
19400
19401        if matches!(
19402            self.config.dialect,
19403            Some(DialectType::Presto) | Some(DialectType::Trino)
19404        ) {
19405            if all_named && !names.is_empty() {
19406                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
19407                // Need to infer types from values
19408                self.write_keyword("CAST");
19409                self.write("(");
19410                self.write_keyword("ROW");
19411                self.write("(");
19412                for (i, value) in values.iter().enumerate() {
19413                    if i > 0 {
19414                        self.write(", ");
19415                    }
19416                    self.generate_expression(value)?;
19417                }
19418                self.write(")");
19419                self.write(" ");
19420                self.write_keyword("AS");
19421                self.write(" ");
19422                self.write_keyword("ROW");
19423                self.write("(");
19424                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
19425                    if i > 0 {
19426                        self.write(", ");
19427                    }
19428                    if let Some(n) = name {
19429                        self.write(n);
19430                    }
19431                    self.write(" ");
19432                    let type_str = Self::infer_sql_type_for_presto(value);
19433                    self.write_keyword(&type_str);
19434                }
19435                self.write(")");
19436                self.write(")");
19437            } else {
19438                // Unnamed: ROW(values...)
19439                self.write_keyword("ROW");
19440                self.write("(");
19441                for (i, value) in values.iter().enumerate() {
19442                    if i > 0 {
19443                        self.write(", ");
19444                    }
19445                    self.generate_expression(value)?;
19446                }
19447                self.write(")");
19448            }
19449            return Ok(());
19450        }
19451
19452        // Default: ROW(values...) for other dialects
19453        self.write_keyword("ROW");
19454        self.write("(");
19455        for (i, value) in values.iter().enumerate() {
19456            if i > 0 {
19457                self.write(", ");
19458            }
19459            self.generate_expression(value)?;
19460        }
19461        self.write(")");
19462        Ok(())
19463    }
19464
19465    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
19466    fn infer_sql_type_for_presto(expr: &Expression) -> String {
19467        match expr {
19468            Expression::Literal(crate::expressions::Literal::String(_)) => "VARCHAR".to_string(),
19469            Expression::Literal(crate::expressions::Literal::Number(n)) => {
19470                if n.contains('.') {
19471                    "DOUBLE".to_string()
19472                } else {
19473                    "INTEGER".to_string()
19474                }
19475            }
19476            Expression::Boolean(_) => "BOOLEAN".to_string(),
19477            Expression::Literal(crate::expressions::Literal::Date(_)) => "DATE".to_string(),
19478            Expression::Literal(crate::expressions::Literal::Timestamp(_)) => {
19479                "TIMESTAMP".to_string()
19480            }
19481            Expression::Literal(crate::expressions::Literal::Datetime(_)) => {
19482                "TIMESTAMP".to_string()
19483            }
19484            Expression::Array(_) | Expression::ArrayFunc(_) => {
19485                // Try to infer element type from first element
19486                "ARRAY(VARCHAR)".to_string()
19487            }
19488            // For nested structs - generate a nested ROW type by inspecting fields
19489            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
19490            Expression::Function(f) => {
19491                let up = f.name.to_uppercase();
19492                if up == "STRUCT" {
19493                    "ROW".to_string()
19494                } else if up == "CURRENT_DATE" {
19495                    "DATE".to_string()
19496                } else if up == "CURRENT_TIMESTAMP" || up == "NOW" {
19497                    "TIMESTAMP".to_string()
19498                } else {
19499                    "VARCHAR".to_string()
19500                }
19501            }
19502            _ => "VARCHAR".to_string(),
19503        }
19504    }
19505
19506    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
19507        // DuckDB uses STRUCT_EXTRACT function syntax
19508        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
19509            self.write_keyword("STRUCT_EXTRACT");
19510            self.write("(");
19511            self.generate_expression(&f.this)?;
19512            self.write(", ");
19513            // Output field name as string literal
19514            self.write("'");
19515            self.write(&f.field.name);
19516            self.write("'");
19517            self.write(")");
19518            return Ok(());
19519        }
19520        self.generate_expression(&f.this)?;
19521        self.write(".");
19522        self.generate_identifier(&f.field)
19523    }
19524
19525    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
19526        self.write_keyword("NAMED_STRUCT");
19527        self.write("(");
19528        for (i, (name, value)) in f.pairs.iter().enumerate() {
19529            if i > 0 {
19530                self.write(", ");
19531            }
19532            self.generate_expression(name)?;
19533            self.write(", ");
19534            self.generate_expression(value)?;
19535        }
19536        self.write(")");
19537        Ok(())
19538    }
19539
19540    // Map function generators
19541
19542    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
19543        if f.curly_brace_syntax {
19544            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
19545            if f.with_map_keyword {
19546                self.write_keyword("MAP");
19547                self.write(" ");
19548            }
19549            self.write("{");
19550            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
19551                if i > 0 {
19552                    self.write(", ");
19553                }
19554                self.generate_expression(key)?;
19555                self.write(": ");
19556                self.generate_expression(val)?;
19557            }
19558            self.write("}");
19559        } else {
19560            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
19561            self.write_keyword("MAP");
19562            self.write("(");
19563            self.write_keyword("ARRAY");
19564            self.write("[");
19565            for (i, key) in f.keys.iter().enumerate() {
19566                if i > 0 {
19567                    self.write(", ");
19568                }
19569                self.generate_expression(key)?;
19570            }
19571            self.write("], ");
19572            self.write_keyword("ARRAY");
19573            self.write("[");
19574            for (i, val) in f.values.iter().enumerate() {
19575                if i > 0 {
19576                    self.write(", ");
19577                }
19578                self.generate_expression(val)?;
19579            }
19580            self.write("])");
19581        }
19582        Ok(())
19583    }
19584
19585    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
19586        self.write_keyword(name);
19587        self.write("(");
19588        self.generate_expression(&f.this)?;
19589        self.write(", ");
19590        self.generate_expression(&f.transform)?;
19591        self.write(")");
19592        Ok(())
19593    }
19594
19595    // JSON function generators
19596
19597    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
19598        use crate::dialects::DialectType;
19599
19600        // Check if we should use arrow syntax (-> or ->>)
19601        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
19602
19603        if use_arrow {
19604            // Output arrow syntax: expr -> path or expr ->> path
19605            self.generate_expression(&f.this)?;
19606            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
19607                self.write(" ->> ");
19608            } else {
19609                self.write(" -> ");
19610            }
19611            self.generate_expression(&f.path)?;
19612            return Ok(());
19613        }
19614
19615        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
19616        if f.hash_arrow_syntax
19617            && matches!(
19618                self.config.dialect,
19619                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19620            )
19621        {
19622            self.generate_expression(&f.this)?;
19623            self.write(" #>> ");
19624            self.generate_expression(&f.path)?;
19625            return Ok(());
19626        }
19627
19628        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
19629        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
19630        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
19631            match name {
19632                "JSON_EXTRACT_SCALAR"
19633                | "JSON_EXTRACT_PATH_TEXT"
19634                | "JSON_EXTRACT"
19635                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
19636                _ => name,
19637            }
19638        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
19639            match name {
19640                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
19641                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
19642                _ => name,
19643            }
19644        } else {
19645            name
19646        };
19647
19648        self.write_keyword(func_name);
19649        self.write("(");
19650        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
19651        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
19652            if let Expression::Cast(ref cast) = f.this {
19653                if matches!(cast.to, crate::expressions::DataType::Json) {
19654                    self.generate_expression(&cast.this)?;
19655                } else {
19656                    self.generate_expression(&f.this)?;
19657                }
19658            } else {
19659                self.generate_expression(&f.this)?;
19660            }
19661        } else {
19662            self.generate_expression(&f.this)?;
19663        }
19664        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
19665        // decompose JSON path into separate string arguments
19666        if matches!(
19667            self.config.dialect,
19668            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19669        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
19670        {
19671            if let Expression::Literal(Literal::String(ref s)) = f.path {
19672                let parts = Self::decompose_json_path(s);
19673                for part in &parts {
19674                    self.write(", '");
19675                    self.write(part);
19676                    self.write("'");
19677                }
19678            } else {
19679                self.write(", ");
19680                self.generate_expression(&f.path)?;
19681            }
19682        } else {
19683            self.write(", ");
19684            self.generate_expression(&f.path)?;
19685        }
19686
19687        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
19688        // These go BEFORE the closing parenthesis
19689        if let Some(ref wrapper) = f.wrapper_option {
19690            self.write_space();
19691            self.write_keyword(wrapper);
19692        }
19693        if let Some(ref quotes) = f.quotes_option {
19694            self.write_space();
19695            self.write_keyword(quotes);
19696            if f.on_scalar_string {
19697                self.write_space();
19698                self.write_keyword("ON SCALAR STRING");
19699            }
19700        }
19701        if let Some(ref on_err) = f.on_error {
19702            self.write_space();
19703            self.write_keyword(on_err);
19704        }
19705        if let Some(ref ret_type) = f.returning {
19706            self.write_space();
19707            self.write_keyword("RETURNING");
19708            self.write_space();
19709            self.generate_data_type(ret_type)?;
19710        }
19711
19712        self.write(")");
19713        Ok(())
19714    }
19715
19716    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
19717    fn dialect_supports_json_arrow(&self) -> bool {
19718        use crate::dialects::DialectType;
19719        match self.config.dialect {
19720            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
19721            Some(DialectType::PostgreSQL) => true,
19722            Some(DialectType::MySQL) => true,
19723            Some(DialectType::DuckDB) => true,
19724            Some(DialectType::CockroachDB) => true,
19725            Some(DialectType::StarRocks) => true,
19726            Some(DialectType::SQLite) => true,
19727            // Other dialects use function syntax
19728            _ => false,
19729        }
19730    }
19731
19732    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
19733        use crate::dialects::DialectType;
19734
19735        // PostgreSQL uses #> operator for JSONB path extraction
19736        if matches!(
19737            self.config.dialect,
19738            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
19739        ) && name == "JSON_EXTRACT_PATH"
19740        {
19741            self.generate_expression(&f.this)?;
19742            self.write(" #> ");
19743            if f.paths.len() == 1 {
19744                self.generate_expression(&f.paths[0])?;
19745            } else {
19746                // Multiple paths: ARRAY[path1, path2, ...]
19747                self.write_keyword("ARRAY");
19748                self.write("[");
19749                for (i, path) in f.paths.iter().enumerate() {
19750                    if i > 0 {
19751                        self.write(", ");
19752                    }
19753                    self.generate_expression(path)?;
19754                }
19755                self.write("]");
19756            }
19757            return Ok(());
19758        }
19759
19760        self.write_keyword(name);
19761        self.write("(");
19762        self.generate_expression(&f.this)?;
19763        for path in &f.paths {
19764            self.write(", ");
19765            self.generate_expression(path)?;
19766        }
19767        self.write(")");
19768        Ok(())
19769    }
19770
19771    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
19772        use crate::dialects::DialectType;
19773
19774        self.write_keyword("JSON_OBJECT");
19775        self.write("(");
19776        if f.star {
19777            self.write("*");
19778        } else {
19779            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
19780            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
19781            // Also respect the json_key_value_pair_sep config
19782            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
19783                || matches!(
19784                    self.config.dialect,
19785                    Some(DialectType::BigQuery)
19786                        | Some(DialectType::MySQL)
19787                        | Some(DialectType::SQLite)
19788                );
19789
19790            for (i, (key, value)) in f.pairs.iter().enumerate() {
19791                if i > 0 {
19792                    self.write(", ");
19793                }
19794                self.generate_expression(key)?;
19795                if use_comma_syntax {
19796                    self.write(", ");
19797                } else {
19798                    self.write(": ");
19799                }
19800                self.generate_expression(value)?;
19801            }
19802        }
19803        if let Some(null_handling) = f.null_handling {
19804            self.write_space();
19805            match null_handling {
19806                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
19807                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
19808            }
19809        }
19810        if f.with_unique_keys {
19811            self.write_space();
19812            self.write_keyword("WITH UNIQUE KEYS");
19813        }
19814        if let Some(ref ret_type) = f.returning_type {
19815            self.write_space();
19816            self.write_keyword("RETURNING");
19817            self.write_space();
19818            self.generate_data_type(ret_type)?;
19819            if f.format_json {
19820                self.write_space();
19821                self.write_keyword("FORMAT JSON");
19822            }
19823            if let Some(ref enc) = f.encoding {
19824                self.write_space();
19825                self.write_keyword("ENCODING");
19826                self.write_space();
19827                self.write(enc);
19828            }
19829        }
19830        self.write(")");
19831        Ok(())
19832    }
19833
19834    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
19835        self.write_keyword(name);
19836        self.write("(");
19837        self.generate_expression(&f.this)?;
19838        for (path, value) in &f.path_values {
19839            self.write(", ");
19840            self.generate_expression(path)?;
19841            self.write(", ");
19842            self.generate_expression(value)?;
19843        }
19844        self.write(")");
19845        Ok(())
19846    }
19847
19848    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
19849        self.write_keyword("JSON_ARRAYAGG");
19850        self.write("(");
19851        self.generate_expression(&f.this)?;
19852        if let Some(ref order_by) = f.order_by {
19853            self.write_space();
19854            self.write_keyword("ORDER BY");
19855            self.write_space();
19856            for (i, ord) in order_by.iter().enumerate() {
19857                if i > 0 {
19858                    self.write(", ");
19859                }
19860                self.generate_ordered(ord)?;
19861            }
19862        }
19863        if let Some(null_handling) = f.null_handling {
19864            self.write_space();
19865            match null_handling {
19866                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
19867                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
19868            }
19869        }
19870        self.write(")");
19871        if let Some(ref filter) = f.filter {
19872            self.write_space();
19873            self.write_keyword("FILTER");
19874            self.write("(");
19875            self.write_keyword("WHERE");
19876            self.write_space();
19877            self.generate_expression(filter)?;
19878            self.write(")");
19879        }
19880        Ok(())
19881    }
19882
19883    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
19884        self.write_keyword("JSON_OBJECTAGG");
19885        self.write("(");
19886        self.generate_expression(&f.key)?;
19887        self.write(": ");
19888        self.generate_expression(&f.value)?;
19889        if let Some(null_handling) = f.null_handling {
19890            self.write_space();
19891            match null_handling {
19892                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
19893                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
19894            }
19895        }
19896        self.write(")");
19897        if let Some(ref filter) = f.filter {
19898            self.write_space();
19899            self.write_keyword("FILTER");
19900            self.write("(");
19901            self.write_keyword("WHERE");
19902            self.write_space();
19903            self.generate_expression(filter)?;
19904            self.write(")");
19905        }
19906        Ok(())
19907    }
19908
19909    // Type casting/conversion generators
19910
19911    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
19912        use crate::dialects::DialectType;
19913
19914        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
19915        if self.config.dialect == Some(DialectType::Redshift) {
19916            self.write_keyword("CAST");
19917            self.write("(");
19918            self.generate_expression(&f.this)?;
19919            self.write_space();
19920            self.write_keyword("AS");
19921            self.write_space();
19922            self.generate_data_type(&f.to)?;
19923            self.write(")");
19924            return Ok(());
19925        }
19926
19927        self.write_keyword("CONVERT");
19928        self.write("(");
19929        self.generate_data_type(&f.to)?;
19930        self.write(", ");
19931        self.generate_expression(&f.this)?;
19932        if let Some(ref style) = f.style {
19933            self.write(", ");
19934            self.generate_expression(style)?;
19935        }
19936        self.write(")");
19937        Ok(())
19938    }
19939
19940    // Additional expression generators
19941
19942    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
19943        if f.colon {
19944            // DuckDB syntax: LAMBDA x : expr
19945            self.write_keyword("LAMBDA");
19946            self.write_space();
19947            for (i, param) in f.parameters.iter().enumerate() {
19948                if i > 0 {
19949                    self.write(", ");
19950                }
19951                self.generate_identifier(param)?;
19952            }
19953            self.write(" : ");
19954        } else {
19955            // Standard syntax: x -> expr or (x, y) -> expr
19956            if f.parameters.len() == 1 {
19957                self.generate_identifier(&f.parameters[0])?;
19958            } else {
19959                self.write("(");
19960                for (i, param) in f.parameters.iter().enumerate() {
19961                    if i > 0 {
19962                        self.write(", ");
19963                    }
19964                    self.generate_identifier(param)?;
19965                }
19966                self.write(")");
19967            }
19968            self.write(" -> ");
19969        }
19970        self.generate_expression(&f.body)
19971    }
19972
19973    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
19974        self.generate_identifier(&f.name)?;
19975        match f.separator {
19976            NamedArgSeparator::DArrow => self.write(" => "),
19977            NamedArgSeparator::ColonEq => self.write(" := "),
19978            NamedArgSeparator::Eq => self.write(" = "),
19979        }
19980        self.generate_expression(&f.value)
19981    }
19982
19983    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
19984        self.write_keyword(&f.prefix);
19985        self.write(" ");
19986        self.generate_expression(&f.this)
19987    }
19988
19989    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
19990        match f.style {
19991            ParameterStyle::Question => self.write("?"),
19992            ParameterStyle::Dollar => {
19993                self.write("$");
19994                if let Some(idx) = f.index {
19995                    self.write(&idx.to_string());
19996                } else if let Some(ref name) = f.name {
19997                    // Session variable like $x or $query_id
19998                    self.write(name);
19999                }
20000            }
20001            ParameterStyle::DollarBrace => {
20002                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
20003                self.write("${");
20004                if let Some(ref name) = f.name {
20005                    self.write(name);
20006                }
20007                if let Some(ref expr) = f.expression {
20008                    self.write(":");
20009                    self.write(expr);
20010                }
20011                self.write("}");
20012            }
20013            ParameterStyle::Colon => {
20014                self.write(":");
20015                if let Some(idx) = f.index {
20016                    self.write(&idx.to_string());
20017                } else if let Some(ref name) = f.name {
20018                    self.write(name);
20019                }
20020            }
20021            ParameterStyle::At => {
20022                self.write("@");
20023                if let Some(ref name) = f.name {
20024                    if f.string_quoted {
20025                        self.write("'");
20026                        self.write(name);
20027                        self.write("'");
20028                    } else if f.quoted {
20029                        self.write("\"");
20030                        self.write(name);
20031                        self.write("\"");
20032                    } else {
20033                        self.write(name);
20034                    }
20035                }
20036            }
20037            ParameterStyle::DoubleAt => {
20038                self.write("@@");
20039                if let Some(ref name) = f.name {
20040                    self.write(name);
20041                }
20042            }
20043            ParameterStyle::DoubleDollar => {
20044                self.write("$$");
20045                if let Some(ref name) = f.name {
20046                    self.write(name);
20047                }
20048            }
20049            ParameterStyle::Percent => {
20050                if let Some(ref name) = f.name {
20051                    // %(name)s format
20052                    self.write("%(");
20053                    self.write(name);
20054                    self.write(")s");
20055                } else {
20056                    // %s format
20057                    self.write("%s");
20058                }
20059            }
20060            ParameterStyle::Brace => {
20061                // Spark/Databricks widget template variable: {name}
20062                // ClickHouse query parameter may include kind: {name: Type}
20063                self.write("{");
20064                if let Some(ref name) = f.name {
20065                    self.write(name);
20066                }
20067                if let Some(ref expr) = f.expression {
20068                    self.write(": ");
20069                    self.write(expr);
20070                }
20071                self.write("}");
20072            }
20073        }
20074        Ok(())
20075    }
20076
20077    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
20078        self.write("?");
20079        if let Some(idx) = f.index {
20080            self.write(&idx.to_string());
20081        }
20082        Ok(())
20083    }
20084
20085    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
20086        if f.is_block {
20087            self.write("/*");
20088            self.write(&f.text);
20089            self.write("*/");
20090        } else {
20091            self.write("--");
20092            self.write(&f.text);
20093        }
20094        Ok(())
20095    }
20096
20097    // Additional predicate generators
20098
20099    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
20100        self.generate_expression(&f.this)?;
20101        if f.not {
20102            self.write_space();
20103            self.write_keyword("NOT");
20104        }
20105        self.write_space();
20106        self.write_keyword("SIMILAR TO");
20107        self.write_space();
20108        self.generate_expression(&f.pattern)?;
20109        if let Some(ref escape) = f.escape {
20110            self.write_space();
20111            self.write_keyword("ESCAPE");
20112            self.write_space();
20113            self.generate_expression(escape)?;
20114        }
20115        Ok(())
20116    }
20117
20118    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
20119        self.generate_expression(&f.this)?;
20120        self.write_space();
20121        // Output comparison operator if present
20122        if let Some(op) = &f.op {
20123            match op {
20124                QuantifiedOp::Eq => self.write("="),
20125                QuantifiedOp::Neq => self.write("<>"),
20126                QuantifiedOp::Lt => self.write("<"),
20127                QuantifiedOp::Lte => self.write("<="),
20128                QuantifiedOp::Gt => self.write(">"),
20129                QuantifiedOp::Gte => self.write(">="),
20130            }
20131            self.write_space();
20132        }
20133        self.write_keyword(name);
20134
20135        // If the child is a Subquery, it provides its own parens — output with space
20136        if matches!(&f.subquery, Expression::Subquery(_)) {
20137            self.write_space();
20138            self.generate_expression(&f.subquery)?;
20139        } else {
20140            self.write("(");
20141
20142            let is_statement = matches!(
20143                &f.subquery,
20144                Expression::Select(_)
20145                    | Expression::Union(_)
20146                    | Expression::Intersect(_)
20147                    | Expression::Except(_)
20148            );
20149
20150            if self.config.pretty && is_statement {
20151                self.write_newline();
20152                self.indent_level += 1;
20153                self.write_indent();
20154            }
20155            self.generate_expression(&f.subquery)?;
20156            if self.config.pretty && is_statement {
20157                self.write_newline();
20158                self.indent_level -= 1;
20159                self.write_indent();
20160            }
20161            self.write(")");
20162        }
20163        Ok(())
20164    }
20165
20166    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
20167        // Check if this is a simple binary form (this OVERLAPS expression)
20168        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
20169            self.generate_expression(this)?;
20170            self.write_space();
20171            self.write_keyword("OVERLAPS");
20172            self.write_space();
20173            self.generate_expression(expr)?;
20174        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
20175            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
20176        {
20177            // Full ANSI form: (a, b) OVERLAPS (c, d)
20178            self.write("(");
20179            self.generate_expression(ls)?;
20180            self.write(", ");
20181            self.generate_expression(le)?;
20182            self.write(")");
20183            self.write_space();
20184            self.write_keyword("OVERLAPS");
20185            self.write_space();
20186            self.write("(");
20187            self.generate_expression(rs)?;
20188            self.write(", ");
20189            self.generate_expression(re)?;
20190            self.write(")");
20191        }
20192        Ok(())
20193    }
20194
20195    // Type conversion generators
20196
20197    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
20198        use crate::dialects::DialectType;
20199
20200        // SingleStore uses !:> syntax for try cast
20201        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
20202            self.generate_expression(&cast.this)?;
20203            self.write(" !:> ");
20204            self.generate_data_type(&cast.to)?;
20205            return Ok(());
20206        }
20207
20208        // Teradata uses TRYCAST (no underscore)
20209        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
20210            self.write_keyword("TRYCAST");
20211            self.write("(");
20212            self.generate_expression(&cast.this)?;
20213            self.write_space();
20214            self.write_keyword("AS");
20215            self.write_space();
20216            self.generate_data_type(&cast.to)?;
20217            self.write(")");
20218            return Ok(());
20219        }
20220
20221        // Dialects without TRY_CAST: generate as regular CAST
20222        let keyword = if matches!(
20223            self.config.dialect,
20224            Some(DialectType::Hive)
20225                | Some(DialectType::MySQL)
20226                | Some(DialectType::SQLite)
20227                | Some(DialectType::Oracle)
20228                | Some(DialectType::ClickHouse)
20229                | Some(DialectType::Redshift)
20230                | Some(DialectType::PostgreSQL)
20231                | Some(DialectType::StarRocks)
20232                | Some(DialectType::Doris)
20233        ) {
20234            "CAST"
20235        } else {
20236            "TRY_CAST"
20237        };
20238
20239        self.write_keyword(keyword);
20240        self.write("(");
20241        self.generate_expression(&cast.this)?;
20242        self.write_space();
20243        self.write_keyword("AS");
20244        self.write_space();
20245        self.generate_data_type(&cast.to)?;
20246
20247        // Output FORMAT clause if present
20248        if let Some(format) = &cast.format {
20249            self.write_space();
20250            self.write_keyword("FORMAT");
20251            self.write_space();
20252            self.generate_expression(format)?;
20253        }
20254
20255        self.write(")");
20256        Ok(())
20257    }
20258
20259    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
20260        self.write_keyword("SAFE_CAST");
20261        self.write("(");
20262        self.generate_expression(&cast.this)?;
20263        self.write_space();
20264        self.write_keyword("AS");
20265        self.write_space();
20266        self.generate_data_type(&cast.to)?;
20267
20268        // Output FORMAT clause if present
20269        if let Some(format) = &cast.format {
20270            self.write_space();
20271            self.write_keyword("FORMAT");
20272            self.write_space();
20273            self.generate_expression(format)?;
20274        }
20275
20276        self.write(")");
20277        Ok(())
20278    }
20279
20280    // Array/struct/map access generators
20281
20282    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
20283        self.generate_expression(&s.this)?;
20284        self.write("[");
20285        self.generate_expression(&s.index)?;
20286        self.write("]");
20287        Ok(())
20288    }
20289
20290    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
20291        self.generate_expression(&d.this)?;
20292        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
20293        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
20294        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
20295            && matches!(
20296                &d.this,
20297                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
20298            );
20299        if use_colon {
20300            self.write(":");
20301        } else {
20302            self.write(".");
20303        }
20304        self.generate_identifier(&d.field)
20305    }
20306
20307    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
20308        self.generate_expression(&m.this)?;
20309        self.write(".");
20310        // Method names after a dot should not be quoted based on reserved keywords
20311        // Only quote if explicitly marked as quoted in the AST
20312        if m.method.quoted {
20313            let q = self.config.identifier_quote;
20314            self.write(&format!("{}{}{}", q, m.method.name, q));
20315        } else {
20316            self.write(&m.method.name);
20317        }
20318        self.write("(");
20319        for (i, arg) in m.args.iter().enumerate() {
20320            if i > 0 {
20321                self.write(", ");
20322            }
20323            self.generate_expression(arg)?;
20324        }
20325        self.write(")");
20326        Ok(())
20327    }
20328
20329    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
20330        // Check if we need to wrap the inner expression in parentheses
20331        // JSON arrow expressions have lower precedence than array subscript
20332        let needs_parens = matches!(
20333            &s.this,
20334            Expression::JsonExtract(f) if f.arrow_syntax
20335        ) || matches!(
20336            &s.this,
20337            Expression::JsonExtractScalar(f) if f.arrow_syntax
20338        );
20339
20340        if needs_parens {
20341            self.write("(");
20342        }
20343        self.generate_expression(&s.this)?;
20344        if needs_parens {
20345            self.write(")");
20346        }
20347        self.write("[");
20348        if let Some(start) = &s.start {
20349            self.generate_expression(start)?;
20350        }
20351        self.write(":");
20352        if let Some(end) = &s.end {
20353            self.generate_expression(end)?;
20354        }
20355        self.write("]");
20356        Ok(())
20357    }
20358
20359    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
20360        // Generate left expression, but skip trailing comments if they're already in left_comments
20361        // to avoid duplication (comments are captured as both expr.trailing_comments
20362        // and BinaryOp.left_comments during parsing)
20363        match &op.left {
20364            Expression::Column(col) => {
20365                // Generate column with trailing comments but skip them if they're
20366                // already captured in BinaryOp.left_comments to avoid duplication
20367                if let Some(table) = &col.table {
20368                    self.generate_identifier(table)?;
20369                    self.write(".");
20370                }
20371                self.generate_identifier(&col.name)?;
20372                // Oracle-style join marker (+)
20373                if col.join_mark && self.config.supports_column_join_marks {
20374                    self.write(" (+)");
20375                }
20376                // Output column trailing comments if they're not already in left_comments
20377                if op.left_comments.is_empty() {
20378                    for comment in &col.trailing_comments {
20379                        self.write_space();
20380                        self.write_formatted_comment(comment);
20381                    }
20382                }
20383            }
20384            Expression::Add(inner_op)
20385            | Expression::Sub(inner_op)
20386            | Expression::Mul(inner_op)
20387            | Expression::Div(inner_op)
20388            | Expression::Concat(inner_op) => {
20389                // Generate binary op without its trailing comments
20390                self.generate_binary_op_no_trailing(inner_op, match &op.left {
20391                    Expression::Add(_) => "+",
20392                    Expression::Sub(_) => "-",
20393                    Expression::Mul(_) => "*",
20394                    Expression::Div(_) => "/",
20395                    Expression::Concat(_) => "||",
20396                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
20397                })?;
20398            }
20399            _ => {
20400                self.generate_expression(&op.left)?;
20401            }
20402        }
20403        // Output comments after left operand
20404        for comment in &op.left_comments {
20405            self.write_space();
20406            self.write_formatted_comment(comment);
20407        }
20408        if self.config.pretty
20409            && matches!(self.config.dialect, Some(DialectType::Snowflake))
20410            && (operator == "AND" || operator == "OR")
20411        {
20412            self.write_newline();
20413            self.write_indent();
20414            self.write_keyword(operator);
20415        } else {
20416            self.write_space();
20417            if operator.chars().all(|c| c.is_alphabetic()) {
20418                self.write_keyword(operator);
20419            } else {
20420                self.write(operator);
20421            }
20422        }
20423        // Output comments after operator (before right operand)
20424        for comment in &op.operator_comments {
20425            self.write_space();
20426            self.write_formatted_comment(comment);
20427        }
20428        self.write_space();
20429        self.generate_expression(&op.right)?;
20430        // Output trailing comments after right operand
20431        for comment in &op.trailing_comments {
20432            self.write_space();
20433            self.write_formatted_comment(comment);
20434        }
20435        Ok(())
20436    }
20437
20438    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
20439        let keyword = connector.keyword();
20440        let Some(terms) = self.flatten_connector_terms(op, connector) else {
20441            return self.generate_binary_op(op, keyword);
20442        };
20443
20444        self.generate_expression(terms[0])?;
20445        for term in terms.iter().skip(1) {
20446            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
20447                self.write_newline();
20448                self.write_indent();
20449                self.write_keyword(keyword);
20450            } else {
20451                self.write_space();
20452                self.write_keyword(keyword);
20453            }
20454            self.write_space();
20455            self.generate_expression(term)?;
20456        }
20457
20458        Ok(())
20459    }
20460
20461    fn flatten_connector_terms<'a>(
20462        &self,
20463        root: &'a BinaryOp,
20464        connector: ConnectorOperator,
20465    ) -> Option<Vec<&'a Expression>> {
20466        if !root.left_comments.is_empty()
20467            || !root.operator_comments.is_empty()
20468            || !root.trailing_comments.is_empty()
20469        {
20470            return None;
20471        }
20472
20473        let mut terms = Vec::new();
20474        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
20475
20476        while let Some(expr) = stack.pop() {
20477            match (connector, expr) {
20478                (ConnectorOperator::And, Expression::And(inner))
20479                    if inner.left_comments.is_empty()
20480                        && inner.operator_comments.is_empty()
20481                        && inner.trailing_comments.is_empty() =>
20482                {
20483                    stack.push(&inner.right);
20484                    stack.push(&inner.left);
20485                }
20486                (ConnectorOperator::Or, Expression::Or(inner))
20487                    if inner.left_comments.is_empty()
20488                        && inner.operator_comments.is_empty()
20489                        && inner.trailing_comments.is_empty() =>
20490                {
20491                    stack.push(&inner.right);
20492                    stack.push(&inner.left);
20493                }
20494                _ => terms.push(expr),
20495            }
20496        }
20497
20498        if terms.len() > 1 {
20499            Some(terms)
20500        } else {
20501            None
20502        }
20503    }
20504
20505    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
20506    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
20507        self.generate_expression(&op.left)?;
20508        self.write_space();
20509        // Drill backtick-quotes ILIKE
20510        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
20511            self.write("`ILIKE`");
20512        } else {
20513            self.write_keyword(operator);
20514        }
20515        if let Some(quantifier) = &op.quantifier {
20516            self.write_space();
20517            self.write_keyword(quantifier);
20518        }
20519        self.write_space();
20520        self.generate_expression(&op.right)?;
20521        if let Some(escape) = &op.escape {
20522            self.write_space();
20523            self.write_keyword("ESCAPE");
20524            self.write_space();
20525            self.generate_expression(escape)?;
20526        }
20527        Ok(())
20528    }
20529
20530    /// Generate null-safe equality
20531    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
20532    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
20533        use crate::dialects::DialectType;
20534        self.generate_expression(&op.left)?;
20535        self.write_space();
20536        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
20537            self.write("<=>");
20538        } else {
20539            self.write_keyword("IS NOT DISTINCT FROM");
20540        }
20541        self.write_space();
20542        self.generate_expression(&op.right)?;
20543        Ok(())
20544    }
20545
20546    /// Generate IS DISTINCT FROM (null-safe inequality)
20547    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
20548        self.generate_expression(&op.left)?;
20549        self.write_space();
20550        self.write_keyword("IS DISTINCT FROM");
20551        self.write_space();
20552        self.generate_expression(&op.right)?;
20553        Ok(())
20554    }
20555
20556    /// Generate binary op without trailing comments (used when nested inside another binary op)
20557    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
20558        // Generate left expression, but skip trailing comments
20559        match &op.left {
20560            Expression::Column(col) => {
20561                if let Some(table) = &col.table {
20562                    self.generate_identifier(table)?;
20563                    self.write(".");
20564                }
20565                self.generate_identifier(&col.name)?;
20566                // Oracle-style join marker (+)
20567                if col.join_mark && self.config.supports_column_join_marks {
20568                    self.write(" (+)");
20569                }
20570            }
20571            Expression::Add(inner_op)
20572            | Expression::Sub(inner_op)
20573            | Expression::Mul(inner_op)
20574            | Expression::Div(inner_op)
20575            | Expression::Concat(inner_op) => {
20576                self.generate_binary_op_no_trailing(inner_op, match &op.left {
20577                    Expression::Add(_) => "+",
20578                    Expression::Sub(_) => "-",
20579                    Expression::Mul(_) => "*",
20580                    Expression::Div(_) => "/",
20581                    Expression::Concat(_) => "||",
20582                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
20583                })?;
20584            }
20585            _ => {
20586                self.generate_expression(&op.left)?;
20587            }
20588        }
20589        // Output left_comments
20590        for comment in &op.left_comments {
20591            self.write_space();
20592            self.write_formatted_comment(comment);
20593        }
20594        self.write_space();
20595        if operator.chars().all(|c| c.is_alphabetic()) {
20596            self.write_keyword(operator);
20597        } else {
20598            self.write(operator);
20599        }
20600        // Output operator_comments
20601        for comment in &op.operator_comments {
20602            self.write_space();
20603            self.write_formatted_comment(comment);
20604        }
20605        self.write_space();
20606        // Generate right expression, but skip trailing comments if it's a Column
20607        // (the parent's left_comments will output them)
20608        match &op.right {
20609            Expression::Column(col) => {
20610                if let Some(table) = &col.table {
20611                    self.generate_identifier(table)?;
20612                    self.write(".");
20613                }
20614                self.generate_identifier(&col.name)?;
20615                // Oracle-style join marker (+)
20616                if col.join_mark && self.config.supports_column_join_marks {
20617                    self.write(" (+)");
20618                }
20619            }
20620            _ => {
20621                self.generate_expression(&op.right)?;
20622            }
20623        }
20624        // Skip trailing_comments - parent will handle them via its left_comments
20625        Ok(())
20626    }
20627
20628    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
20629        if operator.chars().all(|c| c.is_alphabetic()) {
20630            self.write_keyword(operator);
20631            self.write_space();
20632        } else {
20633            self.write(operator);
20634            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
20635            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
20636                self.write_space();
20637            }
20638        }
20639        self.generate_expression(&op.this)
20640    }
20641
20642    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
20643        // Generic mode supports two styles for negated IN:
20644        // - Prefix: NOT a IN (...)
20645        // - Infix:  a NOT IN (...)
20646        let is_generic =
20647            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
20648        let use_prefix_not =
20649            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
20650        if use_prefix_not {
20651            self.write_keyword("NOT");
20652            self.write_space();
20653        }
20654        self.generate_expression(&in_expr.this)?;
20655        if in_expr.global {
20656            self.write_space();
20657            self.write_keyword("GLOBAL");
20658        }
20659        if in_expr.not && !use_prefix_not {
20660            self.write_space();
20661            self.write_keyword("NOT");
20662        }
20663        self.write_space();
20664        self.write_keyword("IN");
20665
20666        // BigQuery: IN UNNEST(expr)
20667        if let Some(unnest_expr) = &in_expr.unnest {
20668            self.write_space();
20669            self.write_keyword("UNNEST");
20670            self.write("(");
20671            self.generate_expression(unnest_expr)?;
20672            self.write(")");
20673            return Ok(());
20674        }
20675
20676        if let Some(query) = &in_expr.query {
20677            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
20678            // vs a subquery (col IN (SELECT ...))
20679            let is_bare = in_expr.expressions.is_empty()
20680                && !matches!(
20681                    query,
20682                    Expression::Select(_)
20683                        | Expression::Union(_)
20684                        | Expression::Intersect(_)
20685                        | Expression::Except(_)
20686                        | Expression::Subquery(_)
20687                );
20688            if is_bare {
20689                // Bare identifier: no parentheses
20690                self.write_space();
20691                self.generate_expression(query)?;
20692            } else {
20693                // Subquery: with parentheses
20694                self.write(" (");
20695                let is_statement = matches!(
20696                    query,
20697                    Expression::Select(_)
20698                        | Expression::Union(_)
20699                        | Expression::Intersect(_)
20700                        | Expression::Except(_)
20701                        | Expression::Subquery(_)
20702                );
20703                if self.config.pretty && is_statement {
20704                    self.write_newline();
20705                    self.indent_level += 1;
20706                    self.write_indent();
20707                }
20708                self.generate_expression(query)?;
20709                if self.config.pretty && is_statement {
20710                    self.write_newline();
20711                    self.indent_level -= 1;
20712                    self.write_indent();
20713                }
20714                self.write(")");
20715            }
20716        } else {
20717            // DuckDB: IN without parentheses for single expression that is NOT a literal
20718            // (array/list membership like 'red' IN tbl.flags)
20719            // ClickHouse: IN without parentheses for single non-array expressions
20720            let is_duckdb = matches!(
20721                self.config.dialect,
20722                Some(crate::dialects::DialectType::DuckDB)
20723            );
20724            let is_clickhouse = matches!(
20725                self.config.dialect,
20726                Some(crate::dialects::DialectType::ClickHouse)
20727            );
20728            let single_expr = in_expr.expressions.len() == 1;
20729            if is_clickhouse && single_expr {
20730                if let Expression::Array(arr) = &in_expr.expressions[0] {
20731                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
20732                    self.write(" (");
20733                    for (i, expr) in arr.expressions.iter().enumerate() {
20734                        if i > 0 {
20735                            self.write(", ");
20736                        }
20737                        self.generate_expression(expr)?;
20738                    }
20739                    self.write(")");
20740                } else {
20741                    self.write_space();
20742                    self.generate_expression(&in_expr.expressions[0])?;
20743                }
20744            } else {
20745                let is_bare_ref = single_expr
20746                    && matches!(
20747                        &in_expr.expressions[0],
20748                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
20749                    );
20750                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
20751                    // Bare field reference (no parens in source): IN identifier
20752                    // Also DuckDB: IN without parentheses for array/list membership
20753                    self.write_space();
20754                    self.generate_expression(&in_expr.expressions[0])?;
20755                } else {
20756                    // Standard IN (list)
20757                    self.write(" (");
20758                    for (i, expr) in in_expr.expressions.iter().enumerate() {
20759                        if i > 0 {
20760                            self.write(", ");
20761                        }
20762                        self.generate_expression(expr)?;
20763                    }
20764                    self.write(")");
20765                }
20766            }
20767        }
20768
20769        Ok(())
20770    }
20771
20772    fn generate_between(&mut self, between: &Between) -> Result<()> {
20773        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
20774        let use_prefix_not = between.not
20775            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
20776        if use_prefix_not {
20777            self.write_keyword("NOT");
20778            self.write_space();
20779        }
20780        self.generate_expression(&between.this)?;
20781        if between.not && !use_prefix_not {
20782            self.write_space();
20783            self.write_keyword("NOT");
20784        }
20785        self.write_space();
20786        self.write_keyword("BETWEEN");
20787        // Emit SYMMETRIC/ASYMMETRIC if present
20788        if let Some(sym) = between.symmetric {
20789            if sym {
20790                self.write(" SYMMETRIC");
20791            } else {
20792                self.write(" ASYMMETRIC");
20793            }
20794        }
20795        self.write_space();
20796        self.generate_expression(&between.low)?;
20797        self.write_space();
20798        self.write_keyword("AND");
20799        self.write_space();
20800        self.generate_expression(&between.high)
20801    }
20802
20803    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
20804        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
20805        let use_prefix_not = is_null.not
20806            && (self.config.dialect.is_none()
20807                || self.config.dialect == Some(DialectType::Generic)
20808                || is_null.postfix_form);
20809        if use_prefix_not {
20810            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
20811            self.write_keyword("NOT");
20812            self.write_space();
20813            self.generate_expression(&is_null.this)?;
20814            self.write_space();
20815            self.write_keyword("IS");
20816            self.write_space();
20817            self.write_keyword("NULL");
20818        } else {
20819            self.generate_expression(&is_null.this)?;
20820            self.write_space();
20821            self.write_keyword("IS");
20822            if is_null.not {
20823                self.write_space();
20824                self.write_keyword("NOT");
20825            }
20826            self.write_space();
20827            self.write_keyword("NULL");
20828        }
20829        Ok(())
20830    }
20831
20832    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
20833        self.generate_expression(&is_true.this)?;
20834        self.write_space();
20835        self.write_keyword("IS");
20836        if is_true.not {
20837            self.write_space();
20838            self.write_keyword("NOT");
20839        }
20840        self.write_space();
20841        self.write_keyword("TRUE");
20842        Ok(())
20843    }
20844
20845    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
20846        self.generate_expression(&is_false.this)?;
20847        self.write_space();
20848        self.write_keyword("IS");
20849        if is_false.not {
20850            self.write_space();
20851            self.write_keyword("NOT");
20852        }
20853        self.write_space();
20854        self.write_keyword("FALSE");
20855        Ok(())
20856    }
20857
20858    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
20859        self.generate_expression(&is_json.this)?;
20860        self.write_space();
20861        self.write_keyword("IS");
20862        if is_json.negated {
20863            self.write_space();
20864            self.write_keyword("NOT");
20865        }
20866        self.write_space();
20867        self.write_keyword("JSON");
20868
20869        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
20870        if let Some(ref json_type) = is_json.json_type {
20871            self.write_space();
20872            self.write_keyword(json_type);
20873        }
20874
20875        // Output key uniqueness constraint if specified
20876        match &is_json.unique_keys {
20877            Some(JsonUniqueKeys::With) => {
20878                self.write_space();
20879                self.write_keyword("WITH UNIQUE KEYS");
20880            }
20881            Some(JsonUniqueKeys::Without) => {
20882                self.write_space();
20883                self.write_keyword("WITHOUT UNIQUE KEYS");
20884            }
20885            Some(JsonUniqueKeys::Shorthand) => {
20886                self.write_space();
20887                self.write_keyword("UNIQUE KEYS");
20888            }
20889            None => {}
20890        }
20891
20892        Ok(())
20893    }
20894
20895    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
20896        self.generate_expression(&is_expr.left)?;
20897        self.write_space();
20898        self.write_keyword("IS");
20899        self.write_space();
20900        self.generate_expression(&is_expr.right)
20901    }
20902
20903    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
20904        if exists.not {
20905            self.write_keyword("NOT");
20906            self.write_space();
20907        }
20908        self.write_keyword("EXISTS");
20909        self.write("(");
20910        let is_statement = matches!(
20911            &exists.this,
20912            Expression::Select(_)
20913                | Expression::Union(_)
20914                | Expression::Intersect(_)
20915                | Expression::Except(_)
20916        );
20917        if self.config.pretty && is_statement {
20918            self.write_newline();
20919            self.indent_level += 1;
20920            self.write_indent();
20921            self.generate_expression(&exists.this)?;
20922            self.write_newline();
20923            self.indent_level -= 1;
20924            self.write_indent();
20925            self.write(")");
20926        } else {
20927            self.generate_expression(&exists.this)?;
20928            self.write(")");
20929        }
20930        Ok(())
20931    }
20932
20933    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
20934        self.generate_expression(&op.left)?;
20935        self.write_space();
20936        self.write_keyword("MEMBER OF");
20937        self.write("(");
20938        self.generate_expression(&op.right)?;
20939        self.write(")");
20940        Ok(())
20941    }
20942
20943    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
20944        if subquery.lateral {
20945            self.write_keyword("LATERAL");
20946            self.write_space();
20947        }
20948
20949        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
20950        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
20951        // to carry the LIMIT modifier without adding more parens
20952        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
20953            matches!(
20954                &p.this,
20955                Expression::Select(_)
20956                    | Expression::Union(_)
20957                    | Expression::Intersect(_)
20958                    | Expression::Except(_)
20959                    | Expression::Subquery(_)
20960            )
20961        } else {
20962            false
20963        };
20964
20965        // Check if inner expression is a statement for pretty formatting
20966        let is_statement = matches!(
20967            &subquery.this,
20968            Expression::Select(_)
20969                | Expression::Union(_)
20970                | Expression::Intersect(_)
20971                | Expression::Except(_)
20972                | Expression::Merge(_)
20973        );
20974
20975        if !skip_outer_parens {
20976            self.write("(");
20977            if self.config.pretty && is_statement {
20978                self.write_newline();
20979                self.indent_level += 1;
20980                self.write_indent();
20981            }
20982        }
20983        self.generate_expression(&subquery.this)?;
20984
20985        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
20986        if subquery.modifiers_inside {
20987            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
20988            if let Some(order_by) = &subquery.order_by {
20989                self.write_space();
20990                self.write_keyword("ORDER BY");
20991                self.write_space();
20992                for (i, ord) in order_by.expressions.iter().enumerate() {
20993                    if i > 0 {
20994                        self.write(", ");
20995                    }
20996                    self.generate_ordered(ord)?;
20997                }
20998            }
20999
21000            if let Some(limit) = &subquery.limit {
21001                self.write_space();
21002                self.write_keyword("LIMIT");
21003                self.write_space();
21004                self.generate_expression(&limit.this)?;
21005                if limit.percent {
21006                    self.write_space();
21007                    self.write_keyword("PERCENT");
21008                }
21009            }
21010
21011            if let Some(offset) = &subquery.offset {
21012                self.write_space();
21013                self.write_keyword("OFFSET");
21014                self.write_space();
21015                self.generate_expression(&offset.this)?;
21016            }
21017        }
21018
21019        if !skip_outer_parens {
21020            if self.config.pretty && is_statement {
21021                self.write_newline();
21022                self.indent_level -= 1;
21023                self.write_indent();
21024            }
21025            self.write(")");
21026        }
21027
21028        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
21029        if !subquery.modifiers_inside {
21030            if let Some(order_by) = &subquery.order_by {
21031                self.write_space();
21032                self.write_keyword("ORDER BY");
21033                self.write_space();
21034                for (i, ord) in order_by.expressions.iter().enumerate() {
21035                    if i > 0 {
21036                        self.write(", ");
21037                    }
21038                    self.generate_ordered(ord)?;
21039                }
21040            }
21041
21042            if let Some(limit) = &subquery.limit {
21043                self.write_space();
21044                self.write_keyword("LIMIT");
21045                self.write_space();
21046                self.generate_expression(&limit.this)?;
21047                if limit.percent {
21048                    self.write_space();
21049                    self.write_keyword("PERCENT");
21050                }
21051            }
21052
21053            if let Some(offset) = &subquery.offset {
21054                self.write_space();
21055                self.write_keyword("OFFSET");
21056                self.write_space();
21057                self.generate_expression(&offset.this)?;
21058            }
21059
21060            // Generate DISTRIBUTE BY (Hive/Spark)
21061            if let Some(distribute_by) = &subquery.distribute_by {
21062                self.write_space();
21063                self.write_keyword("DISTRIBUTE BY");
21064                self.write_space();
21065                for (i, expr) in distribute_by.expressions.iter().enumerate() {
21066                    if i > 0 {
21067                        self.write(", ");
21068                    }
21069                    self.generate_expression(expr)?;
21070                }
21071            }
21072
21073            // Generate SORT BY (Hive/Spark)
21074            if let Some(sort_by) = &subquery.sort_by {
21075                self.write_space();
21076                self.write_keyword("SORT BY");
21077                self.write_space();
21078                for (i, ord) in sort_by.expressions.iter().enumerate() {
21079                    if i > 0 {
21080                        self.write(", ");
21081                    }
21082                    self.generate_ordered(ord)?;
21083                }
21084            }
21085
21086            // Generate CLUSTER BY (Hive/Spark)
21087            if let Some(cluster_by) = &subquery.cluster_by {
21088                self.write_space();
21089                self.write_keyword("CLUSTER BY");
21090                self.write_space();
21091                for (i, ord) in cluster_by.expressions.iter().enumerate() {
21092                    if i > 0 {
21093                        self.write(", ");
21094                    }
21095                    self.generate_ordered(ord)?;
21096                }
21097            }
21098        }
21099
21100        if let Some(alias) = &subquery.alias {
21101            self.write_space();
21102            // Oracle doesn't use AS for subquery aliases
21103            let skip_as = matches!(
21104                self.config.dialect,
21105                Some(crate::dialects::DialectType::Oracle)
21106            );
21107            if !skip_as {
21108                self.write_keyword("AS");
21109                self.write_space();
21110            }
21111            self.generate_identifier(alias)?;
21112            if !subquery.column_aliases.is_empty() {
21113                self.write("(");
21114                for (i, col) in subquery.column_aliases.iter().enumerate() {
21115                    if i > 0 {
21116                        self.write(", ");
21117                    }
21118                    self.generate_identifier(col)?;
21119                }
21120                self.write(")");
21121            }
21122        }
21123        // Output trailing comments
21124        for comment in &subquery.trailing_comments {
21125            self.write(" ");
21126            self.write_formatted_comment(comment);
21127        }
21128        Ok(())
21129    }
21130
21131    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
21132        // Generate WITH clause if present
21133        if let Some(ref with) = pivot.with {
21134            self.generate_with(with)?;
21135            self.write_space();
21136        }
21137
21138        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
21139
21140        // Check for Redshift UNPIVOT in FROM clause:
21141        // UNPIVOT expr [AS val AT attr]
21142        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
21143        let is_redshift_unpivot = pivot.unpivot
21144            && pivot.expressions.is_empty()
21145            && pivot.fields.is_empty()
21146            && pivot.using.is_empty()
21147            && pivot.into.is_none()
21148            && !matches!(&pivot.this, Expression::Null(_));
21149
21150        if is_redshift_unpivot {
21151            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
21152            self.write_keyword("UNPIVOT");
21153            self.write_space();
21154            self.generate_expression(&pivot.this)?;
21155            // Alias - for Redshift it can be "val AT attr" format
21156            if let Some(alias) = &pivot.alias {
21157                self.write_space();
21158                self.write_keyword("AS");
21159                self.write_space();
21160                // The alias might contain " AT " for the attr part
21161                self.write(&alias.name);
21162            }
21163            return Ok(());
21164        }
21165
21166        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
21167        let is_simplified = !pivot.using.is_empty()
21168            || pivot.into.is_some()
21169            || (pivot.fields.is_empty()
21170                && !pivot.expressions.is_empty()
21171                && !matches!(&pivot.this, Expression::Null(_)));
21172
21173        if is_simplified {
21174            // DuckDB simplified syntax:
21175            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
21176            //   UNPIVOT table ON cols INTO NAME col VALUE col
21177            self.write_keyword(direction);
21178            self.write_space();
21179            self.generate_expression(&pivot.this)?;
21180
21181            if !pivot.expressions.is_empty() {
21182                self.write_space();
21183                self.write_keyword("ON");
21184                self.write_space();
21185                for (i, expr) in pivot.expressions.iter().enumerate() {
21186                    if i > 0 {
21187                        self.write(", ");
21188                    }
21189                    self.generate_expression(expr)?;
21190                }
21191            }
21192
21193            // INTO (for UNPIVOT)
21194            if let Some(into) = &pivot.into {
21195                self.write_space();
21196                self.write_keyword("INTO");
21197                self.write_space();
21198                self.generate_expression(into)?;
21199            }
21200
21201            // USING (for PIVOT)
21202            if !pivot.using.is_empty() {
21203                self.write_space();
21204                self.write_keyword("USING");
21205                self.write_space();
21206                for (i, expr) in pivot.using.iter().enumerate() {
21207                    if i > 0 {
21208                        self.write(", ");
21209                    }
21210                    self.generate_expression(expr)?;
21211                }
21212            }
21213
21214            // GROUP BY
21215            if let Some(group) = &pivot.group {
21216                self.write_space();
21217                self.generate_expression(group)?;
21218            }
21219        } else {
21220            // Standard syntax:
21221            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
21222            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
21223            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
21224            if !matches!(&pivot.this, Expression::Null(_)) {
21225                self.generate_expression(&pivot.this)?;
21226                self.write_space();
21227            }
21228            self.write_keyword(direction);
21229            self.write("(");
21230
21231            // Aggregation expressions
21232            for (i, expr) in pivot.expressions.iter().enumerate() {
21233                if i > 0 {
21234                    self.write(", ");
21235                }
21236                self.generate_expression(expr)?;
21237            }
21238
21239            // FOR...IN fields
21240            if !pivot.fields.is_empty() {
21241                if !pivot.expressions.is_empty() {
21242                    self.write_space();
21243                }
21244                self.write_keyword("FOR");
21245                self.write_space();
21246                for (i, field) in pivot.fields.iter().enumerate() {
21247                    if i > 0 {
21248                        self.write_space();
21249                    }
21250                    // field is an In expression: column IN (values)
21251                    self.generate_expression(field)?;
21252                }
21253            }
21254
21255            // DEFAULT ON NULL
21256            if let Some(default_val) = &pivot.default_on_null {
21257                self.write_space();
21258                self.write_keyword("DEFAULT ON NULL");
21259                self.write(" (");
21260                self.generate_expression(default_val)?;
21261                self.write(")");
21262            }
21263
21264            // GROUP BY inside PIVOT parens
21265            if let Some(group) = &pivot.group {
21266                self.write_space();
21267                self.generate_expression(group)?;
21268            }
21269
21270            self.write(")");
21271        }
21272
21273        // Alias
21274        if let Some(alias) = &pivot.alias {
21275            self.write_space();
21276            self.write_keyword("AS");
21277            self.write_space();
21278            self.generate_identifier(alias)?;
21279        }
21280
21281        Ok(())
21282    }
21283
21284    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
21285        self.generate_expression(&unpivot.this)?;
21286        self.write_space();
21287        self.write_keyword("UNPIVOT");
21288        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
21289        if let Some(include) = unpivot.include_nulls {
21290            self.write_space();
21291            if include {
21292                self.write_keyword("INCLUDE NULLS");
21293            } else {
21294                self.write_keyword("EXCLUDE NULLS");
21295            }
21296            self.write_space();
21297        }
21298        self.write("(");
21299        if unpivot.value_column_parenthesized {
21300            self.write("(");
21301        }
21302        self.generate_identifier(&unpivot.value_column)?;
21303        // Output additional value columns if present
21304        for extra_col in &unpivot.extra_value_columns {
21305            self.write(", ");
21306            self.generate_identifier(extra_col)?;
21307        }
21308        if unpivot.value_column_parenthesized {
21309            self.write(")");
21310        }
21311        self.write_space();
21312        self.write_keyword("FOR");
21313        self.write_space();
21314        self.generate_identifier(&unpivot.name_column)?;
21315        self.write_space();
21316        self.write_keyword("IN");
21317        self.write(" (");
21318        for (i, col) in unpivot.columns.iter().enumerate() {
21319            if i > 0 {
21320                self.write(", ");
21321            }
21322            self.generate_expression(col)?;
21323        }
21324        self.write("))");
21325        if let Some(alias) = &unpivot.alias {
21326            self.write_space();
21327            self.write_keyword("AS");
21328            self.write_space();
21329            self.generate_identifier(alias)?;
21330        }
21331        Ok(())
21332    }
21333
21334    fn generate_values(&mut self, values: &Values) -> Result<()> {
21335        self.write_keyword("VALUES");
21336        for (i, row) in values.expressions.iter().enumerate() {
21337            if i > 0 {
21338                self.write(",");
21339            }
21340            self.write(" (");
21341            for (j, expr) in row.expressions.iter().enumerate() {
21342                if j > 0 {
21343                    self.write(", ");
21344                }
21345                self.generate_expression(expr)?;
21346            }
21347            self.write(")");
21348        }
21349        if let Some(alias) = &values.alias {
21350            self.write_space();
21351            self.write_keyword("AS");
21352            self.write_space();
21353            self.generate_identifier(alias)?;
21354            if !values.column_aliases.is_empty() {
21355                self.write("(");
21356                for (i, col) in values.column_aliases.iter().enumerate() {
21357                    if i > 0 {
21358                        self.write(", ");
21359                    }
21360                    self.generate_identifier(col)?;
21361                }
21362                self.write(")");
21363            }
21364        }
21365        Ok(())
21366    }
21367
21368    fn generate_array(&mut self, arr: &Array) -> Result<()> {
21369        // Apply struct name inheritance for target dialects that need it
21370        let needs_inheritance = matches!(
21371            self.config.dialect,
21372            Some(DialectType::DuckDB)
21373                | Some(DialectType::Spark)
21374                | Some(DialectType::Databricks)
21375                | Some(DialectType::Hive)
21376                | Some(DialectType::Snowflake)
21377                | Some(DialectType::Presto)
21378                | Some(DialectType::Trino)
21379        );
21380        let propagated: Vec<Expression>;
21381        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
21382            propagated = Self::inherit_struct_field_names(&arr.expressions);
21383            &propagated
21384        } else {
21385            &arr.expressions
21386        };
21387
21388        // Generic mode: ARRAY(1, 2, 3) with parentheses
21389        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
21390        let use_parens =
21391            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
21392        if !self.config.array_bracket_only {
21393            self.write_keyword("ARRAY");
21394        }
21395        if use_parens {
21396            self.write("(");
21397        } else {
21398            self.write("[");
21399        }
21400        for (i, expr) in expressions.iter().enumerate() {
21401            if i > 0 {
21402                self.write(", ");
21403            }
21404            self.generate_expression(expr)?;
21405        }
21406        if use_parens {
21407            self.write(")");
21408        } else {
21409            self.write("]");
21410        }
21411        Ok(())
21412    }
21413
21414    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
21415        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
21416        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
21417        if tuple.expressions.len() == 2 {
21418            if let Expression::TableAlias(_) = &tuple.expressions[1] {
21419                // First element is the function/expression, second is the TableAlias
21420                self.generate_expression(&tuple.expressions[0])?;
21421                self.write_space();
21422                self.write_keyword("AS");
21423                self.write_space();
21424                self.generate_expression(&tuple.expressions[1])?;
21425                return Ok(());
21426            }
21427        }
21428
21429        // In pretty mode, format long tuples with each element on a new line
21430        // Only expand if total width exceeds threshold
21431        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
21432            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
21433            for expr in &tuple.expressions {
21434                expr_strings.push(self.generate_to_string(expr)?);
21435            }
21436            self.too_wide(&expr_strings)
21437        } else {
21438            false
21439        };
21440
21441        if expand_tuple {
21442            self.write("(");
21443            self.write_newline();
21444            self.indent_level += 1;
21445            for (i, expr) in tuple.expressions.iter().enumerate() {
21446                if i > 0 {
21447                    self.write(",");
21448                    self.write_newline();
21449                }
21450                self.write_indent();
21451                self.generate_expression(expr)?;
21452            }
21453            self.indent_level -= 1;
21454            self.write_newline();
21455            self.write_indent();
21456            self.write(")");
21457        } else {
21458            self.write("(");
21459            for (i, expr) in tuple.expressions.iter().enumerate() {
21460                if i > 0 {
21461                    self.write(", ");
21462                }
21463                self.generate_expression(expr)?;
21464            }
21465            self.write(")");
21466        }
21467        Ok(())
21468    }
21469
21470    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
21471        self.generate_expression(&pipe.this)?;
21472        self.write(" |> ");
21473        self.generate_expression(&pipe.expression)?;
21474        Ok(())
21475    }
21476
21477    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
21478        self.generate_expression(&ordered.this)?;
21479        if ordered.desc {
21480            self.write_space();
21481            self.write_keyword("DESC");
21482        } else if ordered.explicit_asc {
21483            self.write_space();
21484            self.write_keyword("ASC");
21485        }
21486        if let Some(nulls_first) = ordered.nulls_first {
21487            // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
21488            // for the dialect. Different dialects have different NULL ordering defaults:
21489            //
21490            // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
21491            //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
21492            //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
21493            //
21494            // nulls_are_small (Spark, Hive, BigQuery, most others):
21495            //   - ASC: NULLS FIRST is default
21496            //   - DESC: NULLS LAST is default
21497            //
21498            // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
21499            //   - NULLS LAST is always the default regardless of sort direction
21500            let is_asc = !ordered.desc;
21501            let is_nulls_are_large = matches!(
21502                self.config.dialect,
21503                Some(DialectType::Oracle)
21504                    | Some(DialectType::PostgreSQL)
21505                    | Some(DialectType::Redshift)
21506                    | Some(DialectType::Snowflake)
21507            );
21508            let is_nulls_are_last = matches!(
21509                self.config.dialect,
21510                Some(DialectType::Dremio)
21511                    | Some(DialectType::DuckDB)
21512                    | Some(DialectType::Presto)
21513                    | Some(DialectType::Trino)
21514                    | Some(DialectType::Athena)
21515                    | Some(DialectType::ClickHouse)
21516                    | Some(DialectType::Drill)
21517                    | Some(DialectType::Exasol)
21518            );
21519
21520            // Check if the NULLS ordering matches the default for this dialect
21521            let is_default_nulls = if is_nulls_are_large {
21522                // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
21523                (is_asc && !nulls_first) || (!is_asc && nulls_first)
21524            } else if is_nulls_are_last {
21525                // For nulls_are_last: NULLS LAST is always default
21526                !nulls_first
21527            } else {
21528                false
21529            };
21530
21531            if !is_default_nulls {
21532                self.write_space();
21533                self.write_keyword("NULLS");
21534                self.write_space();
21535                self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
21536            }
21537        }
21538        // WITH FILL clause (ClickHouse)
21539        if let Some(ref with_fill) = ordered.with_fill {
21540            self.write_space();
21541            self.generate_with_fill(with_fill)?;
21542        }
21543        Ok(())
21544    }
21545
21546    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
21547    fn write_clickhouse_type(&mut self, type_str: &str) {
21548        if self.clickhouse_nullable_depth < 0 {
21549            // Map key context: don't wrap in Nullable
21550            self.write(type_str);
21551        } else {
21552            self.write(&format!("Nullable({})", type_str));
21553        }
21554    }
21555
21556    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
21557        use crate::dialects::DialectType;
21558
21559        match dt {
21560            DataType::Boolean => {
21561                // Dialect-specific boolean type mappings
21562                match self.config.dialect {
21563                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
21564                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
21565                    Some(DialectType::Oracle) => {
21566                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
21567                        self.write_keyword("NUMBER(1)")
21568                    }
21569                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
21570                    _ => self.write_keyword("BOOLEAN"),
21571                }
21572            }
21573            DataType::TinyInt { length } => {
21574                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
21575                // Dremio maps TINYINT to INT
21576                // ClickHouse maps TINYINT to Int8
21577                match self.config.dialect {
21578                    Some(DialectType::PostgreSQL)
21579                    | Some(DialectType::Redshift)
21580                    | Some(DialectType::Oracle)
21581                    | Some(DialectType::Exasol) => {
21582                        self.write_keyword("SMALLINT");
21583                    }
21584                    Some(DialectType::Teradata) => {
21585                        // Teradata uses BYTEINT for smallest integer
21586                        self.write_keyword("BYTEINT");
21587                    }
21588                    Some(DialectType::Dremio) => {
21589                        // Dremio maps TINYINT to INT
21590                        self.write_keyword("INT");
21591                    }
21592                    Some(DialectType::ClickHouse) => {
21593                        self.write_clickhouse_type("Int8");
21594                    }
21595                    _ => {
21596                        self.write_keyword("TINYINT");
21597                    }
21598                }
21599                if let Some(n) = length {
21600                    if !matches!(
21601                        self.config.dialect,
21602                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
21603                    ) {
21604                        self.write(&format!("({})", n));
21605                    }
21606                }
21607            }
21608            DataType::SmallInt { length } => {
21609                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
21610                match self.config.dialect {
21611                    Some(DialectType::Dremio) => {
21612                        self.write_keyword("INT");
21613                    }
21614                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
21615                        self.write_keyword("INTEGER");
21616                    }
21617                    Some(DialectType::BigQuery) => {
21618                        self.write_keyword("INT64");
21619                    }
21620                    Some(DialectType::ClickHouse) => {
21621                        self.write_clickhouse_type("Int16");
21622                    }
21623                    _ => {
21624                        self.write_keyword("SMALLINT");
21625                        if let Some(n) = length {
21626                            self.write(&format!("({})", n));
21627                        }
21628                    }
21629                }
21630            }
21631            DataType::Int {
21632                length,
21633                integer_spelling,
21634            } => {
21635                // BigQuery uses INT64 for INT
21636                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
21637                    self.write_keyword("INT64");
21638                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
21639                    self.write_clickhouse_type("Int32");
21640                } else {
21641                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
21642                    let use_integer = match self.config.dialect {
21643                        Some(DialectType::TSQL)
21644                        | Some(DialectType::Fabric)
21645                        | Some(DialectType::Presto)
21646                        | Some(DialectType::Trino)
21647                        | Some(DialectType::SQLite)
21648                        | Some(DialectType::Redshift) => true,
21649                        // Databricks preserves the original spelling
21650                        Some(DialectType::Databricks) => *integer_spelling,
21651                        _ => false,
21652                    };
21653                    if use_integer {
21654                        self.write_keyword("INTEGER");
21655                    } else {
21656                        self.write_keyword("INT");
21657                    }
21658                    if let Some(n) = length {
21659                        self.write(&format!("({})", n));
21660                    }
21661                }
21662            }
21663            DataType::BigInt { length } => {
21664                // Dialect-specific bigint type mappings
21665                match self.config.dialect {
21666                    Some(DialectType::Oracle) => {
21667                        // Oracle doesn't have BIGINT, uses INT
21668                        self.write_keyword("INT");
21669                    }
21670                    Some(DialectType::ClickHouse) => {
21671                        self.write_clickhouse_type("Int64");
21672                    }
21673                    _ => {
21674                        self.write_keyword("BIGINT");
21675                        if let Some(n) = length {
21676                            self.write(&format!("({})", n));
21677                        }
21678                    }
21679                }
21680            }
21681            DataType::Float {
21682                precision,
21683                scale,
21684                real_spelling,
21685            } => {
21686                // Dialect-specific float type mappings
21687                // If real_spelling is true, preserve REAL; otherwise use dialect default
21688                // Spark/Hive don't support REAL, always use FLOAT
21689                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
21690                    self.write_clickhouse_type("Float32");
21691                } else if *real_spelling
21692                    && !matches!(
21693                        self.config.dialect,
21694                        Some(DialectType::Spark)
21695                            | Some(DialectType::Databricks)
21696                            | Some(DialectType::Hive)
21697                            | Some(DialectType::Snowflake)
21698                            | Some(DialectType::MySQL)
21699                            | Some(DialectType::BigQuery)
21700                    )
21701                {
21702                    self.write_keyword("REAL")
21703                } else {
21704                    match self.config.dialect {
21705                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
21706                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
21707                        _ => self.write_keyword("FLOAT"),
21708                    }
21709                }
21710                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
21711                // Spark/Hive don't support FLOAT(precision)
21712                if !matches!(
21713                    self.config.dialect,
21714                    Some(DialectType::Spark)
21715                        | Some(DialectType::Databricks)
21716                        | Some(DialectType::Hive)
21717                        | Some(DialectType::Presto)
21718                        | Some(DialectType::Trino)
21719                ) {
21720                    if let Some(p) = precision {
21721                        self.write(&format!("({}", p));
21722                        if let Some(s) = scale {
21723                            self.write(&format!(", {})", s));
21724                        } else {
21725                            self.write(")");
21726                        }
21727                    }
21728                }
21729            }
21730            DataType::Double { precision, scale } => {
21731                // Dialect-specific double type mappings
21732                match self.config.dialect {
21733                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21734                        self.write_keyword("FLOAT")
21735                    } // SQL Server/Fabric FLOAT is double
21736                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
21737                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
21738                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
21739                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
21740                    Some(DialectType::PostgreSQL)
21741                    | Some(DialectType::Redshift)
21742                    | Some(DialectType::Teradata)
21743                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
21744                    _ => self.write_keyword("DOUBLE"),
21745                }
21746                // MySQL supports DOUBLE(precision, scale)
21747                if let Some(p) = precision {
21748                    self.write(&format!("({}", p));
21749                    if let Some(s) = scale {
21750                        self.write(&format!(", {})", s));
21751                    } else {
21752                        self.write(")");
21753                    }
21754                }
21755            }
21756            DataType::Decimal { precision, scale } => {
21757                // Dialect-specific decimal type mappings
21758                match self.config.dialect {
21759                    Some(DialectType::ClickHouse) => {
21760                        self.write("Decimal");
21761                        if let Some(p) = precision {
21762                            self.write(&format!("({}", p));
21763                            if let Some(s) = scale {
21764                                self.write(&format!(", {}", s));
21765                            }
21766                            self.write(")");
21767                        }
21768                    }
21769                    Some(DialectType::Oracle) => {
21770                        // Oracle uses NUMBER instead of DECIMAL
21771                        self.write_keyword("NUMBER");
21772                        if let Some(p) = precision {
21773                            self.write(&format!("({}", p));
21774                            if let Some(s) = scale {
21775                                self.write(&format!(", {}", s));
21776                            }
21777                            self.write(")");
21778                        }
21779                    }
21780                    Some(DialectType::BigQuery) => {
21781                        // BigQuery uses NUMERIC instead of DECIMAL
21782                        self.write_keyword("NUMERIC");
21783                        if let Some(p) = precision {
21784                            self.write(&format!("({}", p));
21785                            if let Some(s) = scale {
21786                                self.write(&format!(", {}", s));
21787                            }
21788                            self.write(")");
21789                        }
21790                    }
21791                    _ => {
21792                        self.write_keyword("DECIMAL");
21793                        if let Some(p) = precision {
21794                            self.write(&format!("({}", p));
21795                            if let Some(s) = scale {
21796                                self.write(&format!(", {}", s));
21797                            }
21798                            self.write(")");
21799                        }
21800                    }
21801                }
21802            }
21803            DataType::Char { length } => {
21804                // Dialect-specific char type mappings
21805                match self.config.dialect {
21806                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
21807                        // DuckDB/SQLite maps CHAR to TEXT
21808                        self.write_keyword("TEXT");
21809                    }
21810                    Some(DialectType::Hive)
21811                    | Some(DialectType::Spark)
21812                    | Some(DialectType::Databricks) => {
21813                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
21814                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
21815                        if length.is_some()
21816                            && !matches!(self.config.dialect, Some(DialectType::Hive))
21817                        {
21818                            self.write_keyword("CHAR");
21819                            if let Some(n) = length {
21820                                self.write(&format!("({})", n));
21821                            }
21822                        } else {
21823                            self.write_keyword("STRING");
21824                        }
21825                    }
21826                    Some(DialectType::Dremio) => {
21827                        // Dremio maps CHAR to VARCHAR
21828                        self.write_keyword("VARCHAR");
21829                        if let Some(n) = length {
21830                            self.write(&format!("({})", n));
21831                        }
21832                    }
21833                    _ => {
21834                        self.write_keyword("CHAR");
21835                        if let Some(n) = length {
21836                            self.write(&format!("({})", n));
21837                        }
21838                    }
21839                }
21840            }
21841            DataType::VarChar {
21842                length,
21843                parenthesized_length,
21844            } => {
21845                // Dialect-specific varchar type mappings
21846                match self.config.dialect {
21847                    Some(DialectType::Oracle) => {
21848                        self.write_keyword("VARCHAR2");
21849                        if let Some(n) = length {
21850                            self.write(&format!("({})", n));
21851                        }
21852                    }
21853                    Some(DialectType::DuckDB) => {
21854                        // DuckDB maps VARCHAR to TEXT, preserving length
21855                        self.write_keyword("TEXT");
21856                        if let Some(n) = length {
21857                            self.write(&format!("({})", n));
21858                        }
21859                    }
21860                    Some(DialectType::SQLite) => {
21861                        // SQLite maps VARCHAR to TEXT, preserving length
21862                        self.write_keyword("TEXT");
21863                        if let Some(n) = length {
21864                            self.write(&format!("({})", n));
21865                        }
21866                    }
21867                    Some(DialectType::MySQL) if length.is_none() => {
21868                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
21869                        self.write_keyword("TEXT");
21870                    }
21871                    Some(DialectType::Hive)
21872                    | Some(DialectType::Spark)
21873                    | Some(DialectType::Databricks)
21874                        if length.is_none() =>
21875                    {
21876                        // Hive/Spark/Databricks: VARCHAR without length → STRING
21877                        self.write_keyword("STRING");
21878                    }
21879                    _ => {
21880                        self.write_keyword("VARCHAR");
21881                        if let Some(n) = length {
21882                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
21883                            if *parenthesized_length {
21884                                self.write(&format!("(({}))", n));
21885                            } else {
21886                                self.write(&format!("({})", n));
21887                            }
21888                        }
21889                    }
21890                }
21891            }
21892            DataType::Text => {
21893                // Dialect-specific text type mappings
21894                match self.config.dialect {
21895                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
21896                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21897                        self.write_keyword("VARCHAR(MAX)")
21898                    }
21899                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
21900                    Some(DialectType::Snowflake)
21901                    | Some(DialectType::Dremio)
21902                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
21903                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
21904                    Some(DialectType::Presto)
21905                    | Some(DialectType::Trino)
21906                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
21907                    Some(DialectType::Spark)
21908                    | Some(DialectType::Databricks)
21909                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
21910                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
21911                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
21912                        self.write_keyword("STRING")
21913                    }
21914                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
21915                    _ => self.write_keyword("TEXT"),
21916                }
21917            }
21918            DataType::TextWithLength { length } => {
21919                // TEXT(n) - dialect-specific type with length
21920                match self.config.dialect {
21921                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
21922                    Some(DialectType::Hive)
21923                    | Some(DialectType::Spark)
21924                    | Some(DialectType::Databricks) => {
21925                        self.write(&format!("VARCHAR({})", length));
21926                    }
21927                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
21928                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
21929                    Some(DialectType::Snowflake)
21930                    | Some(DialectType::Presto)
21931                    | Some(DialectType::Trino)
21932                    | Some(DialectType::Athena)
21933                    | Some(DialectType::Drill)
21934                    | Some(DialectType::Dremio) => {
21935                        self.write(&format!("VARCHAR({})", length));
21936                    }
21937                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21938                        self.write(&format!("VARCHAR({})", length))
21939                    }
21940                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
21941                        self.write(&format!("STRING({})", length))
21942                    }
21943                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
21944                    _ => self.write(&format!("TEXT({})", length)),
21945                }
21946            }
21947            DataType::String { length } => {
21948                // STRING type with optional length (BigQuery STRING(n))
21949                match self.config.dialect {
21950                    Some(DialectType::ClickHouse) => {
21951                        // ClickHouse uses String with specific casing
21952                        self.write("String");
21953                        if let Some(n) = length {
21954                            self.write(&format!("({})", n));
21955                        }
21956                    }
21957                    Some(DialectType::BigQuery)
21958                    | Some(DialectType::Hive)
21959                    | Some(DialectType::Spark)
21960                    | Some(DialectType::Databricks)
21961                    | Some(DialectType::StarRocks)
21962                    | Some(DialectType::Doris) => {
21963                        self.write_keyword("STRING");
21964                        if let Some(n) = length {
21965                            self.write(&format!("({})", n));
21966                        }
21967                    }
21968                    Some(DialectType::PostgreSQL) => {
21969                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
21970                        if let Some(n) = length {
21971                            self.write_keyword("VARCHAR");
21972                            self.write(&format!("({})", n));
21973                        } else {
21974                            self.write_keyword("TEXT");
21975                        }
21976                    }
21977                    Some(DialectType::Redshift) => {
21978                        // Redshift: STRING -> VARCHAR(MAX)
21979                        if let Some(n) = length {
21980                            self.write_keyword("VARCHAR");
21981                            self.write(&format!("({})", n));
21982                        } else {
21983                            self.write_keyword("VARCHAR(MAX)");
21984                        }
21985                    }
21986                    Some(DialectType::MySQL) => {
21987                        // MySQL doesn't have STRING - use VARCHAR or TEXT
21988                        if let Some(n) = length {
21989                            self.write_keyword("VARCHAR");
21990                            self.write(&format!("({})", n));
21991                        } else {
21992                            self.write_keyword("TEXT");
21993                        }
21994                    }
21995                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
21996                        // TSQL: STRING -> VARCHAR(MAX)
21997                        if let Some(n) = length {
21998                            self.write_keyword("VARCHAR");
21999                            self.write(&format!("({})", n));
22000                        } else {
22001                            self.write_keyword("VARCHAR(MAX)");
22002                        }
22003                    }
22004                    Some(DialectType::Oracle) => {
22005                        // Oracle: STRING -> CLOB
22006                        self.write_keyword("CLOB");
22007                    }
22008                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
22009                        // DuckDB/Materialize uses TEXT for string types
22010                        self.write_keyword("TEXT");
22011                        if let Some(n) = length {
22012                            self.write(&format!("({})", n));
22013                        }
22014                    }
22015                    Some(DialectType::Presto)
22016                    | Some(DialectType::Trino)
22017                    | Some(DialectType::Drill)
22018                    | Some(DialectType::Dremio) => {
22019                        // Presto/Trino/Drill use VARCHAR for string types
22020                        self.write_keyword("VARCHAR");
22021                        if let Some(n) = length {
22022                            self.write(&format!("({})", n));
22023                        }
22024                    }
22025                    Some(DialectType::Snowflake) => {
22026                        // Snowflake: STRING stays as STRING (identity/DDL)
22027                        // CAST context STRING -> VARCHAR is handled in generate_cast
22028                        self.write_keyword("STRING");
22029                        if let Some(n) = length {
22030                            self.write(&format!("({})", n));
22031                        }
22032                    }
22033                    _ => {
22034                        // Default: output STRING with optional length
22035                        self.write_keyword("STRING");
22036                        if let Some(n) = length {
22037                            self.write(&format!("({})", n));
22038                        }
22039                    }
22040                }
22041            }
22042            DataType::Binary { length } => {
22043                // Dialect-specific binary type mappings
22044                match self.config.dialect {
22045                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
22046                        self.write_keyword("BYTEA");
22047                        if let Some(n) = length {
22048                            self.write(&format!("({})", n));
22049                        }
22050                    }
22051                    Some(DialectType::Redshift) => {
22052                        self.write_keyword("VARBYTE");
22053                        if let Some(n) = length {
22054                            self.write(&format!("({})", n));
22055                        }
22056                    }
22057                    Some(DialectType::DuckDB)
22058                    | Some(DialectType::SQLite)
22059                    | Some(DialectType::Oracle) => {
22060                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
22061                        self.write_keyword("BLOB");
22062                        if let Some(n) = length {
22063                            self.write(&format!("({})", n));
22064                        }
22065                    }
22066                    Some(DialectType::Presto)
22067                    | Some(DialectType::Trino)
22068                    | Some(DialectType::Athena)
22069                    | Some(DialectType::Drill)
22070                    | Some(DialectType::Dremio) => {
22071                        // These dialects map BINARY to VARBINARY
22072                        self.write_keyword("VARBINARY");
22073                        if let Some(n) = length {
22074                            self.write(&format!("({})", n));
22075                        }
22076                    }
22077                    Some(DialectType::ClickHouse) => {
22078                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
22079                        if self.clickhouse_nullable_depth < 0 {
22080                            self.write("BINARY");
22081                        } else {
22082                            self.write("Nullable(BINARY");
22083                        }
22084                        if let Some(n) = length {
22085                            self.write(&format!("({})", n));
22086                        }
22087                        if self.clickhouse_nullable_depth >= 0 {
22088                            self.write(")");
22089                        }
22090                    }
22091                    _ => {
22092                        self.write_keyword("BINARY");
22093                        if let Some(n) = length {
22094                            self.write(&format!("({})", n));
22095                        }
22096                    }
22097                }
22098            }
22099            DataType::VarBinary { length } => {
22100                // Dialect-specific varbinary type mappings
22101                match self.config.dialect {
22102                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
22103                        self.write_keyword("BYTEA");
22104                        if let Some(n) = length {
22105                            self.write(&format!("({})", n));
22106                        }
22107                    }
22108                    Some(DialectType::Redshift) => {
22109                        self.write_keyword("VARBYTE");
22110                        if let Some(n) = length {
22111                            self.write(&format!("({})", n));
22112                        }
22113                    }
22114                    Some(DialectType::DuckDB)
22115                    | Some(DialectType::SQLite)
22116                    | Some(DialectType::Oracle) => {
22117                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
22118                        self.write_keyword("BLOB");
22119                        if let Some(n) = length {
22120                            self.write(&format!("({})", n));
22121                        }
22122                    }
22123                    Some(DialectType::Exasol) => {
22124                        // Exasol maps VARBINARY to VARCHAR
22125                        self.write_keyword("VARCHAR");
22126                    }
22127                    Some(DialectType::Spark)
22128                    | Some(DialectType::Hive)
22129                    | Some(DialectType::Databricks) => {
22130                        // Spark/Hive use BINARY instead of VARBINARY
22131                        self.write_keyword("BINARY");
22132                        if let Some(n) = length {
22133                            self.write(&format!("({})", n));
22134                        }
22135                    }
22136                    Some(DialectType::ClickHouse) => {
22137                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
22138                        self.write_clickhouse_type("String");
22139                    }
22140                    _ => {
22141                        self.write_keyword("VARBINARY");
22142                        if let Some(n) = length {
22143                            self.write(&format!("({})", n));
22144                        }
22145                    }
22146                }
22147            }
22148            DataType::Blob => {
22149                // Dialect-specific blob type mappings
22150                match self.config.dialect {
22151                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
22152                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
22153                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22154                        self.write_keyword("VARBINARY")
22155                    }
22156                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
22157                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
22158                    Some(DialectType::Presto)
22159                    | Some(DialectType::Trino)
22160                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
22161                    Some(DialectType::DuckDB) => {
22162                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
22163                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
22164                        self.write_keyword("VARBINARY");
22165                    }
22166                    Some(DialectType::Spark)
22167                    | Some(DialectType::Databricks)
22168                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
22169                    Some(DialectType::ClickHouse) => {
22170                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
22171                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
22172                        // This matches Python sqlglot behavior.
22173                        self.write("Nullable(String)");
22174                    }
22175                    _ => self.write_keyword("BLOB"),
22176                }
22177            }
22178            DataType::Bit { length } => {
22179                // Dialect-specific bit type mappings
22180                match self.config.dialect {
22181                    Some(DialectType::Dremio)
22182                    | Some(DialectType::Spark)
22183                    | Some(DialectType::Databricks)
22184                    | Some(DialectType::Hive)
22185                    | Some(DialectType::Snowflake)
22186                    | Some(DialectType::BigQuery)
22187                    | Some(DialectType::Presto)
22188                    | Some(DialectType::Trino)
22189                    | Some(DialectType::ClickHouse)
22190                    | Some(DialectType::Redshift) => {
22191                        // These dialects don't support BIT type, use BOOLEAN
22192                        self.write_keyword("BOOLEAN");
22193                    }
22194                    _ => {
22195                        self.write_keyword("BIT");
22196                        if let Some(n) = length {
22197                            self.write(&format!("({})", n));
22198                        }
22199                    }
22200                }
22201            }
22202            DataType::VarBit { length } => {
22203                self.write_keyword("VARBIT");
22204                if let Some(n) = length {
22205                    self.write(&format!("({})", n));
22206                }
22207            }
22208            DataType::Date => self.write_keyword("DATE"),
22209            DataType::Time {
22210                precision,
22211                timezone,
22212            } => {
22213                if *timezone {
22214                    // Dialect-specific TIME WITH TIME ZONE output
22215                    match self.config.dialect {
22216                        Some(DialectType::DuckDB) => {
22217                            // DuckDB: TIMETZ (drops precision)
22218                            self.write_keyword("TIMETZ");
22219                        }
22220                        Some(DialectType::PostgreSQL) => {
22221                            // PostgreSQL: TIMETZ or TIMETZ(p)
22222                            self.write_keyword("TIMETZ");
22223                            if let Some(p) = precision {
22224                                self.write(&format!("({})", p));
22225                            }
22226                        }
22227                        _ => {
22228                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
22229                            self.write_keyword("TIME");
22230                            if let Some(p) = precision {
22231                                self.write(&format!("({})", p));
22232                            }
22233                            self.write_keyword(" WITH TIME ZONE");
22234                        }
22235                    }
22236                } else {
22237                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
22238                    if matches!(
22239                        self.config.dialect,
22240                        Some(DialectType::Spark)
22241                            | Some(DialectType::Databricks)
22242                            | Some(DialectType::Hive)
22243                    ) {
22244                        self.write_keyword("TIMESTAMP");
22245                    } else {
22246                        self.write_keyword("TIME");
22247                        if let Some(p) = precision {
22248                            self.write(&format!("({})", p));
22249                        }
22250                    }
22251                }
22252            }
22253            DataType::Timestamp {
22254                precision,
22255                timezone,
22256            } => {
22257                // Dialect-specific timestamp type mappings
22258                match self.config.dialect {
22259                    Some(DialectType::ClickHouse) => {
22260                        self.write("DateTime");
22261                        if let Some(p) = precision {
22262                            self.write(&format!("({})", p));
22263                        }
22264                    }
22265                    Some(DialectType::TSQL) => {
22266                        if *timezone {
22267                            self.write_keyword("DATETIMEOFFSET");
22268                        } else {
22269                            self.write_keyword("DATETIME2");
22270                        }
22271                        if let Some(p) = precision {
22272                            self.write(&format!("({})", p));
22273                        }
22274                    }
22275                    Some(DialectType::MySQL) => {
22276                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
22277                        self.write_keyword("TIMESTAMP");
22278                        if let Some(p) = precision {
22279                            self.write(&format!("({})", p));
22280                        }
22281                    }
22282                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
22283                        // Doris/StarRocks: TIMESTAMP -> DATETIME
22284                        self.write_keyword("DATETIME");
22285                        if let Some(p) = precision {
22286                            self.write(&format!("({})", p));
22287                        }
22288                    }
22289                    Some(DialectType::BigQuery) => {
22290                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
22291                        if *timezone {
22292                            self.write_keyword("TIMESTAMP");
22293                        } else {
22294                            self.write_keyword("DATETIME");
22295                        }
22296                    }
22297                    Some(DialectType::DuckDB) => {
22298                        // DuckDB: TIMESTAMPTZ shorthand
22299                        if *timezone {
22300                            self.write_keyword("TIMESTAMPTZ");
22301                        } else {
22302                            self.write_keyword("TIMESTAMP");
22303                            if let Some(p) = precision {
22304                                self.write(&format!("({})", p));
22305                            }
22306                        }
22307                    }
22308                    _ => {
22309                        if *timezone && !self.config.tz_to_with_time_zone {
22310                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
22311                            self.write_keyword("TIMESTAMPTZ");
22312                            if let Some(p) = precision {
22313                                self.write(&format!("({})", p));
22314                            }
22315                        } else {
22316                            self.write_keyword("TIMESTAMP");
22317                            if let Some(p) = precision {
22318                                self.write(&format!("({})", p));
22319                            }
22320                            if *timezone {
22321                                self.write_space();
22322                                self.write_keyword("WITH TIME ZONE");
22323                            }
22324                        }
22325                    }
22326                }
22327            }
22328            DataType::Interval { unit, to } => {
22329                self.write_keyword("INTERVAL");
22330                if let Some(u) = unit {
22331                    self.write_space();
22332                    self.write_keyword(u);
22333                }
22334                // Handle range intervals like DAY TO HOUR
22335                if let Some(t) = to {
22336                    self.write_space();
22337                    self.write_keyword("TO");
22338                    self.write_space();
22339                    self.write_keyword(t);
22340                }
22341            }
22342            DataType::Json => {
22343                // Dialect-specific JSON type mappings
22344                match self.config.dialect {
22345                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
22346                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
22347                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
22348                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
22349                    _ => self.write_keyword("JSON"),
22350                }
22351            }
22352            DataType::JsonB => {
22353                // JSONB is PostgreSQL specific, but Doris also supports it
22354                match self.config.dialect {
22355                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
22356                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
22357                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
22358                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
22359                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
22360                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
22361                }
22362            }
22363            DataType::Uuid => {
22364                // Dialect-specific UUID type mappings
22365                match self.config.dialect {
22366                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
22367                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
22368                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
22369                    Some(DialectType::BigQuery)
22370                    | Some(DialectType::Spark)
22371                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
22372                    _ => self.write_keyword("UUID"),
22373                }
22374            }
22375            DataType::Array {
22376                element_type,
22377                dimension,
22378            } => {
22379                // Dialect-specific array syntax
22380                match self.config.dialect {
22381                    Some(DialectType::PostgreSQL)
22382                    | Some(DialectType::Redshift)
22383                    | Some(DialectType::DuckDB) => {
22384                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
22385                        self.generate_data_type(element_type)?;
22386                        if let Some(dim) = dimension {
22387                            self.write(&format!("[{}]", dim));
22388                        } else {
22389                            self.write("[]");
22390                        }
22391                    }
22392                    Some(DialectType::BigQuery) => {
22393                        self.write_keyword("ARRAY<");
22394                        self.generate_data_type(element_type)?;
22395                        self.write(">");
22396                    }
22397                    Some(DialectType::Snowflake)
22398                    | Some(DialectType::Presto)
22399                    | Some(DialectType::Trino)
22400                    | Some(DialectType::ClickHouse) => {
22401                        // These dialects use Array(TYPE) parentheses syntax
22402                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22403                            self.write("Array(");
22404                        } else {
22405                            self.write_keyword("ARRAY(");
22406                        }
22407                        self.generate_data_type(element_type)?;
22408                        self.write(")");
22409                    }
22410                    Some(DialectType::TSQL)
22411                    | Some(DialectType::MySQL)
22412                    | Some(DialectType::Oracle) => {
22413                        // These dialects don't have native array types
22414                        // Fall back to JSON or use native workarounds
22415                        match self.config.dialect {
22416                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
22417                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
22418                            _ => self.write_keyword("JSON"),
22419                        }
22420                    }
22421                    _ => {
22422                        // Default: use angle bracket syntax (ARRAY<T>)
22423                        self.write_keyword("ARRAY<");
22424                        self.generate_data_type(element_type)?;
22425                        self.write(">");
22426                    }
22427                }
22428            }
22429            DataType::List { element_type } => {
22430                // Materialize: element_type LIST (postfix syntax)
22431                self.generate_data_type(element_type)?;
22432                self.write_keyword(" LIST");
22433            }
22434            DataType::Map {
22435                key_type,
22436                value_type,
22437            } => {
22438                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
22439                match self.config.dialect {
22440                    Some(DialectType::Materialize) => {
22441                        // Materialize: MAP[key_type => value_type]
22442                        self.write_keyword("MAP[");
22443                        self.generate_data_type(key_type)?;
22444                        self.write(" => ");
22445                        self.generate_data_type(value_type)?;
22446                        self.write("]");
22447                    }
22448                    Some(DialectType::Snowflake)
22449                    | Some(DialectType::RisingWave)
22450                    | Some(DialectType::DuckDB)
22451                    | Some(DialectType::Presto)
22452                    | Some(DialectType::Trino)
22453                    | Some(DialectType::Athena) => {
22454                        self.write_keyword("MAP(");
22455                        self.generate_data_type(key_type)?;
22456                        self.write(", ");
22457                        self.generate_data_type(value_type)?;
22458                        self.write(")");
22459                    }
22460                    Some(DialectType::ClickHouse) => {
22461                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
22462                        // Key types must NOT be wrapped in Nullable
22463                        self.write("Map(");
22464                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
22465                        self.generate_data_type(key_type)?;
22466                        self.clickhouse_nullable_depth = 0;
22467                        self.write(", ");
22468                        self.generate_data_type(value_type)?;
22469                        self.write(")");
22470                    }
22471                    _ => {
22472                        self.write_keyword("MAP<");
22473                        self.generate_data_type(key_type)?;
22474                        self.write(", ");
22475                        self.generate_data_type(value_type)?;
22476                        self.write(">");
22477                    }
22478                }
22479            }
22480            DataType::Vector {
22481                element_type,
22482                dimension,
22483            } => {
22484                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
22485                    // SingleStore format: VECTOR(dimension, type_alias)
22486                    self.write_keyword("VECTOR(");
22487                    if let Some(dim) = dimension {
22488                        self.write(&dim.to_string());
22489                    }
22490                    // Map type back to SingleStore alias
22491                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
22492                        DataType::TinyInt { .. } => Some("I8"),
22493                        DataType::SmallInt { .. } => Some("I16"),
22494                        DataType::Int { .. } => Some("I32"),
22495                        DataType::BigInt { .. } => Some("I64"),
22496                        DataType::Float { .. } => Some("F32"),
22497                        DataType::Double { .. } => Some("F64"),
22498                        _ => None,
22499                    });
22500                    if let Some(alias) = type_alias {
22501                        if dimension.is_some() {
22502                            self.write(", ");
22503                        }
22504                        self.write(alias);
22505                    }
22506                    self.write(")");
22507                } else {
22508                    // Snowflake format: VECTOR(type, dimension)
22509                    self.write_keyword("VECTOR(");
22510                    if let Some(ref et) = element_type {
22511                        self.generate_data_type(et)?;
22512                        if dimension.is_some() {
22513                            self.write(", ");
22514                        }
22515                    }
22516                    if let Some(dim) = dimension {
22517                        self.write(&dim.to_string());
22518                    }
22519                    self.write(")");
22520                }
22521            }
22522            DataType::Object { fields, modifier } => {
22523                self.write_keyword("OBJECT(");
22524                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
22525                    if i > 0 {
22526                        self.write(", ");
22527                    }
22528                    self.write(name);
22529                    self.write(" ");
22530                    self.generate_data_type(dt)?;
22531                    if *not_null {
22532                        self.write_keyword(" NOT NULL");
22533                    }
22534                }
22535                self.write(")");
22536                if let Some(mod_str) = modifier {
22537                    self.write(" ");
22538                    self.write_keyword(mod_str);
22539                }
22540            }
22541            DataType::Struct { fields, nested } => {
22542                // Dialect-specific struct type mappings
22543                match self.config.dialect {
22544                    Some(DialectType::Snowflake) => {
22545                        // Snowflake maps STRUCT to OBJECT
22546                        self.write_keyword("OBJECT(");
22547                        for (i, field) in fields.iter().enumerate() {
22548                            if i > 0 {
22549                                self.write(", ");
22550                            }
22551                            if !field.name.is_empty() {
22552                                self.write(&field.name);
22553                                self.write(" ");
22554                            }
22555                            self.generate_data_type(&field.data_type)?;
22556                        }
22557                        self.write(")");
22558                    }
22559                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
22560                        // Presto/Trino use ROW(name TYPE, ...) syntax
22561                        self.write_keyword("ROW(");
22562                        for (i, field) in fields.iter().enumerate() {
22563                            if i > 0 {
22564                                self.write(", ");
22565                            }
22566                            if !field.name.is_empty() {
22567                                self.write(&field.name);
22568                                self.write(" ");
22569                            }
22570                            self.generate_data_type(&field.data_type)?;
22571                        }
22572                        self.write(")");
22573                    }
22574                    Some(DialectType::DuckDB) => {
22575                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
22576                        self.write_keyword("STRUCT(");
22577                        for (i, field) in fields.iter().enumerate() {
22578                            if i > 0 {
22579                                self.write(", ");
22580                            }
22581                            if !field.name.is_empty() {
22582                                self.write(&field.name);
22583                                self.write(" ");
22584                            }
22585                            self.generate_data_type(&field.data_type)?;
22586                        }
22587                        self.write(")");
22588                    }
22589                    Some(DialectType::ClickHouse) => {
22590                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
22591                        self.write("Tuple(");
22592                        for (i, field) in fields.iter().enumerate() {
22593                            if i > 0 {
22594                                self.write(", ");
22595                            }
22596                            if !field.name.is_empty() {
22597                                self.write(&field.name);
22598                                self.write(" ");
22599                            }
22600                            self.generate_data_type(&field.data_type)?;
22601                        }
22602                        self.write(")");
22603                    }
22604                    Some(DialectType::SingleStore) => {
22605                        // SingleStore uses RECORD(name TYPE, ...) for struct types
22606                        self.write_keyword("RECORD(");
22607                        for (i, field) in fields.iter().enumerate() {
22608                            if i > 0 {
22609                                self.write(", ");
22610                            }
22611                            if !field.name.is_empty() {
22612                                self.write(&field.name);
22613                                self.write(" ");
22614                            }
22615                            self.generate_data_type(&field.data_type)?;
22616                        }
22617                        self.write(")");
22618                    }
22619                    _ => {
22620                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
22621                        let force_angle_brackets = matches!(
22622                            self.config.dialect,
22623                            Some(DialectType::Hive)
22624                                | Some(DialectType::Spark)
22625                                | Some(DialectType::Databricks)
22626                        );
22627                        if *nested && !force_angle_brackets {
22628                            self.write_keyword("STRUCT(");
22629                            for (i, field) in fields.iter().enumerate() {
22630                                if i > 0 {
22631                                    self.write(", ");
22632                                }
22633                                if !field.name.is_empty() {
22634                                    self.write(&field.name);
22635                                    self.write(" ");
22636                                }
22637                                self.generate_data_type(&field.data_type)?;
22638                            }
22639                            self.write(")");
22640                        } else {
22641                            self.write_keyword("STRUCT<");
22642                            for (i, field) in fields.iter().enumerate() {
22643                                if i > 0 {
22644                                    self.write(", ");
22645                                }
22646                                if !field.name.is_empty() {
22647                                    // Named field: name TYPE (with configurable separator for Hive)
22648                                    self.write(&field.name);
22649                                    self.write(self.config.struct_field_sep);
22650                                }
22651                                // For anonymous fields, just output the type
22652                                self.generate_data_type(&field.data_type)?;
22653                                // Spark/Databricks: Output COMMENT clause if present
22654                                if let Some(comment) = &field.comment {
22655                                    self.write(" COMMENT '");
22656                                    self.write(comment);
22657                                    self.write("'");
22658                                }
22659                                // BigQuery: Output OPTIONS clause if present
22660                                if !field.options.is_empty() {
22661                                    self.write(" ");
22662                                    self.generate_options_clause(&field.options)?;
22663                                }
22664                            }
22665                            self.write(">");
22666                        }
22667                    }
22668                }
22669            }
22670            DataType::Enum {
22671                values,
22672                assignments,
22673            } => {
22674                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
22675                // ClickHouse: Enum('hello' = 1, 'world' = 2)
22676                if self.config.dialect == Some(DialectType::ClickHouse) {
22677                    self.write("Enum(");
22678                } else {
22679                    self.write_keyword("ENUM(");
22680                }
22681                for (i, val) in values.iter().enumerate() {
22682                    if i > 0 {
22683                        self.write(", ");
22684                    }
22685                    self.write("'");
22686                    self.write(val);
22687                    self.write("'");
22688                    if let Some(Some(assignment)) = assignments.get(i) {
22689                        self.write(" = ");
22690                        self.write(assignment);
22691                    }
22692                }
22693                self.write(")");
22694            }
22695            DataType::Set { values } => {
22696                // MySQL SET type: SET('a', 'b', 'c')
22697                self.write_keyword("SET(");
22698                for (i, val) in values.iter().enumerate() {
22699                    if i > 0 {
22700                        self.write(", ");
22701                    }
22702                    self.write("'");
22703                    self.write(val);
22704                    self.write("'");
22705                }
22706                self.write(")");
22707            }
22708            DataType::Union { fields } => {
22709                // DuckDB UNION type: UNION(num INT, str TEXT)
22710                self.write_keyword("UNION(");
22711                for (i, (name, dt)) in fields.iter().enumerate() {
22712                    if i > 0 {
22713                        self.write(", ");
22714                    }
22715                    if !name.is_empty() {
22716                        self.write(name);
22717                        self.write(" ");
22718                    }
22719                    self.generate_data_type(dt)?;
22720                }
22721                self.write(")");
22722            }
22723            DataType::Nullable { inner } => {
22724                // ClickHouse: Nullable(T), other dialects: just the inner type
22725                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22726                    self.write("Nullable(");
22727                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
22728                    let saved_depth = self.clickhouse_nullable_depth;
22729                    self.clickhouse_nullable_depth = -1;
22730                    self.generate_data_type(inner)?;
22731                    self.clickhouse_nullable_depth = saved_depth;
22732                    self.write(")");
22733                } else {
22734                    // Map ClickHouse-specific custom type names to standard types
22735                    match inner.as_ref() {
22736                        DataType::Custom { name } if name.to_uppercase() == "DATETIME" => {
22737                            self.generate_data_type(&DataType::Timestamp {
22738                                precision: None,
22739                                timezone: false,
22740                            })?;
22741                        }
22742                        _ => {
22743                            self.generate_data_type(inner)?;
22744                        }
22745                    }
22746                }
22747            }
22748            DataType::Custom { name } => {
22749                // Handle dialect-specific type transformations
22750                let name_upper = name.to_uppercase();
22751                match self.config.dialect {
22752                    Some(DialectType::ClickHouse) => {
22753                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
22754                            (name_upper[..idx].to_string(), &name[idx..])
22755                        } else {
22756                            (name_upper.clone(), "")
22757                        };
22758                        let mapped = match base_upper.as_str() {
22759                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
22760                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
22761                            "DATETIME64" => "DateTime64",
22762                            "DATE32" => "Date32",
22763                            "INT" => "Int32",
22764                            "MEDIUMINT" => "Int32",
22765                            "INT8" => "Int8",
22766                            "INT16" => "Int16",
22767                            "INT32" => "Int32",
22768                            "INT64" => "Int64",
22769                            "INT128" => "Int128",
22770                            "INT256" => "Int256",
22771                            "UINT8" => "UInt8",
22772                            "UINT16" => "UInt16",
22773                            "UINT32" => "UInt32",
22774                            "UINT64" => "UInt64",
22775                            "UINT128" => "UInt128",
22776                            "UINT256" => "UInt256",
22777                            "FLOAT32" => "Float32",
22778                            "FLOAT64" => "Float64",
22779                            "DECIMAL32" => "Decimal32",
22780                            "DECIMAL64" => "Decimal64",
22781                            "DECIMAL128" => "Decimal128",
22782                            "DECIMAL256" => "Decimal256",
22783                            "ENUM" => "Enum",
22784                            "ENUM8" => "Enum8",
22785                            "ENUM16" => "Enum16",
22786                            "FIXEDSTRING" => "FixedString",
22787                            "NESTED" => "Nested",
22788                            "LOWCARDINALITY" => "LowCardinality",
22789                            "NULLABLE" => "Nullable",
22790                            "IPV4" => "IPv4",
22791                            "IPV6" => "IPv6",
22792                            "POINT" => "Point",
22793                            "RING" => "Ring",
22794                            "LINESTRING" => "LineString",
22795                            "MULTILINESTRING" => "MultiLineString",
22796                            "POLYGON" => "Polygon",
22797                            "MULTIPOLYGON" => "MultiPolygon",
22798                            "AGGREGATEFUNCTION" => "AggregateFunction",
22799                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
22800                            "DYNAMIC" => "Dynamic",
22801                            _ => "",
22802                        };
22803                        if mapped.is_empty() {
22804                            self.write(name);
22805                        } else {
22806                            self.write(mapped);
22807                            self.write(suffix);
22808                        }
22809                    }
22810                    Some(DialectType::MySQL)
22811                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
22812                    {
22813                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
22814                        self.write_keyword("TIMESTAMP");
22815                    }
22816                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
22817                        self.write_keyword("SQL_VARIANT");
22818                    }
22819                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
22820                        self.write_keyword("DECIMAL(38, 5)");
22821                    }
22822                    Some(DialectType::Exasol) => {
22823                        // Exasol type mappings for custom types
22824                        match name_upper.as_str() {
22825                            // Binary types → VARCHAR
22826                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
22827                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
22828                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
22829                            // Integer types
22830                            "MEDIUMINT" => self.write_keyword("INT"),
22831                            // Decimal types → DECIMAL
22832                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
22833                                self.write_keyword("DECIMAL")
22834                            }
22835                            // Timestamp types
22836                            "DATETIME" => self.write_keyword("TIMESTAMP"),
22837                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
22838                            _ => self.write(name),
22839                        }
22840                    }
22841                    Some(DialectType::Dremio) => {
22842                        // Dremio type mappings for custom types
22843                        match name_upper.as_str() {
22844                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
22845                            "ARRAY" => self.write_keyword("LIST"),
22846                            "NCHAR" => self.write_keyword("VARCHAR"),
22847                            _ => self.write(name),
22848                        }
22849                    }
22850                    // Map dialect-specific custom types to standard SQL types for other dialects
22851                    _ => {
22852                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
22853                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
22854                            (name_upper[..idx].to_string(), Some(&name[idx..]))
22855                        } else {
22856                            (name_upper.clone(), None)
22857                        };
22858
22859                        match base_upper.as_str() {
22860                            "INT64"
22861                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22862                            {
22863                                self.write_keyword("BIGINT");
22864                            }
22865                            "FLOAT64"
22866                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22867                            {
22868                                self.write_keyword("DOUBLE");
22869                            }
22870                            "BOOL"
22871                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22872                            {
22873                                self.write_keyword("BOOLEAN");
22874                            }
22875                            "BYTES"
22876                                if matches!(
22877                                    self.config.dialect,
22878                                    Some(DialectType::Spark)
22879                                        | Some(DialectType::Hive)
22880                                        | Some(DialectType::Databricks)
22881                                ) =>
22882                            {
22883                                self.write_keyword("BINARY");
22884                            }
22885                            "BYTES"
22886                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
22887                            {
22888                                self.write_keyword("VARBINARY");
22889                            }
22890                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
22891                            "DATETIME2" | "SMALLDATETIME"
22892                                if !matches!(
22893                                    self.config.dialect,
22894                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22895                                ) =>
22896                            {
22897                                // PostgreSQL preserves precision, others don't
22898                                if matches!(
22899                                    self.config.dialect,
22900                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22901                                ) {
22902                                    self.write_keyword("TIMESTAMP");
22903                                    if let Some(args) = _args_str {
22904                                        self.write(args);
22905                                    }
22906                                } else {
22907                                    self.write_keyword("TIMESTAMP");
22908                                }
22909                            }
22910                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
22911                            "DATETIMEOFFSET"
22912                                if !matches!(
22913                                    self.config.dialect,
22914                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22915                                ) =>
22916                            {
22917                                if matches!(
22918                                    self.config.dialect,
22919                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22920                                ) {
22921                                    self.write_keyword("TIMESTAMPTZ");
22922                                    if let Some(args) = _args_str {
22923                                        self.write(args);
22924                                    }
22925                                } else {
22926                                    self.write_keyword("TIMESTAMPTZ");
22927                                }
22928                            }
22929                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
22930                            "UNIQUEIDENTIFIER"
22931                                if !matches!(
22932                                    self.config.dialect,
22933                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22934                                ) =>
22935                            {
22936                                match self.config.dialect {
22937                                    Some(DialectType::Spark)
22938                                    | Some(DialectType::Databricks)
22939                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
22940                                    _ => self.write_keyword("UUID"),
22941                                }
22942                            }
22943                            // TSQL BIT -> BOOLEAN for most dialects
22944                            "BIT"
22945                                if !matches!(
22946                                    self.config.dialect,
22947                                    Some(DialectType::TSQL)
22948                                        | Some(DialectType::Fabric)
22949                                        | Some(DialectType::PostgreSQL)
22950                                        | Some(DialectType::MySQL)
22951                                        | Some(DialectType::DuckDB)
22952                                ) =>
22953                            {
22954                                self.write_keyword("BOOLEAN");
22955                            }
22956                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
22957                            "NVARCHAR"
22958                                if !matches!(
22959                                    self.config.dialect,
22960                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
22961                                ) =>
22962                            {
22963                                match self.config.dialect {
22964                                    Some(DialectType::Oracle) => {
22965                                        // Oracle: NVARCHAR -> NVARCHAR2
22966                                        self.write_keyword("NVARCHAR2");
22967                                        if let Some(args) = _args_str {
22968                                            self.write(args);
22969                                        }
22970                                    }
22971                                    Some(DialectType::BigQuery) => {
22972                                        // BigQuery: NVARCHAR -> STRING
22973                                        self.write_keyword("STRING");
22974                                    }
22975                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
22976                                        self.write_keyword("TEXT");
22977                                        if let Some(args) = _args_str {
22978                                            self.write(args);
22979                                        }
22980                                    }
22981                                    Some(DialectType::Hive) => {
22982                                        // Hive: NVARCHAR -> STRING
22983                                        self.write_keyword("STRING");
22984                                    }
22985                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
22986                                        if _args_str.is_some() {
22987                                            self.write_keyword("VARCHAR");
22988                                            self.write(_args_str.unwrap());
22989                                        } else {
22990                                            self.write_keyword("STRING");
22991                                        }
22992                                    }
22993                                    _ => {
22994                                        self.write_keyword("VARCHAR");
22995                                        if let Some(args) = _args_str {
22996                                            self.write(args);
22997                                        }
22998                                    }
22999                                }
23000                            }
23001                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
23002                            "NCHAR"
23003                                if !matches!(
23004                                    self.config.dialect,
23005                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
23006                                ) =>
23007                            {
23008                                match self.config.dialect {
23009                                    Some(DialectType::Oracle) => {
23010                                        // Oracle natively supports NCHAR
23011                                        self.write_keyword("NCHAR");
23012                                        if let Some(args) = _args_str {
23013                                            self.write(args);
23014                                        }
23015                                    }
23016                                    Some(DialectType::BigQuery) => {
23017                                        // BigQuery: NCHAR -> STRING
23018                                        self.write_keyword("STRING");
23019                                    }
23020                                    Some(DialectType::Hive) => {
23021                                        // Hive: NCHAR -> STRING
23022                                        self.write_keyword("STRING");
23023                                    }
23024                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
23025                                        self.write_keyword("TEXT");
23026                                        if let Some(args) = _args_str {
23027                                            self.write(args);
23028                                        }
23029                                    }
23030                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
23031                                        if _args_str.is_some() {
23032                                            self.write_keyword("CHAR");
23033                                            self.write(_args_str.unwrap());
23034                                        } else {
23035                                            self.write_keyword("STRING");
23036                                        }
23037                                    }
23038                                    _ => {
23039                                        self.write_keyword("CHAR");
23040                                        if let Some(args) = _args_str {
23041                                            self.write(args);
23042                                        }
23043                                    }
23044                                }
23045                            }
23046                            // MySQL text variant types -> map to appropriate target type
23047                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
23048                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
23049                                Some(DialectType::MySQL)
23050                                | Some(DialectType::SingleStore)
23051                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
23052                                Some(DialectType::Spark)
23053                                | Some(DialectType::Databricks)
23054                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
23055                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
23056                                Some(DialectType::Presto)
23057                                | Some(DialectType::Trino)
23058                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
23059                                Some(DialectType::Snowflake)
23060                                | Some(DialectType::Redshift)
23061                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
23062                                _ => self.write_keyword("TEXT"),
23063                            },
23064                            // MySQL blob variant types -> map to appropriate target type
23065                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
23066                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
23067                                Some(DialectType::MySQL)
23068                                | Some(DialectType::SingleStore)
23069                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
23070                                Some(DialectType::Spark)
23071                                | Some(DialectType::Databricks)
23072                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
23073                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
23074                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
23075                                Some(DialectType::Presto)
23076                                | Some(DialectType::Trino)
23077                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
23078                                Some(DialectType::Snowflake)
23079                                | Some(DialectType::Redshift)
23080                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
23081                                _ => self.write_keyword("BLOB"),
23082                            },
23083                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
23084                            "LONGVARCHAR" => match self.config.dialect {
23085                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
23086                                _ => self.write_keyword("VARCHAR"),
23087                            },
23088                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
23089                            "DATETIME" => {
23090                                match self.config.dialect {
23091                                    Some(DialectType::MySQL)
23092                                    | Some(DialectType::Doris)
23093                                    | Some(DialectType::StarRocks)
23094                                    | Some(DialectType::TSQL)
23095                                    | Some(DialectType::Fabric)
23096                                    | Some(DialectType::BigQuery)
23097                                    | Some(DialectType::SQLite)
23098                                    | Some(DialectType::Snowflake) => {
23099                                        self.write_keyword("DATETIME");
23100                                        if let Some(args) = _args_str {
23101                                            self.write(args);
23102                                        }
23103                                    }
23104                                    Some(_) => {
23105                                        // Only map to TIMESTAMP when we have a specific target dialect
23106                                        self.write_keyword("TIMESTAMP");
23107                                        if let Some(args) = _args_str {
23108                                            self.write(args);
23109                                        }
23110                                    }
23111                                    None => {
23112                                        // No dialect - preserve original
23113                                        self.write(name);
23114                                    }
23115                                }
23116                            }
23117                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
23118                            "VARCHAR2"
23119                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
23120                            {
23121                                match self.config.dialect {
23122                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23123                                        self.write_keyword("TEXT");
23124                                    }
23125                                    Some(DialectType::Hive)
23126                                    | Some(DialectType::Spark)
23127                                    | Some(DialectType::Databricks)
23128                                    | Some(DialectType::BigQuery)
23129                                    | Some(DialectType::ClickHouse)
23130                                    | Some(DialectType::StarRocks)
23131                                    | Some(DialectType::Doris) => {
23132                                        self.write_keyword("STRING");
23133                                    }
23134                                    _ => {
23135                                        self.write_keyword("VARCHAR");
23136                                        if let Some(args) = _args_str {
23137                                            self.write(args);
23138                                        }
23139                                    }
23140                                }
23141                            }
23142                            "NVARCHAR2"
23143                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
23144                            {
23145                                match self.config.dialect {
23146                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23147                                        self.write_keyword("TEXT");
23148                                    }
23149                                    Some(DialectType::Hive)
23150                                    | Some(DialectType::Spark)
23151                                    | Some(DialectType::Databricks)
23152                                    | Some(DialectType::BigQuery)
23153                                    | Some(DialectType::ClickHouse)
23154                                    | Some(DialectType::StarRocks)
23155                                    | Some(DialectType::Doris) => {
23156                                        self.write_keyword("STRING");
23157                                    }
23158                                    _ => {
23159                                        self.write_keyword("VARCHAR");
23160                                        if let Some(args) = _args_str {
23161                                            self.write(args);
23162                                        }
23163                                    }
23164                                }
23165                            }
23166                            _ => self.write(name),
23167                        }
23168                    }
23169                }
23170            }
23171            DataType::Geometry { subtype, srid } => {
23172                // Dialect-specific geometry type mappings
23173                match self.config.dialect {
23174                    Some(DialectType::MySQL) => {
23175                        // MySQL uses POINT SRID 4326 syntax for specific types
23176                        if let Some(sub) = subtype {
23177                            self.write_keyword(sub);
23178                            if let Some(s) = srid {
23179                                self.write(" SRID ");
23180                                self.write(&s.to_string());
23181                            }
23182                        } else {
23183                            self.write_keyword("GEOMETRY");
23184                        }
23185                    }
23186                    Some(DialectType::BigQuery) => {
23187                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
23188                        self.write_keyword("GEOGRAPHY");
23189                    }
23190                    Some(DialectType::Teradata) => {
23191                        // Teradata uses ST_GEOMETRY
23192                        self.write_keyword("ST_GEOMETRY");
23193                        if subtype.is_some() || srid.is_some() {
23194                            self.write("(");
23195                            if let Some(sub) = subtype {
23196                                self.write_keyword(sub);
23197                            }
23198                            if let Some(s) = srid {
23199                                if subtype.is_some() {
23200                                    self.write(", ");
23201                                }
23202                                self.write(&s.to_string());
23203                            }
23204                            self.write(")");
23205                        }
23206                    }
23207                    _ => {
23208                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
23209                        self.write_keyword("GEOMETRY");
23210                        if subtype.is_some() || srid.is_some() {
23211                            self.write("(");
23212                            if let Some(sub) = subtype {
23213                                self.write_keyword(sub);
23214                            }
23215                            if let Some(s) = srid {
23216                                if subtype.is_some() {
23217                                    self.write(", ");
23218                                }
23219                                self.write(&s.to_string());
23220                            }
23221                            self.write(")");
23222                        }
23223                    }
23224                }
23225            }
23226            DataType::Geography { subtype, srid } => {
23227                // Dialect-specific geography type mappings
23228                match self.config.dialect {
23229                    Some(DialectType::MySQL) => {
23230                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
23231                        if let Some(sub) = subtype {
23232                            self.write_keyword(sub);
23233                        } else {
23234                            self.write_keyword("GEOMETRY");
23235                        }
23236                        // Geography implies SRID 4326 (WGS84)
23237                        let effective_srid = srid.unwrap_or(4326);
23238                        self.write(" SRID ");
23239                        self.write(&effective_srid.to_string());
23240                    }
23241                    Some(DialectType::BigQuery) => {
23242                        // BigQuery uses simple GEOGRAPHY without parameters
23243                        self.write_keyword("GEOGRAPHY");
23244                    }
23245                    Some(DialectType::Snowflake) => {
23246                        // Snowflake uses GEOGRAPHY without parameters
23247                        self.write_keyword("GEOGRAPHY");
23248                    }
23249                    _ => {
23250                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
23251                        self.write_keyword("GEOGRAPHY");
23252                        if subtype.is_some() || srid.is_some() {
23253                            self.write("(");
23254                            if let Some(sub) = subtype {
23255                                self.write_keyword(sub);
23256                            }
23257                            if let Some(s) = srid {
23258                                if subtype.is_some() {
23259                                    self.write(", ");
23260                                }
23261                                self.write(&s.to_string());
23262                            }
23263                            self.write(")");
23264                        }
23265                    }
23266                }
23267            }
23268            DataType::CharacterSet { name } => {
23269                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
23270                self.write_keyword("CHAR CHARACTER SET ");
23271                self.write(name);
23272            }
23273            _ => self.write("UNKNOWN"),
23274        }
23275        Ok(())
23276    }
23277
23278    // === Helper methods ===
23279
23280    fn write(&mut self, s: &str) {
23281        self.output.push_str(s);
23282    }
23283
23284    fn write_space(&mut self) {
23285        self.output.push(' ');
23286    }
23287
23288    fn write_keyword(&mut self, keyword: &str) {
23289        if self.config.uppercase_keywords {
23290            self.output.push_str(keyword);
23291        } else {
23292            self.output.push_str(&keyword.to_lowercase());
23293        }
23294    }
23295
23296    /// Write a function name respecting the normalize_functions config setting
23297    fn write_func_name(&mut self, name: &str) {
23298        let normalized = self.normalize_func_name(name);
23299        self.output.push_str(&normalized);
23300    }
23301
23302    /// Convert strptime format string to Exasol format string
23303    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
23304    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
23305    fn convert_strptime_to_exasol_format(format: &str) -> String {
23306        let mut result = String::new();
23307        let chars: Vec<char> = format.chars().collect();
23308        let mut i = 0;
23309        while i < chars.len() {
23310            if chars[i] == '%' && i + 1 < chars.len() {
23311                let spec = chars[i + 1];
23312                let exasol_spec = match spec {
23313                    'Y' => "YYYY",
23314                    'y' => "YY",
23315                    'm' => "MM",
23316                    'd' => "DD",
23317                    'H' => "HH",
23318                    'M' => "MI",
23319                    'S' => "SS",
23320                    'a' => "DY",    // abbreviated weekday name
23321                    'A' => "DAY",   // full weekday name
23322                    'b' => "MON",   // abbreviated month name
23323                    'B' => "MONTH", // full month name
23324                    'I' => "H12",   // 12-hour format
23325                    'u' => "ID",    // ISO weekday (1-7)
23326                    'V' => "IW",    // ISO week number
23327                    'G' => "IYYY",  // ISO year
23328                    'W' => "UW",    // Week number (Monday as first day)
23329                    'U' => "UW",    // Week number (Sunday as first day)
23330                    'z' => "Z",     // timezone offset
23331                    _ => {
23332                        // Unknown specifier, keep as-is
23333                        result.push('%');
23334                        result.push(spec);
23335                        i += 2;
23336                        continue;
23337                    }
23338                };
23339                result.push_str(exasol_spec);
23340                i += 2;
23341            } else {
23342                result.push(chars[i]);
23343                i += 1;
23344            }
23345        }
23346        result
23347    }
23348
23349    /// Convert strptime format string to PostgreSQL/Redshift format string
23350    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
23351    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
23352    fn convert_strptime_to_postgres_format(format: &str) -> String {
23353        let mut result = String::new();
23354        let chars: Vec<char> = format.chars().collect();
23355        let mut i = 0;
23356        while i < chars.len() {
23357            if chars[i] == '%' && i + 1 < chars.len() {
23358                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
23359                if chars[i + 1] == '-' && i + 2 < chars.len() {
23360                    let spec = chars[i + 2];
23361                    let pg_spec = match spec {
23362                        'd' => "FMDD",
23363                        'm' => "FMMM",
23364                        'H' => "FMHH24",
23365                        'M' => "FMMI",
23366                        'S' => "FMSS",
23367                        _ => {
23368                            result.push('%');
23369                            result.push('-');
23370                            result.push(spec);
23371                            i += 3;
23372                            continue;
23373                        }
23374                    };
23375                    result.push_str(pg_spec);
23376                    i += 3;
23377                    continue;
23378                }
23379                let spec = chars[i + 1];
23380                let pg_spec = match spec {
23381                    'Y' => "YYYY",
23382                    'y' => "YY",
23383                    'm' => "MM",
23384                    'd' => "DD",
23385                    'H' => "HH24",
23386                    'I' => "HH12",
23387                    'M' => "MI",
23388                    'S' => "SS",
23389                    'f' => "US",      // microseconds
23390                    'u' => "D",       // day of week (1=Monday)
23391                    'j' => "DDD",     // day of year
23392                    'z' => "OF",      // UTC offset
23393                    'Z' => "TZ",      // timezone name
23394                    'A' => "TMDay",   // full weekday name
23395                    'a' => "TMDy",    // abbreviated weekday name
23396                    'b' => "TMMon",   // abbreviated month name
23397                    'B' => "TMMonth", // full month name
23398                    'U' => "WW",      // week number
23399                    _ => {
23400                        // Unknown specifier, keep as-is
23401                        result.push('%');
23402                        result.push(spec);
23403                        i += 2;
23404                        continue;
23405                    }
23406                };
23407                result.push_str(pg_spec);
23408                i += 2;
23409            } else {
23410                result.push(chars[i]);
23411                i += 1;
23412            }
23413        }
23414        result
23415    }
23416
23417    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
23418    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
23419        if self.config.limit_only_literals {
23420            if let Some(value) = Self::try_evaluate_constant(expr) {
23421                self.write(&value.to_string());
23422                return Ok(());
23423            }
23424        }
23425        self.generate_expression(expr)
23426    }
23427
23428    /// Format a comment with proper spacing.
23429    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
23430    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
23431    fn write_formatted_comment(&mut self, comment: &str) {
23432        // Normalize all comments to block comment format /* ... */
23433        // This matches Python sqlglot behavior which always outputs block comments
23434        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
23435            // Already block comment - extract inner content
23436            // Preserve internal whitespace, but ensure at least one space padding
23437            &comment[2..comment.len() - 2]
23438        } else if comment.starts_with("--") {
23439            // Line comment - extract content after --
23440            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
23441            &comment[2..]
23442        } else {
23443            // Raw content (no delimiters)
23444            comment
23445        };
23446        // Skip empty comments (e.g., bare "--" with no content)
23447        if content.trim().is_empty() {
23448            return;
23449        }
23450        // Ensure at least one space after /* and before */
23451        self.output.push_str("/*");
23452        if !content.starts_with(' ') {
23453            self.output.push(' ');
23454        }
23455        self.output.push_str(content);
23456        if !content.ends_with(' ') {
23457            self.output.push(' ');
23458        }
23459        self.output.push_str("*/");
23460    }
23461
23462    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
23463    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
23464    fn escape_block_for_single_quote(&self, block: &str) -> String {
23465        let escape_backslash = matches!(
23466            self.config.dialect,
23467            Some(crate::dialects::DialectType::Snowflake)
23468        );
23469        let mut escaped = String::with_capacity(block.len() + 4);
23470        for ch in block.chars() {
23471            if ch == '\'' {
23472                escaped.push('\\');
23473                escaped.push('\'');
23474            } else if escape_backslash && ch == '\\' {
23475                escaped.push('\\');
23476                escaped.push('\\');
23477            } else {
23478                escaped.push(ch);
23479            }
23480        }
23481        escaped
23482    }
23483
23484    fn write_newline(&mut self) {
23485        self.output.push('\n');
23486    }
23487
23488    fn write_indent(&mut self) {
23489        for _ in 0..self.indent_level {
23490            self.output.push_str(&self.config.indent);
23491        }
23492    }
23493
23494    // === SQLGlot-style pretty printing helpers ===
23495
23496    /// Returns the separator string for pretty printing.
23497    /// Check if the total length of arguments exceeds max_text_width.
23498    /// Used for dynamic line breaking in expressions() formatting.
23499    fn too_wide(&self, args: &[String]) -> bool {
23500        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
23501    }
23502
23503    /// Generate an expression to a string using a temporary non-pretty generator.
23504    /// Useful for width calculations before deciding on formatting.
23505    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
23506        let config = GeneratorConfig {
23507            pretty: false,
23508            dialect: self.config.dialect,
23509            ..Default::default()
23510        };
23511        let mut gen = Generator::with_config(config);
23512        gen.generate_expression(expr)?;
23513        Ok(gen.output)
23514    }
23515
23516    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
23517    /// In pretty mode: newline + indented keyword + newline + indented condition
23518    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
23519        if self.config.pretty {
23520            self.write_newline();
23521            self.write_indent();
23522            self.write_keyword(keyword);
23523            self.write_newline();
23524            self.indent_level += 1;
23525            self.write_indent();
23526            self.generate_expression(condition)?;
23527            self.indent_level -= 1;
23528        } else {
23529            self.write_space();
23530            self.write_keyword(keyword);
23531            self.write_space();
23532            self.generate_expression(condition)?;
23533        }
23534        Ok(())
23535    }
23536
23537    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
23538    /// In pretty mode: each expression on new line with indentation
23539    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
23540        if exprs.is_empty() {
23541            return Ok(());
23542        }
23543
23544        if self.config.pretty {
23545            self.write_newline();
23546            self.write_indent();
23547            self.write_keyword(keyword);
23548            self.write_newline();
23549            self.indent_level += 1;
23550            for (i, expr) in exprs.iter().enumerate() {
23551                if i > 0 {
23552                    self.write(",");
23553                    self.write_newline();
23554                }
23555                self.write_indent();
23556                self.generate_expression(expr)?;
23557            }
23558            self.indent_level -= 1;
23559        } else {
23560            self.write_space();
23561            self.write_keyword(keyword);
23562            self.write_space();
23563            for (i, expr) in exprs.iter().enumerate() {
23564                if i > 0 {
23565                    self.write(", ");
23566                }
23567                self.generate_expression(expr)?;
23568            }
23569        }
23570        Ok(())
23571    }
23572
23573    /// Writes ORDER BY / SORT BY clause with Ordered expressions
23574    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
23575        if orderings.is_empty() {
23576            return Ok(());
23577        }
23578
23579        if self.config.pretty {
23580            self.write_newline();
23581            self.write_indent();
23582            self.write_keyword(keyword);
23583            self.write_newline();
23584            self.indent_level += 1;
23585            for (i, ordered) in orderings.iter().enumerate() {
23586                if i > 0 {
23587                    self.write(",");
23588                    self.write_newline();
23589                }
23590                self.write_indent();
23591                self.generate_ordered(ordered)?;
23592            }
23593            self.indent_level -= 1;
23594        } else {
23595            self.write_space();
23596            self.write_keyword(keyword);
23597            self.write_space();
23598            for (i, ordered) in orderings.iter().enumerate() {
23599                if i > 0 {
23600                    self.write(", ");
23601                }
23602                self.generate_ordered(ordered)?;
23603            }
23604        }
23605        Ok(())
23606    }
23607
23608    /// Writes WINDOW clause with named window definitions
23609    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
23610        if windows.is_empty() {
23611            return Ok(());
23612        }
23613
23614        if self.config.pretty {
23615            self.write_newline();
23616            self.write_indent();
23617            self.write_keyword("WINDOW");
23618            self.write_newline();
23619            self.indent_level += 1;
23620            for (i, named_window) in windows.iter().enumerate() {
23621                if i > 0 {
23622                    self.write(",");
23623                    self.write_newline();
23624                }
23625                self.write_indent();
23626                self.generate_identifier(&named_window.name)?;
23627                self.write_space();
23628                self.write_keyword("AS");
23629                self.write(" (");
23630                self.generate_over(&named_window.spec)?;
23631                self.write(")");
23632            }
23633            self.indent_level -= 1;
23634        } else {
23635            self.write_space();
23636            self.write_keyword("WINDOW");
23637            self.write_space();
23638            for (i, named_window) in windows.iter().enumerate() {
23639                if i > 0 {
23640                    self.write(", ");
23641                }
23642                self.generate_identifier(&named_window.name)?;
23643                self.write_space();
23644                self.write_keyword("AS");
23645                self.write(" (");
23646                self.generate_over(&named_window.spec)?;
23647                self.write(")");
23648            }
23649        }
23650        Ok(())
23651    }
23652
23653    // === BATCH-GENERATED STUB METHODS (481 variants) ===
23654    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
23655        // AI_AGG(this, expression)
23656        self.write_keyword("AI_AGG");
23657        self.write("(");
23658        self.generate_expression(&e.this)?;
23659        self.write(", ");
23660        self.generate_expression(&e.expression)?;
23661        self.write(")");
23662        Ok(())
23663    }
23664
23665    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
23666        // AI_CLASSIFY(input, [categories], [config])
23667        self.write_keyword("AI_CLASSIFY");
23668        self.write("(");
23669        self.generate_expression(&e.this)?;
23670        if let Some(categories) = &e.categories {
23671            self.write(", ");
23672            self.generate_expression(categories)?;
23673        }
23674        if let Some(config) = &e.config {
23675            self.write(", ");
23676            self.generate_expression(config)?;
23677        }
23678        self.write(")");
23679        Ok(())
23680    }
23681
23682    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
23683        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
23684        self.write_keyword("ADD");
23685        self.write_space();
23686        if e.exists {
23687            self.write_keyword("IF NOT EXISTS");
23688            self.write_space();
23689        }
23690        self.generate_expression(&e.this)?;
23691        if let Some(location) = &e.location {
23692            self.write_space();
23693            self.generate_expression(location)?;
23694        }
23695        Ok(())
23696    }
23697
23698    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
23699        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
23700        self.write_keyword("ALGORITHM");
23701        self.write("=");
23702        self.generate_expression(&e.this)?;
23703        Ok(())
23704    }
23705
23706    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
23707        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
23708        self.generate_expression(&e.this)?;
23709        self.write_space();
23710        self.write_keyword("AS");
23711        self.write(" (");
23712        for (i, expr) in e.expressions.iter().enumerate() {
23713            if i > 0 {
23714                self.write(", ");
23715            }
23716            self.generate_expression(expr)?;
23717        }
23718        self.write(")");
23719        Ok(())
23720    }
23721
23722    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
23723        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
23724        self.write_keyword("ALLOWED_VALUES");
23725        self.write_space();
23726        for (i, expr) in e.expressions.iter().enumerate() {
23727            if i > 0 {
23728                self.write(", ");
23729            }
23730            self.generate_expression(expr)?;
23731        }
23732        Ok(())
23733    }
23734
23735    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
23736        // Python: complex logic based on dtype, default, comment, visible, etc.
23737        self.write_keyword("ALTER COLUMN");
23738        self.write_space();
23739        self.generate_expression(&e.this)?;
23740
23741        if let Some(dtype) = &e.dtype {
23742            self.write_space();
23743            self.write_keyword("SET DATA TYPE");
23744            self.write_space();
23745            self.generate_expression(dtype)?;
23746            if let Some(collate) = &e.collate {
23747                self.write_space();
23748                self.write_keyword("COLLATE");
23749                self.write_space();
23750                self.generate_expression(collate)?;
23751            }
23752            if let Some(using) = &e.using {
23753                self.write_space();
23754                self.write_keyword("USING");
23755                self.write_space();
23756                self.generate_expression(using)?;
23757            }
23758        } else if let Some(default) = &e.default {
23759            self.write_space();
23760            self.write_keyword("SET DEFAULT");
23761            self.write_space();
23762            self.generate_expression(default)?;
23763        } else if let Some(comment) = &e.comment {
23764            self.write_space();
23765            self.write_keyword("COMMENT");
23766            self.write_space();
23767            self.generate_expression(comment)?;
23768        } else if let Some(drop) = &e.drop {
23769            self.write_space();
23770            self.write_keyword("DROP");
23771            self.write_space();
23772            self.generate_expression(drop)?;
23773        } else if let Some(visible) = &e.visible {
23774            self.write_space();
23775            self.generate_expression(visible)?;
23776        } else if let Some(rename_to) = &e.rename_to {
23777            self.write_space();
23778            self.write_keyword("RENAME TO");
23779            self.write_space();
23780            self.generate_expression(rename_to)?;
23781        } else if let Some(allow_null) = &e.allow_null {
23782            self.write_space();
23783            self.generate_expression(allow_null)?;
23784        }
23785        Ok(())
23786    }
23787
23788    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
23789        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
23790        self.write_keyword("ALTER SESSION");
23791        self.write_space();
23792        if e.unset.is_some() {
23793            self.write_keyword("UNSET");
23794        } else {
23795            self.write_keyword("SET");
23796        }
23797        self.write_space();
23798        for (i, expr) in e.expressions.iter().enumerate() {
23799            if i > 0 {
23800                self.write(", ");
23801            }
23802            self.generate_expression(expr)?;
23803        }
23804        Ok(())
23805    }
23806
23807    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
23808        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
23809        self.write_keyword("SET");
23810
23811        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
23812        if let Some(opt) = &e.option {
23813            self.write_space();
23814            self.generate_expression(opt)?;
23815        }
23816
23817        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
23818        // Check if expressions look like property assignments
23819        if !e.expressions.is_empty() {
23820            // Check if this looks like property assignments (for SET PROPERTIES)
23821            let is_properties = e
23822                .expressions
23823                .iter()
23824                .any(|expr| matches!(expr, Expression::Eq(_)));
23825            if is_properties && e.option.is_none() {
23826                self.write_space();
23827                self.write_keyword("PROPERTIES");
23828            }
23829            self.write_space();
23830            for (i, expr) in e.expressions.iter().enumerate() {
23831                if i > 0 {
23832                    self.write(", ");
23833                }
23834                self.generate_expression(expr)?;
23835            }
23836        }
23837
23838        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
23839        if let Some(file_format) = &e.file_format {
23840            self.write(" ");
23841            self.write_keyword("STAGE_FILE_FORMAT");
23842            self.write(" = (");
23843            self.generate_space_separated_properties(file_format)?;
23844            self.write(")");
23845        }
23846
23847        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
23848        if let Some(copy_options) = &e.copy_options {
23849            self.write(" ");
23850            self.write_keyword("STAGE_COPY_OPTIONS");
23851            self.write(" = (");
23852            self.generate_space_separated_properties(copy_options)?;
23853            self.write(")");
23854        }
23855
23856        // Generate TAG ...
23857        if let Some(tag) = &e.tag {
23858            self.write(" ");
23859            self.write_keyword("TAG");
23860            self.write(" ");
23861            self.generate_expression(tag)?;
23862        }
23863
23864        Ok(())
23865    }
23866
23867    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
23868    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
23869        match expr {
23870            Expression::Tuple(t) => {
23871                for (i, prop) in t.expressions.iter().enumerate() {
23872                    if i > 0 {
23873                        self.write(" ");
23874                    }
23875                    self.generate_expression(prop)?;
23876                }
23877            }
23878            _ => {
23879                self.generate_expression(expr)?;
23880            }
23881        }
23882        Ok(())
23883    }
23884
23885    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
23886        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
23887        self.write_keyword("ALTER");
23888        if e.compound.is_some() {
23889            self.write_space();
23890            self.write_keyword("COMPOUND");
23891        }
23892        self.write_space();
23893        self.write_keyword("SORTKEY");
23894        self.write_space();
23895        if let Some(this) = &e.this {
23896            self.generate_expression(this)?;
23897        } else if !e.expressions.is_empty() {
23898            self.write("(");
23899            for (i, expr) in e.expressions.iter().enumerate() {
23900                if i > 0 {
23901                    self.write(", ");
23902                }
23903                self.generate_expression(expr)?;
23904            }
23905            self.write(")");
23906        }
23907        Ok(())
23908    }
23909
23910    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
23911        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
23912        self.write_keyword("ANALYZE");
23913        if !e.options.is_empty() {
23914            self.write_space();
23915            for (i, opt) in e.options.iter().enumerate() {
23916                if i > 0 {
23917                    self.write_space();
23918                }
23919                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
23920                if let Expression::Identifier(id) = opt {
23921                    self.write_keyword(&id.name);
23922                } else {
23923                    self.generate_expression(opt)?;
23924                }
23925            }
23926        }
23927        if let Some(kind) = &e.kind {
23928            self.write_space();
23929            self.write_keyword(kind);
23930        }
23931        if let Some(this) = &e.this {
23932            self.write_space();
23933            self.generate_expression(this)?;
23934        }
23935        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
23936        if !e.columns.is_empty() {
23937            self.write("(");
23938            for (i, col) in e.columns.iter().enumerate() {
23939                if i > 0 {
23940                    self.write(", ");
23941                }
23942                self.write(col);
23943            }
23944            self.write(")");
23945        }
23946        if let Some(partition) = &e.partition {
23947            self.write_space();
23948            self.generate_expression(partition)?;
23949        }
23950        if let Some(mode) = &e.mode {
23951            self.write_space();
23952            self.generate_expression(mode)?;
23953        }
23954        if let Some(expression) = &e.expression {
23955            self.write_space();
23956            self.generate_expression(expression)?;
23957        }
23958        if !e.properties.is_empty() {
23959            self.write_space();
23960            self.write_keyword(self.config.with_properties_prefix);
23961            self.write(" (");
23962            for (i, prop) in e.properties.iter().enumerate() {
23963                if i > 0 {
23964                    self.write(", ");
23965                }
23966                self.generate_expression(prop)?;
23967            }
23968            self.write(")");
23969        }
23970        Ok(())
23971    }
23972
23973    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
23974        // Python: return f"DELETE{kind} STATISTICS"
23975        self.write_keyword("DELETE");
23976        if let Some(kind) = &e.kind {
23977            self.write_space();
23978            self.write_keyword(kind);
23979        }
23980        self.write_space();
23981        self.write_keyword("STATISTICS");
23982        Ok(())
23983    }
23984
23985    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
23986        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
23987        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
23988        if let Expression::Identifier(id) = e.this.as_ref() {
23989            self.write_keyword(&id.name);
23990        } else {
23991            self.generate_expression(&e.this)?;
23992        }
23993        self.write_space();
23994        self.write_keyword("HISTOGRAM ON");
23995        self.write_space();
23996        for (i, expr) in e.expressions.iter().enumerate() {
23997            if i > 0 {
23998                self.write(", ");
23999            }
24000            self.generate_expression(expr)?;
24001        }
24002        if let Some(expression) = &e.expression {
24003            self.write_space();
24004            self.generate_expression(expression)?;
24005        }
24006        if let Some(update_options) = &e.update_options {
24007            self.write_space();
24008            self.generate_expression(update_options)?;
24009            self.write_space();
24010            self.write_keyword("UPDATE");
24011        }
24012        Ok(())
24013    }
24014
24015    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
24016        // Python: return f"LIST CHAINED ROWS{inner_expression}"
24017        self.write_keyword("LIST CHAINED ROWS");
24018        if let Some(expression) = &e.expression {
24019            self.write_space();
24020            self.write_keyword("INTO");
24021            self.write_space();
24022            self.generate_expression(expression)?;
24023        }
24024        Ok(())
24025    }
24026
24027    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
24028        // Python: return f"SAMPLE {sample} {kind}"
24029        self.write_keyword("SAMPLE");
24030        self.write_space();
24031        if let Some(sample) = &e.sample {
24032            self.generate_expression(sample)?;
24033            self.write_space();
24034        }
24035        self.write_keyword(&e.kind);
24036        Ok(())
24037    }
24038
24039    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
24040        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
24041        self.write_keyword(&e.kind);
24042        if let Some(option) = &e.option {
24043            self.write_space();
24044            self.generate_expression(option)?;
24045        }
24046        self.write_space();
24047        self.write_keyword("STATISTICS");
24048        if let Some(this) = &e.this {
24049            self.write_space();
24050            self.generate_expression(this)?;
24051        }
24052        if !e.expressions.is_empty() {
24053            self.write_space();
24054            for (i, expr) in e.expressions.iter().enumerate() {
24055                if i > 0 {
24056                    self.write(", ");
24057                }
24058                self.generate_expression(expr)?;
24059            }
24060        }
24061        Ok(())
24062    }
24063
24064    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
24065        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
24066        self.write_keyword("VALIDATE");
24067        self.write_space();
24068        self.write_keyword(&e.kind);
24069        if let Some(this) = &e.this {
24070            self.write_space();
24071            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
24072            if let Expression::Identifier(id) = this.as_ref() {
24073                self.write_keyword(&id.name);
24074            } else {
24075                self.generate_expression(this)?;
24076            }
24077        }
24078        if let Some(expression) = &e.expression {
24079            self.write_space();
24080            self.write_keyword("INTO");
24081            self.write_space();
24082            self.generate_expression(expression)?;
24083        }
24084        Ok(())
24085    }
24086
24087    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
24088        // Python: return f"WITH {expressions}"
24089        self.write_keyword("WITH");
24090        self.write_space();
24091        for (i, expr) in e.expressions.iter().enumerate() {
24092            if i > 0 {
24093                self.write(", ");
24094            }
24095            self.generate_expression(expr)?;
24096        }
24097        Ok(())
24098    }
24099
24100    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
24101        // Anonymous represents a generic function call: FUNC_NAME(args...)
24102        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
24103        self.generate_expression(&e.this)?;
24104        self.write("(");
24105        for (i, arg) in e.expressions.iter().enumerate() {
24106            if i > 0 {
24107                self.write(", ");
24108            }
24109            self.generate_expression(arg)?;
24110        }
24111        self.write(")");
24112        Ok(())
24113    }
24114
24115    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
24116        // Same as Anonymous but for aggregate functions
24117        self.generate_expression(&e.this)?;
24118        self.write("(");
24119        for (i, arg) in e.expressions.iter().enumerate() {
24120            if i > 0 {
24121                self.write(", ");
24122            }
24123            self.generate_expression(arg)?;
24124        }
24125        self.write(")");
24126        Ok(())
24127    }
24128
24129    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
24130        // Python: return f"{this} APPLY({expr})"
24131        self.generate_expression(&e.this)?;
24132        self.write_space();
24133        self.write_keyword("APPLY");
24134        self.write("(");
24135        self.generate_expression(&e.expression)?;
24136        self.write(")");
24137        Ok(())
24138    }
24139
24140    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
24141        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
24142        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
24143        self.write("(");
24144        self.generate_expression(&e.this)?;
24145        if let Some(percentile) = &e.percentile {
24146            self.write(", ");
24147            self.generate_expression(percentile)?;
24148        }
24149        self.write(")");
24150        Ok(())
24151    }
24152
24153    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
24154        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
24155        self.write_keyword("APPROX_QUANTILE");
24156        self.write("(");
24157        self.generate_expression(&e.this)?;
24158        if let Some(quantile) = &e.quantile {
24159            self.write(", ");
24160            self.generate_expression(quantile)?;
24161        }
24162        if let Some(accuracy) = &e.accuracy {
24163            self.write(", ");
24164            self.generate_expression(accuracy)?;
24165        }
24166        if let Some(weight) = &e.weight {
24167            self.write(", ");
24168            self.generate_expression(weight)?;
24169        }
24170        self.write(")");
24171        Ok(())
24172    }
24173
24174    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
24175        // APPROX_QUANTILES(this, expression)
24176        self.write_keyword("APPROX_QUANTILES");
24177        self.write("(");
24178        self.generate_expression(&e.this)?;
24179        if let Some(expression) = &e.expression {
24180            self.write(", ");
24181            self.generate_expression(expression)?;
24182        }
24183        self.write(")");
24184        Ok(())
24185    }
24186
24187    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
24188        // APPROX_TOP_K(this[, expression][, counters])
24189        self.write_keyword("APPROX_TOP_K");
24190        self.write("(");
24191        self.generate_expression(&e.this)?;
24192        if let Some(expression) = &e.expression {
24193            self.write(", ");
24194            self.generate_expression(expression)?;
24195        }
24196        if let Some(counters) = &e.counters {
24197            self.write(", ");
24198            self.generate_expression(counters)?;
24199        }
24200        self.write(")");
24201        Ok(())
24202    }
24203
24204    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
24205        // APPROX_TOP_K_ACCUMULATE(this[, expression])
24206        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
24207        self.write("(");
24208        self.generate_expression(&e.this)?;
24209        if let Some(expression) = &e.expression {
24210            self.write(", ");
24211            self.generate_expression(expression)?;
24212        }
24213        self.write(")");
24214        Ok(())
24215    }
24216
24217    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
24218        // APPROX_TOP_K_COMBINE(this[, expression])
24219        self.write_keyword("APPROX_TOP_K_COMBINE");
24220        self.write("(");
24221        self.generate_expression(&e.this)?;
24222        if let Some(expression) = &e.expression {
24223            self.write(", ");
24224            self.generate_expression(expression)?;
24225        }
24226        self.write(")");
24227        Ok(())
24228    }
24229
24230    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
24231        // APPROX_TOP_K_ESTIMATE(this[, expression])
24232        self.write_keyword("APPROX_TOP_K_ESTIMATE");
24233        self.write("(");
24234        self.generate_expression(&e.this)?;
24235        if let Some(expression) = &e.expression {
24236            self.write(", ");
24237            self.generate_expression(expression)?;
24238        }
24239        self.write(")");
24240        Ok(())
24241    }
24242
24243    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
24244        // APPROX_TOP_SUM(this, expression[, count])
24245        self.write_keyword("APPROX_TOP_SUM");
24246        self.write("(");
24247        self.generate_expression(&e.this)?;
24248        self.write(", ");
24249        self.generate_expression(&e.expression)?;
24250        if let Some(count) = &e.count {
24251            self.write(", ");
24252            self.generate_expression(count)?;
24253        }
24254        self.write(")");
24255        Ok(())
24256    }
24257
24258    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
24259        // ARG_MAX(this, expression[, count])
24260        self.write_keyword("ARG_MAX");
24261        self.write("(");
24262        self.generate_expression(&e.this)?;
24263        self.write(", ");
24264        self.generate_expression(&e.expression)?;
24265        if let Some(count) = &e.count {
24266            self.write(", ");
24267            self.generate_expression(count)?;
24268        }
24269        self.write(")");
24270        Ok(())
24271    }
24272
24273    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
24274        // ARG_MIN(this, expression[, count])
24275        self.write_keyword("ARG_MIN");
24276        self.write("(");
24277        self.generate_expression(&e.this)?;
24278        self.write(", ");
24279        self.generate_expression(&e.expression)?;
24280        if let Some(count) = &e.count {
24281            self.write(", ");
24282            self.generate_expression(count)?;
24283        }
24284        self.write(")");
24285        Ok(())
24286    }
24287
24288    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
24289        // ARRAY_ALL(this, expression)
24290        self.write_keyword("ARRAY_ALL");
24291        self.write("(");
24292        self.generate_expression(&e.this)?;
24293        self.write(", ");
24294        self.generate_expression(&e.expression)?;
24295        self.write(")");
24296        Ok(())
24297    }
24298
24299    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
24300        // ARRAY_ANY(this, expression) - fallback implementation
24301        self.write_keyword("ARRAY_ANY");
24302        self.write("(");
24303        self.generate_expression(&e.this)?;
24304        self.write(", ");
24305        self.generate_expression(&e.expression)?;
24306        self.write(")");
24307        Ok(())
24308    }
24309
24310    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
24311        // ARRAY_CONSTRUCT_COMPACT(expressions...)
24312        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
24313        self.write("(");
24314        for (i, expr) in e.expressions.iter().enumerate() {
24315            if i > 0 {
24316                self.write(", ");
24317            }
24318            self.generate_expression(expr)?;
24319        }
24320        self.write(")");
24321        Ok(())
24322    }
24323
24324    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
24325        // ARRAY_SUM(this[, expression])
24326        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24327            self.write("arraySum");
24328        } else {
24329            self.write_keyword("ARRAY_SUM");
24330        }
24331        self.write("(");
24332        self.generate_expression(&e.this)?;
24333        if let Some(expression) = &e.expression {
24334            self.write(", ");
24335            self.generate_expression(expression)?;
24336        }
24337        self.write(")");
24338        Ok(())
24339    }
24340
24341    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
24342        // Python: return f"{this} AT {index}"
24343        self.generate_expression(&e.this)?;
24344        self.write_space();
24345        self.write_keyword("AT");
24346        self.write_space();
24347        self.generate_expression(&e.expression)?;
24348        Ok(())
24349    }
24350
24351    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
24352        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
24353        self.write_keyword("ATTACH");
24354        if e.exists {
24355            self.write_space();
24356            self.write_keyword("IF NOT EXISTS");
24357        }
24358        self.write_space();
24359        self.generate_expression(&e.this)?;
24360        if !e.expressions.is_empty() {
24361            self.write(" (");
24362            for (i, expr) in e.expressions.iter().enumerate() {
24363                if i > 0 {
24364                    self.write(", ");
24365                }
24366                self.generate_expression(expr)?;
24367            }
24368            self.write(")");
24369        }
24370        Ok(())
24371    }
24372
24373    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
24374        // AttachOption: this [expression]
24375        // Python sqlglot: no equals sign, just space-separated
24376        self.generate_expression(&e.this)?;
24377        if let Some(expression) = &e.expression {
24378            self.write_space();
24379            self.generate_expression(expression)?;
24380        }
24381        Ok(())
24382    }
24383
24384    /// Generate the auto_increment keyword and options for a column definition.
24385    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
24386    /// GENERATED AS IDENTITY, etc.
24387    fn generate_auto_increment_keyword(
24388        &mut self,
24389        col: &crate::expressions::ColumnDef,
24390    ) -> Result<()> {
24391        use crate::dialects::DialectType;
24392        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
24393            self.write_keyword("IDENTITY");
24394            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24395                self.write("(");
24396                if let Some(ref start) = col.auto_increment_start {
24397                    self.generate_expression(start)?;
24398                } else {
24399                    self.write("0");
24400                }
24401                self.write(", ");
24402                if let Some(ref inc) = col.auto_increment_increment {
24403                    self.generate_expression(inc)?;
24404                } else {
24405                    self.write("1");
24406                }
24407                self.write(")");
24408            }
24409        } else if matches!(
24410            self.config.dialect,
24411            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
24412        ) {
24413            self.write_keyword("AUTOINCREMENT");
24414            if let Some(ref start) = col.auto_increment_start {
24415                self.write_space();
24416                self.write_keyword("START");
24417                self.write_space();
24418                self.generate_expression(start)?;
24419            }
24420            if let Some(ref inc) = col.auto_increment_increment {
24421                self.write_space();
24422                self.write_keyword("INCREMENT");
24423                self.write_space();
24424                self.generate_expression(inc)?;
24425            }
24426            if let Some(order) = col.auto_increment_order {
24427                self.write_space();
24428                if order {
24429                    self.write_keyword("ORDER");
24430                } else {
24431                    self.write_keyword("NOORDER");
24432                }
24433            }
24434        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
24435            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
24436            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24437                self.write(" (");
24438                let mut first = true;
24439                if let Some(ref start) = col.auto_increment_start {
24440                    self.write_keyword("START WITH");
24441                    self.write_space();
24442                    self.generate_expression(start)?;
24443                    first = false;
24444                }
24445                if let Some(ref inc) = col.auto_increment_increment {
24446                    if !first {
24447                        self.write_space();
24448                    }
24449                    self.write_keyword("INCREMENT BY");
24450                    self.write_space();
24451                    self.generate_expression(inc)?;
24452                }
24453                self.write(")");
24454            }
24455        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
24456            self.write_keyword("GENERATED ALWAYS AS IDENTITY");
24457            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24458                self.write(" (");
24459                let mut first = true;
24460                if let Some(ref start) = col.auto_increment_start {
24461                    self.write_keyword("START WITH");
24462                    self.write_space();
24463                    self.generate_expression(start)?;
24464                    first = false;
24465                }
24466                if let Some(ref inc) = col.auto_increment_increment {
24467                    if !first {
24468                        self.write_space();
24469                    }
24470                    self.write_keyword("INCREMENT BY");
24471                    self.write_space();
24472                    self.generate_expression(inc)?;
24473                }
24474                self.write(")");
24475            }
24476        } else if matches!(
24477            self.config.dialect,
24478            Some(DialectType::TSQL) | Some(DialectType::Fabric)
24479        ) {
24480            self.write_keyword("IDENTITY");
24481            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
24482                self.write("(");
24483                if let Some(ref start) = col.auto_increment_start {
24484                    self.generate_expression(start)?;
24485                } else {
24486                    self.write("0");
24487                }
24488                self.write(", ");
24489                if let Some(ref inc) = col.auto_increment_increment {
24490                    self.generate_expression(inc)?;
24491                } else {
24492                    self.write("1");
24493                }
24494                self.write(")");
24495            }
24496        } else {
24497            self.write_keyword("AUTO_INCREMENT");
24498            if let Some(ref start) = col.auto_increment_start {
24499                self.write_space();
24500                self.write_keyword("START");
24501                self.write_space();
24502                self.generate_expression(start)?;
24503            }
24504            if let Some(ref inc) = col.auto_increment_increment {
24505                self.write_space();
24506                self.write_keyword("INCREMENT");
24507                self.write_space();
24508                self.generate_expression(inc)?;
24509            }
24510            if let Some(order) = col.auto_increment_order {
24511                self.write_space();
24512                if order {
24513                    self.write_keyword("ORDER");
24514                } else {
24515                    self.write_keyword("NOORDER");
24516                }
24517            }
24518        }
24519        Ok(())
24520    }
24521
24522    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
24523        // AUTO_INCREMENT=value
24524        self.write_keyword("AUTO_INCREMENT");
24525        self.write("=");
24526        self.generate_expression(&e.this)?;
24527        Ok(())
24528    }
24529
24530    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
24531        // AUTO_REFRESH=value
24532        self.write_keyword("AUTO_REFRESH");
24533        self.write("=");
24534        self.generate_expression(&e.this)?;
24535        Ok(())
24536    }
24537
24538    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
24539        // BACKUP YES|NO (Redshift syntax uses space, not equals)
24540        self.write_keyword("BACKUP");
24541        self.write_space();
24542        self.generate_expression(&e.this)?;
24543        Ok(())
24544    }
24545
24546    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
24547        // BASE64_DECODE_BINARY(this[, alphabet])
24548        self.write_keyword("BASE64_DECODE_BINARY");
24549        self.write("(");
24550        self.generate_expression(&e.this)?;
24551        if let Some(alphabet) = &e.alphabet {
24552            self.write(", ");
24553            self.generate_expression(alphabet)?;
24554        }
24555        self.write(")");
24556        Ok(())
24557    }
24558
24559    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
24560        // BASE64_DECODE_STRING(this[, alphabet])
24561        self.write_keyword("BASE64_DECODE_STRING");
24562        self.write("(");
24563        self.generate_expression(&e.this)?;
24564        if let Some(alphabet) = &e.alphabet {
24565            self.write(", ");
24566            self.generate_expression(alphabet)?;
24567        }
24568        self.write(")");
24569        Ok(())
24570    }
24571
24572    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
24573        // BASE64_ENCODE(this[, max_line_length][, alphabet])
24574        self.write_keyword("BASE64_ENCODE");
24575        self.write("(");
24576        self.generate_expression(&e.this)?;
24577        if let Some(max_line_length) = &e.max_line_length {
24578            self.write(", ");
24579            self.generate_expression(max_line_length)?;
24580        }
24581        if let Some(alphabet) = &e.alphabet {
24582            self.write(", ");
24583            self.generate_expression(alphabet)?;
24584        }
24585        self.write(")");
24586        Ok(())
24587    }
24588
24589    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
24590        // BLOCKCOMPRESSION=... (complex Teradata property)
24591        self.write_keyword("BLOCKCOMPRESSION");
24592        self.write("=");
24593        if let Some(autotemp) = &e.autotemp {
24594            self.write_keyword("AUTOTEMP");
24595            self.write("(");
24596            self.generate_expression(autotemp)?;
24597            self.write(")");
24598        }
24599        if let Some(always) = &e.always {
24600            self.generate_expression(always)?;
24601        }
24602        if let Some(default) = &e.default {
24603            self.generate_expression(default)?;
24604        }
24605        if let Some(manual) = &e.manual {
24606            self.generate_expression(manual)?;
24607        }
24608        if let Some(never) = &e.never {
24609            self.generate_expression(never)?;
24610        }
24611        Ok(())
24612    }
24613
24614    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
24615        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
24616        self.write("((");
24617        self.generate_expression(&e.this)?;
24618        self.write(") ");
24619        self.write_keyword("AND");
24620        self.write(" (");
24621        self.generate_expression(&e.expression)?;
24622        self.write("))");
24623        Ok(())
24624    }
24625
24626    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
24627        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
24628        self.write("((");
24629        self.generate_expression(&e.this)?;
24630        self.write(") ");
24631        self.write_keyword("OR");
24632        self.write(" (");
24633        self.generate_expression(&e.expression)?;
24634        self.write("))");
24635        Ok(())
24636    }
24637
24638    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
24639        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
24640        self.write_keyword("BUILD");
24641        self.write_space();
24642        self.generate_expression(&e.this)?;
24643        Ok(())
24644    }
24645
24646    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
24647        // Byte string literal like B'...' or X'...'
24648        self.generate_expression(&e.this)?;
24649        Ok(())
24650    }
24651
24652    fn generate_case_specific_column_constraint(
24653        &mut self,
24654        e: &CaseSpecificColumnConstraint,
24655    ) -> Result<()> {
24656        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
24657        if e.not_.is_some() {
24658            self.write_keyword("NOT");
24659            self.write_space();
24660        }
24661        self.write_keyword("CASESPECIFIC");
24662        Ok(())
24663    }
24664
24665    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
24666        // Cast to string type (dialect-specific)
24667        self.write_keyword("CAST");
24668        self.write("(");
24669        self.generate_expression(&e.this)?;
24670        if self.config.dialect == Some(DialectType::ClickHouse) {
24671            // ClickHouse: CAST(expr, 'type_string')
24672            self.write(", ");
24673        } else {
24674            self.write_space();
24675            self.write_keyword("AS");
24676            self.write_space();
24677        }
24678        if let Some(to) = &e.to {
24679            self.generate_expression(to)?;
24680        }
24681        self.write(")");
24682        Ok(())
24683    }
24684
24685    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
24686        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
24687        // Python: f"CHANGES ({information}){at_before}{end}"
24688        self.write_keyword("CHANGES");
24689        self.write(" (");
24690        if let Some(information) = &e.information {
24691            self.write_keyword("INFORMATION");
24692            self.write(" => ");
24693            self.generate_expression(information)?;
24694        }
24695        self.write(")");
24696        // at_before and end are HistoricalData expressions that generate their own keywords
24697        if let Some(at_before) = &e.at_before {
24698            self.write(" ");
24699            self.generate_expression(at_before)?;
24700        }
24701        if let Some(end) = &e.end {
24702            self.write(" ");
24703            self.generate_expression(end)?;
24704        }
24705        Ok(())
24706    }
24707
24708    fn generate_character_set_column_constraint(
24709        &mut self,
24710        e: &CharacterSetColumnConstraint,
24711    ) -> Result<()> {
24712        // CHARACTER SET charset_name
24713        self.write_keyword("CHARACTER SET");
24714        self.write_space();
24715        self.generate_expression(&e.this)?;
24716        Ok(())
24717    }
24718
24719    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
24720        // [DEFAULT] CHARACTER SET=value
24721        if e.default.is_some() {
24722            self.write_keyword("DEFAULT");
24723            self.write_space();
24724        }
24725        self.write_keyword("CHARACTER SET");
24726        self.write("=");
24727        self.generate_expression(&e.this)?;
24728        Ok(())
24729    }
24730
24731    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
24732        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
24733        self.write_keyword("CHECK");
24734        self.write(" (");
24735        self.generate_expression(&e.this)?;
24736        self.write(")");
24737        if e.enforced.is_some() {
24738            self.write_space();
24739            self.write_keyword("ENFORCED");
24740        }
24741        Ok(())
24742    }
24743
24744    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
24745        // CHECK_JSON(this)
24746        self.write_keyword("CHECK_JSON");
24747        self.write("(");
24748        self.generate_expression(&e.this)?;
24749        self.write(")");
24750        Ok(())
24751    }
24752
24753    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
24754        // CHECK_XML(this)
24755        self.write_keyword("CHECK_XML");
24756        self.write("(");
24757        self.generate_expression(&e.this)?;
24758        self.write(")");
24759        Ok(())
24760    }
24761
24762    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
24763        // CHECKSUM=[ON|OFF|DEFAULT]
24764        self.write_keyword("CHECKSUM");
24765        self.write("=");
24766        if e.on.is_some() {
24767            self.write_keyword("ON");
24768        } else if e.default.is_some() {
24769            self.write_keyword("DEFAULT");
24770        } else {
24771            self.write_keyword("OFF");
24772        }
24773        Ok(())
24774    }
24775
24776    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
24777        // Python: return f"{shallow}{keyword} {this}"
24778        if e.shallow.is_some() {
24779            self.write_keyword("SHALLOW");
24780            self.write_space();
24781        }
24782        if e.copy.is_some() {
24783            self.write_keyword("COPY");
24784        } else {
24785            self.write_keyword("CLONE");
24786        }
24787        self.write_space();
24788        self.generate_expression(&e.this)?;
24789        Ok(())
24790    }
24791
24792    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
24793        // CLUSTER BY (expressions)
24794        self.write_keyword("CLUSTER BY");
24795        self.write(" (");
24796        for (i, ord) in e.expressions.iter().enumerate() {
24797            if i > 0 {
24798                self.write(", ");
24799            }
24800            self.generate_ordered(ord)?;
24801        }
24802        self.write(")");
24803        Ok(())
24804    }
24805
24806    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
24807        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
24808        self.write_keyword("CLUSTERED BY");
24809        self.write(" (");
24810        for (i, expr) in e.expressions.iter().enumerate() {
24811            if i > 0 {
24812                self.write(", ");
24813            }
24814            self.generate_expression(expr)?;
24815        }
24816        self.write(")");
24817        if let Some(sorted_by) = &e.sorted_by {
24818            self.write_space();
24819            self.write_keyword("SORTED BY");
24820            self.write(" (");
24821            // Unwrap Tuple to avoid double parentheses
24822            if let Expression::Tuple(t) = sorted_by.as_ref() {
24823                for (i, expr) in t.expressions.iter().enumerate() {
24824                    if i > 0 {
24825                        self.write(", ");
24826                    }
24827                    self.generate_expression(expr)?;
24828                }
24829            } else {
24830                self.generate_expression(sorted_by)?;
24831            }
24832            self.write(")");
24833        }
24834        if let Some(buckets) = &e.buckets {
24835            self.write_space();
24836            self.write_keyword("INTO");
24837            self.write_space();
24838            self.generate_expression(buckets)?;
24839            self.write_space();
24840            self.write_keyword("BUCKETS");
24841        }
24842        Ok(())
24843    }
24844
24845    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
24846        // [DEFAULT] COLLATE [=] value
24847        // BigQuery uses space: DEFAULT COLLATE 'en'
24848        // Others use equals: COLLATE='en'
24849        if e.default.is_some() {
24850            self.write_keyword("DEFAULT");
24851            self.write_space();
24852        }
24853        self.write_keyword("COLLATE");
24854        // BigQuery uses space between COLLATE and value
24855        match self.config.dialect {
24856            Some(DialectType::BigQuery) => self.write_space(),
24857            _ => self.write("="),
24858        }
24859        self.generate_expression(&e.this)?;
24860        Ok(())
24861    }
24862
24863    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
24864        // ColumnConstraint is an enum
24865        match e {
24866            ColumnConstraint::NotNull => {
24867                self.write_keyword("NOT NULL");
24868            }
24869            ColumnConstraint::Null => {
24870                self.write_keyword("NULL");
24871            }
24872            ColumnConstraint::Unique => {
24873                self.write_keyword("UNIQUE");
24874            }
24875            ColumnConstraint::PrimaryKey => {
24876                self.write_keyword("PRIMARY KEY");
24877            }
24878            ColumnConstraint::Default(expr) => {
24879                self.write_keyword("DEFAULT");
24880                self.write_space();
24881                self.generate_expression(expr)?;
24882            }
24883            ColumnConstraint::Check(expr) => {
24884                self.write_keyword("CHECK");
24885                self.write(" (");
24886                self.generate_expression(expr)?;
24887                self.write(")");
24888            }
24889            ColumnConstraint::References(fk_ref) => {
24890                if fk_ref.has_foreign_key_keywords {
24891                    self.write_keyword("FOREIGN KEY");
24892                    self.write_space();
24893                }
24894                self.write_keyword("REFERENCES");
24895                self.write_space();
24896                self.generate_table(&fk_ref.table)?;
24897                if !fk_ref.columns.is_empty() {
24898                    self.write(" (");
24899                    for (i, col) in fk_ref.columns.iter().enumerate() {
24900                        if i > 0 {
24901                            self.write(", ");
24902                        }
24903                        self.generate_identifier(col)?;
24904                    }
24905                    self.write(")");
24906                }
24907            }
24908            ColumnConstraint::GeneratedAsIdentity(gen) => {
24909                self.write_keyword("GENERATED");
24910                self.write_space();
24911                if gen.always {
24912                    self.write_keyword("ALWAYS");
24913                } else {
24914                    self.write_keyword("BY DEFAULT");
24915                    if gen.on_null {
24916                        self.write_space();
24917                        self.write_keyword("ON NULL");
24918                    }
24919                }
24920                self.write_space();
24921                self.write_keyword("AS IDENTITY");
24922            }
24923            ColumnConstraint::Collate(collation) => {
24924                self.write_keyword("COLLATE");
24925                self.write_space();
24926                self.generate_identifier(collation)?;
24927            }
24928            ColumnConstraint::Comment(comment) => {
24929                self.write_keyword("COMMENT");
24930                self.write(" '");
24931                self.write(comment);
24932                self.write("'");
24933            }
24934            ColumnConstraint::ComputedColumn(cc) => {
24935                self.generate_computed_column_inline(cc)?;
24936            }
24937            ColumnConstraint::GeneratedAsRow(gar) => {
24938                self.generate_generated_as_row_inline(gar)?;
24939            }
24940            ColumnConstraint::Tags(tags) => {
24941                self.write_keyword("TAG");
24942                self.write(" (");
24943                for (i, expr) in tags.expressions.iter().enumerate() {
24944                    if i > 0 {
24945                        self.write(", ");
24946                    }
24947                    self.generate_expression(expr)?;
24948                }
24949                self.write(")");
24950            }
24951            ColumnConstraint::Path(path_expr) => {
24952                self.write_keyword("PATH");
24953                self.write_space();
24954                self.generate_expression(path_expr)?;
24955            }
24956        }
24957        Ok(())
24958    }
24959
24960    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
24961        // ColumnPosition is an enum
24962        match e {
24963            ColumnPosition::First => {
24964                self.write_keyword("FIRST");
24965            }
24966            ColumnPosition::After(ident) => {
24967                self.write_keyword("AFTER");
24968                self.write_space();
24969                self.generate_identifier(ident)?;
24970            }
24971        }
24972        Ok(())
24973    }
24974
24975    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
24976        // column(prefix)
24977        self.generate_expression(&e.this)?;
24978        self.write("(");
24979        self.generate_expression(&e.expression)?;
24980        self.write(")");
24981        Ok(())
24982    }
24983
24984    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
24985        // If unpack is true, this came from * COLUMNS(pattern)
24986        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
24987        if let Some(ref unpack) = e.unpack {
24988            if let Expression::Boolean(b) = unpack.as_ref() {
24989                if b.value {
24990                    self.write("*");
24991                }
24992            }
24993        }
24994        self.write_keyword("COLUMNS");
24995        self.write("(");
24996        self.generate_expression(&e.this)?;
24997        self.write(")");
24998        Ok(())
24999    }
25000
25001    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
25002        // Combined aggregate: FUNC(args) combined
25003        self.generate_expression(&e.this)?;
25004        self.write("(");
25005        for (i, expr) in e.expressions.iter().enumerate() {
25006            if i > 0 {
25007                self.write(", ");
25008            }
25009            self.generate_expression(expr)?;
25010        }
25011        self.write(")");
25012        Ok(())
25013    }
25014
25015    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
25016        // Combined parameterized aggregate: FUNC(params)(expressions)
25017        self.generate_expression(&e.this)?;
25018        self.write("(");
25019        for (i, param) in e.params.iter().enumerate() {
25020            if i > 0 {
25021                self.write(", ");
25022            }
25023            self.generate_expression(param)?;
25024        }
25025        self.write(")(");
25026        for (i, expr) in e.expressions.iter().enumerate() {
25027            if i > 0 {
25028                self.write(", ");
25029            }
25030            self.generate_expression(expr)?;
25031        }
25032        self.write(")");
25033        Ok(())
25034    }
25035
25036    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
25037        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
25038        self.write_keyword("COMMIT");
25039
25040        // TSQL always uses COMMIT TRANSACTION
25041        if e.this.is_none()
25042            && matches!(
25043                self.config.dialect,
25044                Some(DialectType::TSQL) | Some(DialectType::Fabric)
25045            )
25046        {
25047            self.write_space();
25048            self.write_keyword("TRANSACTION");
25049        }
25050
25051        // Check if this has TRANSACTION keyword or transaction name
25052        if let Some(this) = &e.this {
25053            // Check if it's just the "TRANSACTION" marker or an actual transaction name
25054            let is_transaction_marker = matches!(
25055                this.as_ref(),
25056                Expression::Identifier(id) if id.name == "TRANSACTION"
25057            );
25058
25059            self.write_space();
25060            self.write_keyword("TRANSACTION");
25061
25062            // If it's a real transaction name, output it
25063            if !is_transaction_marker {
25064                self.write_space();
25065                self.generate_expression(this)?;
25066            }
25067        }
25068
25069        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
25070        if let Some(durability) = &e.durability {
25071            self.write_space();
25072            self.write_keyword("WITH");
25073            self.write(" (");
25074            self.write_keyword("DELAYED_DURABILITY");
25075            self.write(" = ");
25076            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
25077                self.write_keyword("ON");
25078            } else {
25079                self.write_keyword("OFF");
25080            }
25081            self.write(")");
25082        }
25083
25084        // Output AND [NO] CHAIN
25085        if let Some(chain) = &e.chain {
25086            self.write_space();
25087            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
25088                self.write_keyword("AND NO CHAIN");
25089            } else {
25090                self.write_keyword("AND CHAIN");
25091            }
25092        }
25093        Ok(())
25094    }
25095
25096    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
25097        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
25098        self.write("[");
25099        self.generate_expression(&e.this)?;
25100        self.write_space();
25101        self.write_keyword("FOR");
25102        self.write_space();
25103        self.generate_expression(&e.expression)?;
25104        // Handle optional position variable (for enumerate-like syntax)
25105        if let Some(pos) = &e.position {
25106            self.write(", ");
25107            self.generate_expression(pos)?;
25108        }
25109        if let Some(iterator) = &e.iterator {
25110            self.write_space();
25111            self.write_keyword("IN");
25112            self.write_space();
25113            self.generate_expression(iterator)?;
25114        }
25115        if let Some(condition) = &e.condition {
25116            self.write_space();
25117            self.write_keyword("IF");
25118            self.write_space();
25119            self.generate_expression(condition)?;
25120        }
25121        self.write("]");
25122        Ok(())
25123    }
25124
25125    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
25126        // COMPRESS(this[, method])
25127        self.write_keyword("COMPRESS");
25128        self.write("(");
25129        self.generate_expression(&e.this)?;
25130        if let Some(method) = &e.method {
25131            self.write(", '");
25132            self.write(method);
25133            self.write("'");
25134        }
25135        self.write(")");
25136        Ok(())
25137    }
25138
25139    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
25140        // Python: return f"COMPRESS {this}"
25141        self.write_keyword("COMPRESS");
25142        if let Some(this) = &e.this {
25143            self.write_space();
25144            self.generate_expression(this)?;
25145        }
25146        Ok(())
25147    }
25148
25149    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
25150        // Python: return f"AS {this}{persisted}"
25151        self.write_keyword("AS");
25152        self.write_space();
25153        self.generate_expression(&e.this)?;
25154        if e.not_null.is_some() {
25155            self.write_space();
25156            self.write_keyword("PERSISTED NOT NULL");
25157        } else if e.persisted.is_some() {
25158            self.write_space();
25159            self.write_keyword("PERSISTED");
25160        }
25161        Ok(())
25162    }
25163
25164    /// Generate a ComputedColumn constraint inline within a column definition.
25165    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
25166    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
25167    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
25168        let computed_expr = if matches!(
25169            self.config.dialect,
25170            Some(DialectType::TSQL) | Some(DialectType::Fabric)
25171        ) {
25172            match &*cc.expression {
25173                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
25174                {
25175                    let wrapped = Expression::Cast(Box::new(Cast {
25176                        this: y.this.clone(),
25177                        to: DataType::Date,
25178                        trailing_comments: Vec::new(),
25179                        double_colon_syntax: false,
25180                        format: None,
25181                        default: None,
25182                    }));
25183                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
25184                }
25185                Expression::Function(f)
25186                    if f.name.eq_ignore_ascii_case("YEAR")
25187                        && f.args.len() == 1
25188                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
25189                {
25190                    let wrapped = Expression::Cast(Box::new(Cast {
25191                        this: f.args[0].clone(),
25192                        to: DataType::Date,
25193                        trailing_comments: Vec::new(),
25194                        double_colon_syntax: false,
25195                        format: None,
25196                        default: None,
25197                    }));
25198                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
25199                }
25200                _ => *cc.expression.clone(),
25201            }
25202        } else {
25203            *cc.expression.clone()
25204        };
25205
25206        match cc.persistence_kind.as_deref() {
25207            Some("STORED") | Some("VIRTUAL") => {
25208                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
25209                self.write_keyword("GENERATED ALWAYS AS");
25210                self.write(" (");
25211                self.generate_expression(&computed_expr)?;
25212                self.write(")");
25213                self.write_space();
25214                if cc.persisted {
25215                    self.write_keyword("STORED");
25216                } else {
25217                    self.write_keyword("VIRTUAL");
25218                }
25219            }
25220            Some("PERSISTED") => {
25221                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
25222                self.write_keyword("AS");
25223                self.write(" (");
25224                self.generate_expression(&computed_expr)?;
25225                self.write(")");
25226                self.write_space();
25227                self.write_keyword("PERSISTED");
25228                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
25229                if let Some(ref dt) = cc.data_type {
25230                    self.write_space();
25231                    self.generate_data_type(dt)?;
25232                }
25233                if cc.not_null {
25234                    self.write_space();
25235                    self.write_keyword("NOT NULL");
25236                }
25237            }
25238            _ => {
25239                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
25240                // TSQL computed column without PERSISTED: AS (expr)
25241                if matches!(
25242                    self.config.dialect,
25243                    Some(DialectType::Spark)
25244                        | Some(DialectType::Databricks)
25245                        | Some(DialectType::Hive)
25246                ) {
25247                    self.write_keyword("GENERATED ALWAYS AS");
25248                    self.write(" (");
25249                    self.generate_expression(&computed_expr)?;
25250                    self.write(")");
25251                } else if matches!(
25252                    self.config.dialect,
25253                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25254                ) {
25255                    self.write_keyword("AS");
25256                    let omit_parens = matches!(computed_expr, Expression::Year(_))
25257                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
25258                    if omit_parens {
25259                        self.write_space();
25260                        self.generate_expression(&computed_expr)?;
25261                    } else {
25262                        self.write(" (");
25263                        self.generate_expression(&computed_expr)?;
25264                        self.write(")");
25265                    }
25266                } else {
25267                    self.write_keyword("AS");
25268                    self.write(" (");
25269                    self.generate_expression(&computed_expr)?;
25270                    self.write(")");
25271                }
25272            }
25273        }
25274        Ok(())
25275    }
25276
25277    /// Generate a GeneratedAsRow constraint inline within a column definition.
25278    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
25279    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
25280        self.write_keyword("GENERATED ALWAYS AS ROW ");
25281        if gar.start {
25282            self.write_keyword("START");
25283        } else {
25284            self.write_keyword("END");
25285        }
25286        if gar.hidden {
25287            self.write_space();
25288            self.write_keyword("HIDDEN");
25289        }
25290        Ok(())
25291    }
25292
25293    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
25294    fn generate_system_versioning_content(
25295        &mut self,
25296        e: &WithSystemVersioningProperty,
25297    ) -> Result<()> {
25298        let mut parts = Vec::new();
25299
25300        if let Some(this) = &e.this {
25301            let mut s = String::from("HISTORY_TABLE=");
25302            let mut gen = Generator::new();
25303            gen.config = self.config.clone();
25304            gen.generate_expression(this)?;
25305            s.push_str(&gen.output);
25306            parts.push(s);
25307        }
25308
25309        if let Some(data_consistency) = &e.data_consistency {
25310            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
25311            let mut gen = Generator::new();
25312            gen.config = self.config.clone();
25313            gen.generate_expression(data_consistency)?;
25314            s.push_str(&gen.output);
25315            parts.push(s);
25316        }
25317
25318        if let Some(retention_period) = &e.retention_period {
25319            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
25320            let mut gen = Generator::new();
25321            gen.config = self.config.clone();
25322            gen.generate_expression(retention_period)?;
25323            s.push_str(&gen.output);
25324            parts.push(s);
25325        }
25326
25327        self.write_keyword("SYSTEM_VERSIONING");
25328        self.write("=");
25329
25330        if !parts.is_empty() {
25331            self.write_keyword("ON");
25332            self.write("(");
25333            self.write(&parts.join(", "));
25334            self.write(")");
25335        } else if e.on.is_some() {
25336            self.write_keyword("ON");
25337        } else {
25338            self.write_keyword("OFF");
25339        }
25340
25341        Ok(())
25342    }
25343
25344    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
25345        // Conditional INSERT for multi-table inserts
25346        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
25347        if e.else_.is_some() {
25348            self.write_keyword("ELSE");
25349            self.write_space();
25350        } else if let Some(expression) = &e.expression {
25351            self.write_keyword("WHEN");
25352            self.write_space();
25353            self.generate_expression(expression)?;
25354            self.write_space();
25355            self.write_keyword("THEN");
25356            self.write_space();
25357        }
25358
25359        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
25360        // without the "INSERT " prefix
25361        if let Expression::Insert(insert) = e.this.as_ref() {
25362            self.write_keyword("INTO");
25363            self.write_space();
25364            self.generate_table(&insert.table)?;
25365
25366            // Optional column list
25367            if !insert.columns.is_empty() {
25368                self.write(" (");
25369                for (i, col) in insert.columns.iter().enumerate() {
25370                    if i > 0 {
25371                        self.write(", ");
25372                    }
25373                    self.generate_identifier(col)?;
25374                }
25375                self.write(")");
25376            }
25377
25378            // Optional VALUES clause
25379            if !insert.values.is_empty() {
25380                self.write_space();
25381                self.write_keyword("VALUES");
25382                for (row_idx, row) in insert.values.iter().enumerate() {
25383                    if row_idx > 0 {
25384                        self.write(", ");
25385                    }
25386                    self.write(" (");
25387                    for (i, val) in row.iter().enumerate() {
25388                        if i > 0 {
25389                            self.write(", ");
25390                        }
25391                        self.generate_expression(val)?;
25392                    }
25393                    self.write(")");
25394                }
25395            }
25396        } else {
25397            // Fallback for non-Insert expressions
25398            self.generate_expression(&e.this)?;
25399        }
25400        Ok(())
25401    }
25402
25403    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
25404        // Python: return f"CONSTRAINT {this} {expressions}"
25405        self.write_keyword("CONSTRAINT");
25406        self.write_space();
25407        self.generate_expression(&e.this)?;
25408        if !e.expressions.is_empty() {
25409            self.write_space();
25410            for (i, expr) in e.expressions.iter().enumerate() {
25411                if i > 0 {
25412                    self.write_space();
25413                }
25414                self.generate_expression(expr)?;
25415            }
25416        }
25417        Ok(())
25418    }
25419
25420    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
25421        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
25422        self.write_keyword("CONVERT_TIMEZONE");
25423        self.write("(");
25424        let mut first = true;
25425        if let Some(source_tz) = &e.source_tz {
25426            self.generate_expression(source_tz)?;
25427            first = false;
25428        }
25429        if let Some(target_tz) = &e.target_tz {
25430            if !first {
25431                self.write(", ");
25432            }
25433            self.generate_expression(target_tz)?;
25434            first = false;
25435        }
25436        if let Some(timestamp) = &e.timestamp {
25437            if !first {
25438                self.write(", ");
25439            }
25440            self.generate_expression(timestamp)?;
25441        }
25442        self.write(")");
25443        Ok(())
25444    }
25445
25446    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
25447        // CONVERT(this USING dest)
25448        self.write_keyword("CONVERT");
25449        self.write("(");
25450        self.generate_expression(&e.this)?;
25451        if let Some(dest) = &e.dest {
25452            self.write_space();
25453            self.write_keyword("USING");
25454            self.write_space();
25455            self.generate_expression(dest)?;
25456        }
25457        self.write(")");
25458        Ok(())
25459    }
25460
25461    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
25462        self.write_keyword("COPY");
25463        if e.is_into {
25464            self.write_space();
25465            self.write_keyword("INTO");
25466        }
25467        self.write_space();
25468
25469        // Generate target table or query (or stage for COPY INTO @stage)
25470        if let Expression::Literal(Literal::String(s)) = &e.this {
25471            if s.starts_with('@') {
25472                self.write(s);
25473            } else {
25474                self.generate_expression(&e.this)?;
25475            }
25476        } else {
25477            self.generate_expression(&e.this)?;
25478        }
25479
25480        // FROM or TO based on kind
25481        if e.kind {
25482            // kind=true means FROM (loading into table)
25483            if self.config.pretty {
25484                self.write_newline();
25485            } else {
25486                self.write_space();
25487            }
25488            self.write_keyword("FROM");
25489            self.write_space();
25490        } else if !e.files.is_empty() {
25491            // kind=false means TO (exporting)
25492            if self.config.pretty {
25493                self.write_newline();
25494            } else {
25495                self.write_space();
25496            }
25497            self.write_keyword("TO");
25498            self.write_space();
25499        }
25500
25501        // Generate source/destination files
25502        for (i, file) in e.files.iter().enumerate() {
25503            if i > 0 {
25504                self.write_space();
25505            }
25506            // For stage references (strings starting with @), output without quotes
25507            if let Expression::Literal(Literal::String(s)) = file {
25508                if s.starts_with('@') {
25509                    self.write(s);
25510                } else {
25511                    self.generate_expression(file)?;
25512                }
25513            } else if let Expression::Identifier(id) = file {
25514                // Backtick-quoted file path (Databricks style: `s3://link`)
25515                if id.quoted {
25516                    self.write("`");
25517                    self.write(&id.name);
25518                    self.write("`");
25519                } else {
25520                    self.generate_expression(file)?;
25521                }
25522            } else {
25523                self.generate_expression(file)?;
25524            }
25525        }
25526
25527        // Generate credentials if present (Snowflake style - not wrapped in WITH)
25528        if !e.with_wrapped {
25529            if let Some(ref creds) = e.credentials {
25530                if let Some(ref storage) = creds.storage {
25531                    if self.config.pretty {
25532                        self.write_newline();
25533                    } else {
25534                        self.write_space();
25535                    }
25536                    self.write_keyword("STORAGE_INTEGRATION");
25537                    self.write(" = ");
25538                    self.write(storage);
25539                }
25540                if creds.credentials.is_empty() {
25541                    // Empty credentials: CREDENTIALS = ()
25542                    if self.config.pretty {
25543                        self.write_newline();
25544                    } else {
25545                        self.write_space();
25546                    }
25547                    self.write_keyword("CREDENTIALS");
25548                    self.write(" = ()");
25549                } else {
25550                    if self.config.pretty {
25551                        self.write_newline();
25552                    } else {
25553                        self.write_space();
25554                    }
25555                    self.write_keyword("CREDENTIALS");
25556                    // Check if this is Redshift-style (single value with empty key)
25557                    // vs Snowflake-style (multiple key=value pairs)
25558                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
25559                        // Redshift style: CREDENTIALS 'value'
25560                        self.write(" '");
25561                        self.write(&creds.credentials[0].1);
25562                        self.write("'");
25563                    } else {
25564                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
25565                        self.write(" = (");
25566                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
25567                            if i > 0 {
25568                                self.write_space();
25569                            }
25570                            self.write(k);
25571                            self.write("='");
25572                            self.write(v);
25573                            self.write("'");
25574                        }
25575                        self.write(")");
25576                    }
25577                }
25578                if let Some(ref encryption) = creds.encryption {
25579                    self.write_space();
25580                    self.write_keyword("ENCRYPTION");
25581                    self.write(" = ");
25582                    self.write(encryption);
25583                }
25584            }
25585        }
25586
25587        // Generate parameters
25588        if !e.params.is_empty() {
25589            if e.with_wrapped {
25590                // DuckDB/PostgreSQL/TSQL WITH (...) format
25591                self.write_space();
25592                self.write_keyword("WITH");
25593                self.write(" (");
25594                for (i, param) in e.params.iter().enumerate() {
25595                    if i > 0 {
25596                        self.write(", ");
25597                    }
25598                    self.generate_copy_param_with_format(param)?;
25599                }
25600                self.write(")");
25601            } else {
25602                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
25603                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
25604                // For Snowflake: KEY = VALUE
25605                for param in &e.params {
25606                    if self.config.pretty {
25607                        self.write_newline();
25608                    } else {
25609                        self.write_space();
25610                    }
25611                    // Preserve original case of parameter name (important for Redshift COPY options)
25612                    self.write(&param.name);
25613                    if let Some(ref value) = param.value {
25614                        // Use = only if it was present in the original (param.eq)
25615                        if param.eq {
25616                            self.write(" = ");
25617                        } else {
25618                            self.write(" ");
25619                        }
25620                        if !param.values.is_empty() {
25621                            self.write("(");
25622                            for (i, v) in param.values.iter().enumerate() {
25623                                if i > 0 {
25624                                    self.write_space();
25625                                }
25626                                self.generate_copy_nested_param(v)?;
25627                            }
25628                            self.write(")");
25629                        } else {
25630                            // For COPY parameter values, output identifiers without quoting
25631                            self.generate_copy_param_value(value)?;
25632                        }
25633                    } else if !param.values.is_empty() {
25634                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
25635                        if param.eq {
25636                            self.write(" = (");
25637                        } else {
25638                            self.write(" (");
25639                        }
25640                        // Determine separator for values inside parentheses:
25641                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
25642                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
25643                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
25644                        let is_key_value_pairs = param
25645                            .values
25646                            .first()
25647                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
25648                        let sep = if is_key_value_pairs && param.eq {
25649                            " "
25650                        } else {
25651                            ", "
25652                        };
25653                        for (i, v) in param.values.iter().enumerate() {
25654                            if i > 0 {
25655                                self.write(sep);
25656                            }
25657                            self.generate_copy_nested_param(v)?;
25658                        }
25659                        self.write(")");
25660                    }
25661                }
25662            }
25663        }
25664
25665        Ok(())
25666    }
25667
25668    /// Generate a COPY parameter in WITH (...) format
25669    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
25670    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
25671        self.write_keyword(&param.name);
25672        if !param.values.is_empty() {
25673            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
25674            self.write(" = (");
25675            for (i, v) in param.values.iter().enumerate() {
25676                if i > 0 {
25677                    self.write(", ");
25678                }
25679                self.generate_copy_nested_param(v)?;
25680            }
25681            self.write(")");
25682        } else if let Some(ref value) = param.value {
25683            if param.eq {
25684                self.write(" = ");
25685            } else {
25686                self.write(" ");
25687            }
25688            self.generate_expression(value)?;
25689        }
25690        Ok(())
25691    }
25692
25693    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
25694    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
25695        match expr {
25696            Expression::Eq(eq) => {
25697                // Generate key
25698                match &eq.left {
25699                    Expression::Column(c) => self.write(&c.name.name),
25700                    _ => self.generate_expression(&eq.left)?,
25701                }
25702                self.write("=");
25703                // Generate value
25704                match &eq.right {
25705                    Expression::Literal(Literal::String(s)) => {
25706                        self.write("'");
25707                        self.write(s);
25708                        self.write("'");
25709                    }
25710                    Expression::Tuple(t) => {
25711                        // For lists like NULL_IF=('', 'str1')
25712                        self.write("(");
25713                        if self.config.pretty {
25714                            self.write_newline();
25715                            self.indent_level += 1;
25716                            for (i, item) in t.expressions.iter().enumerate() {
25717                                if i > 0 {
25718                                    self.write(", ");
25719                                }
25720                                self.write_indent();
25721                                self.generate_expression(item)?;
25722                            }
25723                            self.write_newline();
25724                            self.indent_level -= 1;
25725                        } else {
25726                            for (i, item) in t.expressions.iter().enumerate() {
25727                                if i > 0 {
25728                                    self.write(", ");
25729                                }
25730                                self.generate_expression(item)?;
25731                            }
25732                        }
25733                        self.write(")");
25734                    }
25735                    _ => self.generate_expression(&eq.right)?,
25736                }
25737                Ok(())
25738            }
25739            Expression::Column(c) => {
25740                // Standalone keyword like COMPRESSION
25741                self.write(&c.name.name);
25742                Ok(())
25743            }
25744            _ => self.generate_expression(expr),
25745        }
25746    }
25747
25748    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
25749    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
25750    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
25751        match expr {
25752            Expression::Column(c) => {
25753                // Output identifier, preserving quotes if originally quoted
25754                if c.name.quoted {
25755                    self.write("\"");
25756                    self.write(&c.name.name);
25757                    self.write("\"");
25758                } else {
25759                    self.write(&c.name.name);
25760                }
25761                Ok(())
25762            }
25763            Expression::Identifier(id) => {
25764                // Output identifier, preserving quotes if originally quoted
25765                if id.quoted {
25766                    self.write("\"");
25767                    self.write(&id.name);
25768                    self.write("\"");
25769                } else {
25770                    self.write(&id.name);
25771                }
25772                Ok(())
25773            }
25774            Expression::Literal(Literal::String(s)) => {
25775                // Output string with quotes
25776                self.write("'");
25777                self.write(s);
25778                self.write("'");
25779                Ok(())
25780            }
25781            _ => self.generate_expression(expr),
25782        }
25783    }
25784
25785    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
25786        self.write_keyword(&e.name);
25787        if let Some(ref value) = e.value {
25788            if e.eq {
25789                self.write(" = ");
25790            } else {
25791                self.write(" ");
25792            }
25793            self.generate_expression(value)?;
25794        }
25795        if !e.values.is_empty() {
25796            if e.eq {
25797                self.write(" = ");
25798            } else {
25799                self.write(" ");
25800            }
25801            self.write("(");
25802            for (i, v) in e.values.iter().enumerate() {
25803                if i > 0 {
25804                    self.write(", ");
25805                }
25806                self.generate_expression(v)?;
25807            }
25808            self.write(")");
25809        }
25810        Ok(())
25811    }
25812
25813    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
25814        // CORR(this, expression)
25815        self.write_keyword("CORR");
25816        self.write("(");
25817        self.generate_expression(&e.this)?;
25818        self.write(", ");
25819        self.generate_expression(&e.expression)?;
25820        self.write(")");
25821        Ok(())
25822    }
25823
25824    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
25825        // COSINE_DISTANCE(this, expression)
25826        self.write_keyword("COSINE_DISTANCE");
25827        self.write("(");
25828        self.generate_expression(&e.this)?;
25829        self.write(", ");
25830        self.generate_expression(&e.expression)?;
25831        self.write(")");
25832        Ok(())
25833    }
25834
25835    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
25836        // COVAR_POP(this, expression)
25837        self.write_keyword("COVAR_POP");
25838        self.write("(");
25839        self.generate_expression(&e.this)?;
25840        self.write(", ");
25841        self.generate_expression(&e.expression)?;
25842        self.write(")");
25843        Ok(())
25844    }
25845
25846    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
25847        // COVAR_SAMP(this, expression)
25848        self.write_keyword("COVAR_SAMP");
25849        self.write("(");
25850        self.generate_expression(&e.this)?;
25851        self.write(", ");
25852        self.generate_expression(&e.expression)?;
25853        self.write(")");
25854        Ok(())
25855    }
25856
25857    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
25858        // CREDENTIALS (key1='value1', key2='value2')
25859        self.write_keyword("CREDENTIALS");
25860        self.write(" (");
25861        for (i, (key, value)) in e.credentials.iter().enumerate() {
25862            if i > 0 {
25863                self.write(", ");
25864            }
25865            self.write(key);
25866            self.write("='");
25867            self.write(value);
25868            self.write("'");
25869        }
25870        self.write(")");
25871        Ok(())
25872    }
25873
25874    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
25875        // CREDENTIALS=(expressions)
25876        self.write_keyword("CREDENTIALS");
25877        self.write("=(");
25878        for (i, expr) in e.expressions.iter().enumerate() {
25879            if i > 0 {
25880                self.write(", ");
25881            }
25882            self.generate_expression(expr)?;
25883        }
25884        self.write(")");
25885        Ok(())
25886    }
25887
25888    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
25889        use crate::dialects::DialectType;
25890
25891        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
25892        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
25893        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
25894            self.generate_expression(&e.this)?;
25895            self.write_space();
25896            self.write_keyword("AS");
25897            self.write_space();
25898            self.generate_identifier(&e.alias)?;
25899            return Ok(());
25900        }
25901        self.write(&e.alias.name);
25902
25903        // BigQuery doesn't support column aliases in CTE definitions
25904        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
25905
25906        if !e.columns.is_empty() && !skip_cte_columns {
25907            self.write("(");
25908            for (i, col) in e.columns.iter().enumerate() {
25909                if i > 0 {
25910                    self.write(", ");
25911                }
25912                self.write(&col.name);
25913            }
25914            self.write(")");
25915        }
25916        // USING KEY (columns) for DuckDB recursive CTEs
25917        if !e.key_expressions.is_empty() {
25918            self.write_space();
25919            self.write_keyword("USING KEY");
25920            self.write(" (");
25921            for (i, key) in e.key_expressions.iter().enumerate() {
25922                if i > 0 {
25923                    self.write(", ");
25924                }
25925                self.write(&key.name);
25926            }
25927            self.write(")");
25928        }
25929        self.write_space();
25930        self.write_keyword("AS");
25931        self.write_space();
25932        if let Some(materialized) = e.materialized {
25933            if materialized {
25934                self.write_keyword("MATERIALIZED");
25935            } else {
25936                self.write_keyword("NOT MATERIALIZED");
25937            }
25938            self.write_space();
25939        }
25940        self.write("(");
25941        self.generate_expression(&e.this)?;
25942        self.write(")");
25943        Ok(())
25944    }
25945
25946    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
25947        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
25948        if e.expressions.is_empty() {
25949            self.write_keyword("WITH CUBE");
25950        } else {
25951            self.write_keyword("CUBE");
25952            self.write("(");
25953            for (i, expr) in e.expressions.iter().enumerate() {
25954                if i > 0 {
25955                    self.write(", ");
25956                }
25957                self.generate_expression(expr)?;
25958            }
25959            self.write(")");
25960        }
25961        Ok(())
25962    }
25963
25964    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
25965        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
25966        self.write_keyword("CURRENT_DATETIME");
25967        if let Some(this) = &e.this {
25968            self.write("(");
25969            self.generate_expression(this)?;
25970            self.write(")");
25971        }
25972        Ok(())
25973    }
25974
25975    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
25976        // CURRENT_SCHEMA - no arguments
25977        self.write_keyword("CURRENT_SCHEMA");
25978        Ok(())
25979    }
25980
25981    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
25982        // CURRENT_SCHEMAS(include_implicit)
25983        self.write_keyword("CURRENT_SCHEMAS");
25984        self.write("(");
25985        if let Some(this) = &e.this {
25986            self.generate_expression(this)?;
25987        }
25988        self.write(")");
25989        Ok(())
25990    }
25991
25992    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
25993        // CURRENT_USER or CURRENT_USER()
25994        self.write_keyword("CURRENT_USER");
25995        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
25996        let needs_parens = e.this.is_some()
25997            || matches!(
25998                self.config.dialect,
25999                Some(DialectType::Snowflake)
26000                    | Some(DialectType::Spark)
26001                    | Some(DialectType::Hive)
26002                    | Some(DialectType::DuckDB)
26003                    | Some(DialectType::BigQuery)
26004                    | Some(DialectType::MySQL)
26005                    | Some(DialectType::Databricks)
26006            );
26007        if needs_parens {
26008            self.write("()");
26009        }
26010        Ok(())
26011    }
26012
26013    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
26014        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
26015        if self.config.dialect == Some(DialectType::Solr) {
26016            self.generate_expression(&e.this)?;
26017            self.write(" ");
26018            self.write_keyword("OR");
26019            self.write(" ");
26020            self.generate_expression(&e.expression)?;
26021        } else {
26022            // String concatenation: this || expression
26023            self.generate_expression(&e.this)?;
26024            self.write(" || ");
26025            self.generate_expression(&e.expression)?;
26026        }
26027        Ok(())
26028    }
26029
26030    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
26031        // DATABLOCKSIZE=... (Teradata)
26032        self.write_keyword("DATABLOCKSIZE");
26033        self.write("=");
26034        if let Some(size) = e.size {
26035            self.write(&size.to_string());
26036            if let Some(units) = &e.units {
26037                self.write_space();
26038                self.generate_expression(units)?;
26039            }
26040        } else if e.minimum.is_some() {
26041            self.write_keyword("MINIMUM");
26042        } else if e.maximum.is_some() {
26043            self.write_keyword("MAXIMUM");
26044        } else if e.default.is_some() {
26045            self.write_keyword("DEFAULT");
26046        }
26047        Ok(())
26048    }
26049
26050    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
26051        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
26052        self.write_keyword("DATA_DELETION");
26053        self.write("=");
26054
26055        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
26056        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
26057
26058        if is_on {
26059            self.write_keyword("ON");
26060            if has_options {
26061                self.write("(");
26062                let mut first = true;
26063                if let Some(filter_column) = &e.filter_column {
26064                    self.write_keyword("FILTER_COLUMN");
26065                    self.write("=");
26066                    self.generate_expression(filter_column)?;
26067                    first = false;
26068                }
26069                if let Some(retention_period) = &e.retention_period {
26070                    if !first {
26071                        self.write(", ");
26072                    }
26073                    self.write_keyword("RETENTION_PERIOD");
26074                    self.write("=");
26075                    self.generate_expression(retention_period)?;
26076                }
26077                self.write(")");
26078            }
26079        } else {
26080            self.write_keyword("OFF");
26081        }
26082        Ok(())
26083    }
26084
26085    /// Generate a Date function expression
26086    /// For Exasol: {d'value'} -> TO_DATE('value')
26087    /// For other dialects: DATE('value')
26088    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
26089        use crate::dialects::DialectType;
26090        use crate::expressions::Literal;
26091
26092        match self.config.dialect {
26093            // Exasol uses TO_DATE for Date expressions
26094            Some(DialectType::Exasol) => {
26095                self.write_keyword("TO_DATE");
26096                self.write("(");
26097                // Extract the string value from the expression if it's a string literal
26098                match &e.this {
26099                    Expression::Literal(Literal::String(s)) => {
26100                        self.write("'");
26101                        self.write(s);
26102                        self.write("'");
26103                    }
26104                    _ => {
26105                        self.generate_expression(&e.this)?;
26106                    }
26107                }
26108                self.write(")");
26109            }
26110            // Standard: DATE(value)
26111            _ => {
26112                self.write_keyword("DATE");
26113                self.write("(");
26114                self.generate_expression(&e.this)?;
26115                self.write(")");
26116            }
26117        }
26118        Ok(())
26119    }
26120
26121    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
26122        // DATE_BIN(interval, timestamp[, origin])
26123        self.write_keyword("DATE_BIN");
26124        self.write("(");
26125        self.generate_expression(&e.this)?;
26126        self.write(", ");
26127        self.generate_expression(&e.expression)?;
26128        if let Some(origin) = &e.origin {
26129            self.write(", ");
26130            self.generate_expression(origin)?;
26131        }
26132        self.write(")");
26133        Ok(())
26134    }
26135
26136    fn generate_date_format_column_constraint(
26137        &mut self,
26138        e: &DateFormatColumnConstraint,
26139    ) -> Result<()> {
26140        // FORMAT 'format_string' (Teradata)
26141        self.write_keyword("FORMAT");
26142        self.write_space();
26143        self.generate_expression(&e.this)?;
26144        Ok(())
26145    }
26146
26147    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
26148        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
26149        self.write_keyword("DATE_FROM_PARTS");
26150        self.write("(");
26151        let mut first = true;
26152        if let Some(year) = &e.year {
26153            self.generate_expression(year)?;
26154            first = false;
26155        }
26156        if let Some(month) = &e.month {
26157            if !first {
26158                self.write(", ");
26159            }
26160            self.generate_expression(month)?;
26161            first = false;
26162        }
26163        if let Some(day) = &e.day {
26164            if !first {
26165                self.write(", ");
26166            }
26167            self.generate_expression(day)?;
26168        }
26169        self.write(")");
26170        Ok(())
26171    }
26172
26173    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
26174        // DATETIME(this) or DATETIME(this, expression)
26175        self.write_keyword("DATETIME");
26176        self.write("(");
26177        self.generate_expression(&e.this)?;
26178        if let Some(expr) = &e.expression {
26179            self.write(", ");
26180            self.generate_expression(expr)?;
26181        }
26182        self.write(")");
26183        Ok(())
26184    }
26185
26186    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
26187        // DATETIME_ADD(this, expression, unit)
26188        self.write_keyword("DATETIME_ADD");
26189        self.write("(");
26190        self.generate_expression(&e.this)?;
26191        self.write(", ");
26192        self.generate_expression(&e.expression)?;
26193        if let Some(unit) = &e.unit {
26194            self.write(", ");
26195            self.write_keyword(unit);
26196        }
26197        self.write(")");
26198        Ok(())
26199    }
26200
26201    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
26202        // DATETIME_DIFF(this, expression, unit)
26203        self.write_keyword("DATETIME_DIFF");
26204        self.write("(");
26205        self.generate_expression(&e.this)?;
26206        self.write(", ");
26207        self.generate_expression(&e.expression)?;
26208        if let Some(unit) = &e.unit {
26209            self.write(", ");
26210            self.write_keyword(unit);
26211        }
26212        self.write(")");
26213        Ok(())
26214    }
26215
26216    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
26217        // DATETIME_SUB(this, expression, unit)
26218        self.write_keyword("DATETIME_SUB");
26219        self.write("(");
26220        self.generate_expression(&e.this)?;
26221        self.write(", ");
26222        self.generate_expression(&e.expression)?;
26223        if let Some(unit) = &e.unit {
26224            self.write(", ");
26225            self.write_keyword(unit);
26226        }
26227        self.write(")");
26228        Ok(())
26229    }
26230
26231    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
26232        // DATETIME_TRUNC(this, unit, zone)
26233        self.write_keyword("DATETIME_TRUNC");
26234        self.write("(");
26235        self.generate_expression(&e.this)?;
26236        self.write(", ");
26237        self.write_keyword(&e.unit);
26238        if let Some(zone) = &e.zone {
26239            self.write(", ");
26240            self.generate_expression(zone)?;
26241        }
26242        self.write(")");
26243        Ok(())
26244    }
26245
26246    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
26247        // DAYNAME(this)
26248        self.write_keyword("DAYNAME");
26249        self.write("(");
26250        self.generate_expression(&e.this)?;
26251        self.write(")");
26252        Ok(())
26253    }
26254
26255    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
26256        // DECLARE var1 AS type1, var2 AS type2, ...
26257        self.write_keyword("DECLARE");
26258        self.write_space();
26259        for (i, expr) in e.expressions.iter().enumerate() {
26260            if i > 0 {
26261                self.write(", ");
26262            }
26263            self.generate_expression(expr)?;
26264        }
26265        Ok(())
26266    }
26267
26268    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
26269        use crate::dialects::DialectType;
26270
26271        // variable TYPE [DEFAULT default]
26272        self.generate_expression(&e.this)?;
26273        // BigQuery multi-variable: DECLARE X, Y, Z INT64
26274        for name in &e.additional_names {
26275            self.write(", ");
26276            self.generate_expression(name)?;
26277        }
26278        if let Some(kind) = &e.kind {
26279            self.write_space();
26280            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
26281            // TSQL: Always includes AS (normalization)
26282            // Others: Include AS if present in original
26283            match self.config.dialect {
26284                Some(DialectType::BigQuery) => {
26285                    self.write(kind);
26286                }
26287                Some(DialectType::TSQL) => {
26288                    // TSQL: Check for complex TABLE constraints that should be passed through unchanged
26289                    // Python sqlglot falls back to Command for TABLE declarations with CLUSTERED,
26290                    // NONCLUSTERED, or INDEX constraints
26291                    let is_complex_table = kind.starts_with("TABLE")
26292                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
26293
26294                    if is_complex_table {
26295                        // Complex TABLE declarations: preserve as-is (no AS, no INT normalization)
26296                        self.write(kind);
26297                    } else {
26298                        // Simple declarations: add AS (except for CURSOR) and normalize INT
26299                        if !kind.starts_with("CURSOR") {
26300                            self.write_keyword("AS");
26301                            self.write_space();
26302                        }
26303                        // Normalize INT to INTEGER for TSQL DECLARE statements
26304                        if kind == "INT" {
26305                            self.write("INTEGER");
26306                        } else if kind.starts_with("TABLE") {
26307                            // Normalize INT to INTEGER inside TABLE column definitions
26308                            let normalized = kind
26309                                .replace(" INT ", " INTEGER ")
26310                                .replace(" INT,", " INTEGER,")
26311                                .replace(" INT)", " INTEGER)")
26312                                .replace("(INT ", "(INTEGER ");
26313                            self.write(&normalized);
26314                        } else {
26315                            self.write(kind);
26316                        }
26317                    }
26318                }
26319                _ => {
26320                    if e.has_as {
26321                        self.write_keyword("AS");
26322                        self.write_space();
26323                    }
26324                    self.write(kind);
26325                }
26326            }
26327        }
26328        if let Some(default) = &e.default {
26329            // BigQuery uses DEFAULT, others use =
26330            match self.config.dialect {
26331                Some(DialectType::BigQuery) => {
26332                    self.write_space();
26333                    self.write_keyword("DEFAULT");
26334                    self.write_space();
26335                }
26336                _ => {
26337                    self.write(" = ");
26338                }
26339            }
26340            self.generate_expression(default)?;
26341        }
26342        Ok(())
26343    }
26344
26345    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
26346        // DECODE(expr, search1, result1, search2, result2, ..., default)
26347        self.write_keyword("DECODE");
26348        self.write("(");
26349        for (i, expr) in e.expressions.iter().enumerate() {
26350            if i > 0 {
26351                self.write(", ");
26352            }
26353            self.generate_expression(expr)?;
26354        }
26355        self.write(")");
26356        Ok(())
26357    }
26358
26359    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
26360        // DECOMPRESS(expr, 'method')
26361        self.write_keyword("DECOMPRESS");
26362        self.write("(");
26363        self.generate_expression(&e.this)?;
26364        self.write(", '");
26365        self.write(&e.method);
26366        self.write("')");
26367        Ok(())
26368    }
26369
26370    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
26371        // DECOMPRESS(expr, 'method')
26372        self.write_keyword("DECOMPRESS");
26373        self.write("(");
26374        self.generate_expression(&e.this)?;
26375        self.write(", '");
26376        self.write(&e.method);
26377        self.write("')");
26378        Ok(())
26379    }
26380
26381    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
26382        // DECRYPT(value, passphrase [, aad [, algorithm]])
26383        self.write_keyword("DECRYPT");
26384        self.write("(");
26385        self.generate_expression(&e.this)?;
26386        if let Some(passphrase) = &e.passphrase {
26387            self.write(", ");
26388            self.generate_expression(passphrase)?;
26389        }
26390        if let Some(aad) = &e.aad {
26391            self.write(", ");
26392            self.generate_expression(aad)?;
26393        }
26394        if let Some(method) = &e.encryption_method {
26395            self.write(", ");
26396            self.generate_expression(method)?;
26397        }
26398        self.write(")");
26399        Ok(())
26400    }
26401
26402    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
26403        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
26404        self.write_keyword("DECRYPT_RAW");
26405        self.write("(");
26406        self.generate_expression(&e.this)?;
26407        if let Some(key) = &e.key {
26408            self.write(", ");
26409            self.generate_expression(key)?;
26410        }
26411        if let Some(iv) = &e.iv {
26412            self.write(", ");
26413            self.generate_expression(iv)?;
26414        }
26415        if let Some(aad) = &e.aad {
26416            self.write(", ");
26417            self.generate_expression(aad)?;
26418        }
26419        if let Some(method) = &e.encryption_method {
26420            self.write(", ");
26421            self.generate_expression(method)?;
26422        }
26423        self.write(")");
26424        Ok(())
26425    }
26426
26427    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
26428        // DEFINER = user
26429        self.write_keyword("DEFINER");
26430        self.write(" = ");
26431        self.generate_expression(&e.this)?;
26432        Ok(())
26433    }
26434
26435    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
26436        // Python: DETACH[DATABASE IF EXISTS] this
26437        self.write_keyword("DETACH");
26438        if e.exists {
26439            self.write_keyword(" DATABASE IF EXISTS");
26440        }
26441        self.write_space();
26442        self.generate_expression(&e.this)?;
26443        Ok(())
26444    }
26445
26446    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
26447        let property_name = match e.this.as_ref() {
26448            Expression::Identifier(id) => id.name.as_str(),
26449            Expression::Var(v) => v.this.as_str(),
26450            _ => "DICTIONARY",
26451        };
26452        self.write_keyword(property_name);
26453        self.write("(");
26454        self.write(&e.kind);
26455        if let Some(settings) = &e.settings {
26456            self.write("(");
26457            if let Expression::Tuple(t) = settings.as_ref() {
26458                if self.config.pretty && !t.expressions.is_empty() {
26459                    self.write_newline();
26460                    self.indent_level += 1;
26461                    for (i, pair) in t.expressions.iter().enumerate() {
26462                        if i > 0 {
26463                            self.write(",");
26464                            self.write_newline();
26465                        }
26466                        self.write_indent();
26467                        if let Expression::Tuple(pair_tuple) = pair {
26468                            if let Some(k) = pair_tuple.expressions.first() {
26469                                self.generate_expression(k)?;
26470                            }
26471                            if let Some(v) = pair_tuple.expressions.get(1) {
26472                                self.write(" ");
26473                                self.generate_expression(v)?;
26474                            }
26475                        } else {
26476                            self.generate_expression(pair)?;
26477                        }
26478                    }
26479                    self.indent_level -= 1;
26480                    self.write_newline();
26481                    self.write_indent();
26482                } else {
26483                    for (i, pair) in t.expressions.iter().enumerate() {
26484                        if i > 0 {
26485                            self.write(", ");
26486                        }
26487                        if let Expression::Tuple(pair_tuple) = pair {
26488                            if let Some(k) = pair_tuple.expressions.first() {
26489                                self.generate_expression(k)?;
26490                            }
26491                            if let Some(v) = pair_tuple.expressions.get(1) {
26492                                self.write(" ");
26493                                self.generate_expression(v)?;
26494                            }
26495                        } else {
26496                            self.generate_expression(pair)?;
26497                        }
26498                    }
26499                }
26500            } else {
26501                self.generate_expression(settings)?;
26502            }
26503            self.write(")");
26504        } else if property_name.eq_ignore_ascii_case("LAYOUT") {
26505            self.write("()");
26506        }
26507        self.write(")");
26508        Ok(())
26509    }
26510
26511    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
26512        let property_name = match e.this.as_ref() {
26513            Expression::Identifier(id) => id.name.as_str(),
26514            Expression::Var(v) => v.this.as_str(),
26515            _ => "RANGE",
26516        };
26517        self.write_keyword(property_name);
26518        self.write("(");
26519        if let Some(min) = &e.min {
26520            self.write_keyword("MIN");
26521            self.write_space();
26522            self.generate_expression(min)?;
26523        }
26524        if let Some(max) = &e.max {
26525            self.write_space();
26526            self.write_keyword("MAX");
26527            self.write_space();
26528            self.generate_expression(max)?;
26529        }
26530        self.write(")");
26531        Ok(())
26532    }
26533
26534    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
26535        // Python: {local}DIRECTORY {this}{row_format}
26536        if e.local.is_some() {
26537            self.write_keyword("LOCAL ");
26538        }
26539        self.write_keyword("DIRECTORY");
26540        self.write_space();
26541        self.generate_expression(&e.this)?;
26542        if let Some(row_format) = &e.row_format {
26543            self.write_space();
26544            self.generate_expression(row_format)?;
26545        }
26546        Ok(())
26547    }
26548
26549    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
26550        // Redshift: DISTKEY(column)
26551        self.write_keyword("DISTKEY");
26552        self.write("(");
26553        self.generate_expression(&e.this)?;
26554        self.write(")");
26555        Ok(())
26556    }
26557
26558    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
26559        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
26560        self.write_keyword("DISTSTYLE");
26561        self.write_space();
26562        self.generate_expression(&e.this)?;
26563        Ok(())
26564    }
26565
26566    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
26567        // Python: "DISTRIBUTE BY" expressions
26568        self.write_keyword("DISTRIBUTE BY");
26569        self.write_space();
26570        for (i, expr) in e.expressions.iter().enumerate() {
26571            if i > 0 {
26572                self.write(", ");
26573            }
26574            self.generate_expression(expr)?;
26575        }
26576        Ok(())
26577    }
26578
26579    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
26580        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
26581        self.write_keyword("DISTRIBUTED BY");
26582        self.write_space();
26583        self.write(&e.kind);
26584        if !e.expressions.is_empty() {
26585            self.write(" (");
26586            for (i, expr) in e.expressions.iter().enumerate() {
26587                if i > 0 {
26588                    self.write(", ");
26589                }
26590                self.generate_expression(expr)?;
26591            }
26592            self.write(")");
26593        }
26594        if let Some(buckets) = &e.buckets {
26595            self.write_space();
26596            self.write_keyword("BUCKETS");
26597            self.write_space();
26598            self.generate_expression(buckets)?;
26599        }
26600        if let Some(order) = &e.order {
26601            self.write_space();
26602            self.generate_expression(order)?;
26603        }
26604        Ok(())
26605    }
26606
26607    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
26608        // DOT_PRODUCT(vector1, vector2)
26609        self.write_keyword("DOT_PRODUCT");
26610        self.write("(");
26611        self.generate_expression(&e.this)?;
26612        self.write(", ");
26613        self.generate_expression(&e.expression)?;
26614        self.write(")");
26615        Ok(())
26616    }
26617
26618    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
26619        // Python: DROP{IF EXISTS }expressions
26620        self.write_keyword("DROP");
26621        if e.exists {
26622            self.write_keyword(" IF EXISTS ");
26623        } else {
26624            self.write_space();
26625        }
26626        for (i, expr) in e.expressions.iter().enumerate() {
26627            if i > 0 {
26628                self.write(", ");
26629            }
26630            self.generate_expression(expr)?;
26631        }
26632        Ok(())
26633    }
26634
26635    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
26636        // Python: DUPLICATE KEY (expressions)
26637        self.write_keyword("DUPLICATE KEY");
26638        self.write(" (");
26639        for (i, expr) in e.expressions.iter().enumerate() {
26640            if i > 0 {
26641                self.write(", ");
26642            }
26643            self.generate_expression(expr)?;
26644        }
26645        self.write(")");
26646        Ok(())
26647    }
26648
26649    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
26650        // ELT(index, str1, str2, ...)
26651        self.write_keyword("ELT");
26652        self.write("(");
26653        self.generate_expression(&e.this)?;
26654        for expr in &e.expressions {
26655            self.write(", ");
26656            self.generate_expression(expr)?;
26657        }
26658        self.write(")");
26659        Ok(())
26660    }
26661
26662    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
26663        // ENCODE(string, charset)
26664        self.write_keyword("ENCODE");
26665        self.write("(");
26666        self.generate_expression(&e.this)?;
26667        if let Some(charset) = &e.charset {
26668            self.write(", ");
26669            self.generate_expression(charset)?;
26670        }
26671        self.write(")");
26672        Ok(())
26673    }
26674
26675    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
26676        // Python: [KEY ]ENCODE this [properties]
26677        if e.key.is_some() {
26678            self.write_keyword("KEY ");
26679        }
26680        self.write_keyword("ENCODE");
26681        self.write_space();
26682        self.generate_expression(&e.this)?;
26683        if !e.properties.is_empty() {
26684            self.write(" (");
26685            for (i, prop) in e.properties.iter().enumerate() {
26686                if i > 0 {
26687                    self.write(", ");
26688                }
26689                self.generate_expression(prop)?;
26690            }
26691            self.write(")");
26692        }
26693        Ok(())
26694    }
26695
26696    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
26697        // ENCRYPT(value, passphrase [, aad [, algorithm]])
26698        self.write_keyword("ENCRYPT");
26699        self.write("(");
26700        self.generate_expression(&e.this)?;
26701        if let Some(passphrase) = &e.passphrase {
26702            self.write(", ");
26703            self.generate_expression(passphrase)?;
26704        }
26705        if let Some(aad) = &e.aad {
26706            self.write(", ");
26707            self.generate_expression(aad)?;
26708        }
26709        if let Some(method) = &e.encryption_method {
26710            self.write(", ");
26711            self.generate_expression(method)?;
26712        }
26713        self.write(")");
26714        Ok(())
26715    }
26716
26717    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
26718        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
26719        self.write_keyword("ENCRYPT_RAW");
26720        self.write("(");
26721        self.generate_expression(&e.this)?;
26722        if let Some(key) = &e.key {
26723            self.write(", ");
26724            self.generate_expression(key)?;
26725        }
26726        if let Some(iv) = &e.iv {
26727            self.write(", ");
26728            self.generate_expression(iv)?;
26729        }
26730        if let Some(aad) = &e.aad {
26731            self.write(", ");
26732            self.generate_expression(aad)?;
26733        }
26734        if let Some(method) = &e.encryption_method {
26735            self.write(", ");
26736            self.generate_expression(method)?;
26737        }
26738        self.write(")");
26739        Ok(())
26740    }
26741
26742    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
26743        // MySQL: ENGINE = InnoDB
26744        self.write_keyword("ENGINE");
26745        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
26746            self.write("=");
26747        } else {
26748            self.write(" = ");
26749        }
26750        self.generate_expression(&e.this)?;
26751        Ok(())
26752    }
26753
26754    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
26755        // ENVIRONMENT (expressions)
26756        self.write_keyword("ENVIRONMENT");
26757        self.write(" (");
26758        for (i, expr) in e.expressions.iter().enumerate() {
26759            if i > 0 {
26760                self.write(", ");
26761            }
26762            self.generate_expression(expr)?;
26763        }
26764        self.write(")");
26765        Ok(())
26766    }
26767
26768    fn generate_ephemeral_column_constraint(
26769        &mut self,
26770        e: &EphemeralColumnConstraint,
26771    ) -> Result<()> {
26772        // MySQL: EPHEMERAL [expr]
26773        self.write_keyword("EPHEMERAL");
26774        if let Some(this) = &e.this {
26775            self.write_space();
26776            self.generate_expression(this)?;
26777        }
26778        Ok(())
26779    }
26780
26781    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
26782        // Snowflake: EQUAL_NULL(a, b)
26783        self.write_keyword("EQUAL_NULL");
26784        self.write("(");
26785        self.generate_expression(&e.this)?;
26786        self.write(", ");
26787        self.generate_expression(&e.expression)?;
26788        self.write(")");
26789        Ok(())
26790    }
26791
26792    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
26793        use crate::dialects::DialectType;
26794
26795        // PostgreSQL uses <-> operator syntax
26796        match self.config.dialect {
26797            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
26798                self.generate_expression(&e.this)?;
26799                self.write(" <-> ");
26800                self.generate_expression(&e.expression)?;
26801            }
26802            _ => {
26803                // Other dialects use EUCLIDEAN_DISTANCE function
26804                self.write_keyword("EUCLIDEAN_DISTANCE");
26805                self.write("(");
26806                self.generate_expression(&e.this)?;
26807                self.write(", ");
26808                self.generate_expression(&e.expression)?;
26809                self.write(")");
26810            }
26811        }
26812        Ok(())
26813    }
26814
26815    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
26816        // EXECUTE AS CALLER|OWNER|user
26817        self.write_keyword("EXECUTE AS");
26818        self.write_space();
26819        self.generate_expression(&e.this)?;
26820        Ok(())
26821    }
26822
26823    fn generate_export(&mut self, e: &Export) -> Result<()> {
26824        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
26825        self.write_keyword("EXPORT DATA");
26826        if let Some(connection) = &e.connection {
26827            self.write_space();
26828            self.write_keyword("WITH CONNECTION");
26829            self.write_space();
26830            self.generate_expression(connection)?;
26831        }
26832        if !e.options.is_empty() {
26833            self.write_space();
26834            self.generate_options_clause(&e.options)?;
26835        }
26836        self.write_space();
26837        self.write_keyword("AS");
26838        self.write_space();
26839        self.generate_expression(&e.this)?;
26840        Ok(())
26841    }
26842
26843    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
26844        // EXTERNAL [this]
26845        self.write_keyword("EXTERNAL");
26846        if let Some(this) = &e.this {
26847            self.write_space();
26848            self.generate_expression(this)?;
26849        }
26850        Ok(())
26851    }
26852
26853    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
26854        // Python: {no}FALLBACK{protection}
26855        if e.no.is_some() {
26856            self.write_keyword("NO ");
26857        }
26858        self.write_keyword("FALLBACK");
26859        if e.protection.is_some() {
26860            self.write_keyword(" PROTECTION");
26861        }
26862        Ok(())
26863    }
26864
26865    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
26866        // BigQuery: FARM_FINGERPRINT(value)
26867        self.write_keyword("FARM_FINGERPRINT");
26868        self.write("(");
26869        for (i, expr) in e.expressions.iter().enumerate() {
26870            if i > 0 {
26871                self.write(", ");
26872            }
26873            self.generate_expression(expr)?;
26874        }
26875        self.write(")");
26876        Ok(())
26877    }
26878
26879    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
26880        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
26881        self.write_keyword("FEATURES_AT_TIME");
26882        self.write("(");
26883        self.generate_expression(&e.this)?;
26884        if let Some(time) = &e.time {
26885            self.write(", ");
26886            self.generate_expression(time)?;
26887        }
26888        if let Some(num_rows) = &e.num_rows {
26889            self.write(", ");
26890            self.generate_expression(num_rows)?;
26891        }
26892        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
26893            self.write(", ");
26894            self.generate_expression(ignore_nulls)?;
26895        }
26896        self.write(")");
26897        Ok(())
26898    }
26899
26900    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
26901        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
26902        let use_limit = !e.percent
26903            && !e.with_ties
26904            && e.count.is_some()
26905            && matches!(
26906                self.config.dialect,
26907                Some(DialectType::Spark)
26908                    | Some(DialectType::Hive)
26909                    | Some(DialectType::DuckDB)
26910                    | Some(DialectType::SQLite)
26911                    | Some(DialectType::MySQL)
26912                    | Some(DialectType::BigQuery)
26913                    | Some(DialectType::Databricks)
26914                    | Some(DialectType::StarRocks)
26915                    | Some(DialectType::Doris)
26916                    | Some(DialectType::Athena)
26917                    | Some(DialectType::ClickHouse)
26918            );
26919
26920        if use_limit {
26921            self.write_keyword("LIMIT");
26922            self.write_space();
26923            self.generate_expression(e.count.as_ref().unwrap())?;
26924            return Ok(());
26925        }
26926
26927        // Python: FETCH direction count limit_options
26928        self.write_keyword("FETCH");
26929        if !e.direction.is_empty() {
26930            self.write_space();
26931            self.write_keyword(&e.direction);
26932        }
26933        if let Some(count) = &e.count {
26934            self.write_space();
26935            self.generate_expression(count)?;
26936        }
26937        // Generate PERCENT, ROWS, WITH TIES/ONLY
26938        if e.percent {
26939            self.write_keyword(" PERCENT");
26940        }
26941        if e.rows {
26942            self.write_keyword(" ROWS");
26943        }
26944        if e.with_ties {
26945            self.write_keyword(" WITH TIES");
26946        } else if e.rows {
26947            self.write_keyword(" ONLY");
26948        } else {
26949            self.write_keyword(" ROWS ONLY");
26950        }
26951        Ok(())
26952    }
26953
26954    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
26955        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
26956        // For Spark/Databricks without hive_format: USING this
26957        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
26958        if e.hive_format.is_some() {
26959            // Hive format: STORED AS ...
26960            self.write_keyword("STORED AS");
26961            self.write_space();
26962            if let Some(this) = &e.this {
26963                // Uppercase the format name (e.g., parquet -> PARQUET)
26964                if let Expression::Identifier(id) = this.as_ref() {
26965                    self.write_keyword(&id.name.to_uppercase());
26966                } else {
26967                    self.generate_expression(this)?;
26968                }
26969            }
26970        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
26971            // Hive: STORED AS format
26972            self.write_keyword("STORED AS");
26973            self.write_space();
26974            if let Some(this) = &e.this {
26975                if let Expression::Identifier(id) = this.as_ref() {
26976                    self.write_keyword(&id.name.to_uppercase());
26977                } else {
26978                    self.generate_expression(this)?;
26979                }
26980            }
26981        } else if matches!(
26982            self.config.dialect,
26983            Some(DialectType::Spark) | Some(DialectType::Databricks)
26984        ) {
26985            // Spark/Databricks: USING format (e.g., USING DELTA)
26986            self.write_keyword("USING");
26987            self.write_space();
26988            if let Some(this) = &e.this {
26989                self.generate_expression(this)?;
26990            }
26991        } else {
26992            // Snowflake/standard format
26993            self.write_keyword("FILE_FORMAT");
26994            self.write(" = ");
26995            if let Some(this) = &e.this {
26996                self.generate_expression(this)?;
26997            } else if !e.expressions.is_empty() {
26998                self.write("(");
26999                for (i, expr) in e.expressions.iter().enumerate() {
27000                    if i > 0 {
27001                        self.write(", ");
27002                    }
27003                    self.generate_expression(expr)?;
27004                }
27005                self.write(")");
27006            }
27007        }
27008        Ok(())
27009    }
27010
27011    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
27012        // agg_func FILTER(WHERE condition)
27013        self.generate_expression(&e.this)?;
27014        self.write_space();
27015        self.write_keyword("FILTER");
27016        self.write("(");
27017        self.write_keyword("WHERE");
27018        self.write_space();
27019        self.generate_expression(&e.expression)?;
27020        self.write(")");
27021        Ok(())
27022    }
27023
27024    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
27025        // FLOAT64(this) or FLOAT64(this, expression)
27026        self.write_keyword("FLOAT64");
27027        self.write("(");
27028        self.generate_expression(&e.this)?;
27029        if let Some(expr) = &e.expression {
27030            self.write(", ");
27031            self.generate_expression(expr)?;
27032        }
27033        self.write(")");
27034        Ok(())
27035    }
27036
27037    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
27038        // FOR this DO expression
27039        self.write_keyword("FOR");
27040        self.write_space();
27041        self.generate_expression(&e.this)?;
27042        self.write_space();
27043        self.write_keyword("DO");
27044        self.write_space();
27045        self.generate_expression(&e.expression)?;
27046        Ok(())
27047    }
27048
27049    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
27050        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
27051        self.write_keyword("FOREIGN KEY");
27052        if !e.expressions.is_empty() {
27053            self.write(" (");
27054            for (i, expr) in e.expressions.iter().enumerate() {
27055                if i > 0 {
27056                    self.write(", ");
27057                }
27058                self.generate_expression(expr)?;
27059            }
27060            self.write(")");
27061        }
27062        if let Some(reference) = &e.reference {
27063            self.write_space();
27064            self.generate_expression(reference)?;
27065        }
27066        if let Some(delete) = &e.delete {
27067            self.write_space();
27068            self.write_keyword("ON DELETE");
27069            self.write_space();
27070            self.generate_expression(delete)?;
27071        }
27072        if let Some(update) = &e.update {
27073            self.write_space();
27074            self.write_keyword("ON UPDATE");
27075            self.write_space();
27076            self.generate_expression(update)?;
27077        }
27078        if !e.options.is_empty() {
27079            self.write_space();
27080            for (i, opt) in e.options.iter().enumerate() {
27081                if i > 0 {
27082                    self.write_space();
27083                }
27084                self.generate_expression(opt)?;
27085            }
27086        }
27087        Ok(())
27088    }
27089
27090    fn generate_format(&mut self, e: &Format) -> Result<()> {
27091        // FORMAT(this, expressions...)
27092        self.write_keyword("FORMAT");
27093        self.write("(");
27094        self.generate_expression(&e.this)?;
27095        for expr in &e.expressions {
27096            self.write(", ");
27097            self.generate_expression(expr)?;
27098        }
27099        self.write(")");
27100        Ok(())
27101    }
27102
27103    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
27104        // Teradata: column (FORMAT 'format_string')
27105        self.generate_expression(&e.this)?;
27106        self.write(" (");
27107        self.write_keyword("FORMAT");
27108        self.write(" '");
27109        self.write(&e.format);
27110        self.write("')");
27111        Ok(())
27112    }
27113
27114    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
27115        // Python: FREESPACE=this[PERCENT]
27116        self.write_keyword("FREESPACE");
27117        self.write("=");
27118        self.generate_expression(&e.this)?;
27119        if e.percent.is_some() {
27120            self.write_keyword(" PERCENT");
27121        }
27122        Ok(())
27123    }
27124
27125    fn generate_from(&mut self, e: &From) -> Result<()> {
27126        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
27127        self.write_keyword("FROM");
27128        self.write_space();
27129
27130        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
27131        // But keep commas when TABLESAMPLE is present
27132        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
27133        use crate::dialects::DialectType;
27134        let has_tablesample = e
27135            .expressions
27136            .iter()
27137            .any(|expr| matches!(expr, Expression::TableSample(_)));
27138        let is_cross_join_dialect = matches!(
27139            self.config.dialect,
27140            Some(DialectType::BigQuery)
27141                | Some(DialectType::Hive)
27142                | Some(DialectType::Spark)
27143                | Some(DialectType::Databricks)
27144                | Some(DialectType::SQLite)
27145                | Some(DialectType::ClickHouse)
27146        );
27147        let source_is_same_as_target2 = self.config.source_dialect.is_some()
27148            && self.config.source_dialect == self.config.dialect;
27149        let source_is_cross_join_dialect2 = matches!(
27150            self.config.source_dialect,
27151            Some(DialectType::BigQuery)
27152                | Some(DialectType::Hive)
27153                | Some(DialectType::Spark)
27154                | Some(DialectType::Databricks)
27155                | Some(DialectType::SQLite)
27156                | Some(DialectType::ClickHouse)
27157        );
27158        let use_cross_join = !has_tablesample
27159            && is_cross_join_dialect
27160            && (source_is_same_as_target2
27161                || source_is_cross_join_dialect2
27162                || self.config.source_dialect.is_none());
27163
27164        // Snowflake wraps standalone VALUES in FROM clause with parentheses
27165        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
27166
27167        for (i, expr) in e.expressions.iter().enumerate() {
27168            if i > 0 {
27169                if use_cross_join {
27170                    self.write(" CROSS JOIN ");
27171                } else {
27172                    self.write(", ");
27173                }
27174            }
27175            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
27176                self.write("(");
27177                self.generate_expression(expr)?;
27178                self.write(")");
27179            } else {
27180                self.generate_expression(expr)?;
27181            }
27182        }
27183        Ok(())
27184    }
27185
27186    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
27187        // FROM_BASE(this, expression) - convert from base N
27188        self.write_keyword("FROM_BASE");
27189        self.write("(");
27190        self.generate_expression(&e.this)?;
27191        self.write(", ");
27192        self.generate_expression(&e.expression)?;
27193        self.write(")");
27194        Ok(())
27195    }
27196
27197    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
27198        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
27199        self.generate_expression(&e.this)?;
27200        if let Some(zone) = &e.zone {
27201            self.write_space();
27202            self.write_keyword("AT TIME ZONE");
27203            self.write_space();
27204            self.generate_expression(zone)?;
27205            self.write_space();
27206            self.write_keyword("AT TIME ZONE");
27207            self.write(" 'UTC'");
27208        }
27209        Ok(())
27210    }
27211
27212    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
27213        // GAP_FILL(this, ts_column, bucket_width, ...)
27214        self.write_keyword("GAP_FILL");
27215        self.write("(");
27216        self.generate_expression(&e.this)?;
27217        if let Some(ts_column) = &e.ts_column {
27218            self.write(", ");
27219            self.generate_expression(ts_column)?;
27220        }
27221        if let Some(bucket_width) = &e.bucket_width {
27222            self.write(", ");
27223            self.generate_expression(bucket_width)?;
27224        }
27225        if let Some(partitioning_columns) = &e.partitioning_columns {
27226            self.write(", ");
27227            self.generate_expression(partitioning_columns)?;
27228        }
27229        if let Some(value_columns) = &e.value_columns {
27230            self.write(", ");
27231            self.generate_expression(value_columns)?;
27232        }
27233        self.write(")");
27234        Ok(())
27235    }
27236
27237    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
27238        // GENERATE_DATE_ARRAY(start, end, step)
27239        self.write_keyword("GENERATE_DATE_ARRAY");
27240        self.write("(");
27241        let mut first = true;
27242        if let Some(start) = &e.start {
27243            self.generate_expression(start)?;
27244            first = false;
27245        }
27246        if let Some(end) = &e.end {
27247            if !first {
27248                self.write(", ");
27249            }
27250            self.generate_expression(end)?;
27251            first = false;
27252        }
27253        if let Some(step) = &e.step {
27254            if !first {
27255                self.write(", ");
27256            }
27257            self.generate_expression(step)?;
27258        }
27259        self.write(")");
27260        Ok(())
27261    }
27262
27263    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
27264        // ML.GENERATE_EMBEDDING(model, content, params)
27265        self.write_keyword("ML.GENERATE_EMBEDDING");
27266        self.write("(");
27267        self.generate_expression(&e.this)?;
27268        self.write(", ");
27269        self.generate_expression(&e.expression)?;
27270        if let Some(params) = &e.params_struct {
27271            self.write(", ");
27272            self.generate_expression(params)?;
27273        }
27274        self.write(")");
27275        Ok(())
27276    }
27277
27278    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
27279        // Dialect-specific function name
27280        let fn_name = match self.config.dialect {
27281            Some(DialectType::Presto)
27282            | Some(DialectType::Trino)
27283            | Some(DialectType::Athena)
27284            | Some(DialectType::Spark)
27285            | Some(DialectType::Databricks)
27286            | Some(DialectType::Hive) => "SEQUENCE",
27287            _ => "GENERATE_SERIES",
27288        };
27289        self.write_keyword(fn_name);
27290        self.write("(");
27291        let mut first = true;
27292        if let Some(start) = &e.start {
27293            self.generate_expression(start)?;
27294            first = false;
27295        }
27296        if let Some(end) = &e.end {
27297            if !first {
27298                self.write(", ");
27299            }
27300            self.generate_expression(end)?;
27301            first = false;
27302        }
27303        if let Some(step) = &e.step {
27304            if !first {
27305                self.write(", ");
27306            }
27307            // For Presto/Trino: convert WEEK intervals to DAY multiples
27308            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
27309            if matches!(
27310                self.config.dialect,
27311                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
27312            ) {
27313                if let Some(converted) = self.convert_week_interval_to_day(step) {
27314                    self.generate_expression(&converted)?;
27315                } else {
27316                    self.generate_expression(step)?;
27317                }
27318            } else {
27319                self.generate_expression(step)?;
27320            }
27321        }
27322        self.write(")");
27323        Ok(())
27324    }
27325
27326    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
27327    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
27328    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
27329        use crate::expressions::*;
27330        if let Expression::Interval(ref iv) = expr {
27331            // Check for structured WEEK unit
27332            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
27333                unit: IntervalUnit::Week,
27334                ..
27335            }) = &iv.unit
27336            {
27337                // Value is in iv.this
27338                let count = match &iv.this {
27339                    Some(Expression::Literal(Literal::String(s))) => s.clone(),
27340                    Some(Expression::Literal(Literal::Number(s))) => s.clone(),
27341                    _ => return None,
27342                };
27343                (true, count)
27344            } else if iv.unit.is_none() {
27345                // Check for string-encoded interval like "1 WEEK"
27346                if let Some(Expression::Literal(Literal::String(s))) = &iv.this {
27347                    let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
27348                    if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
27349                        (true, parts[0].to_string())
27350                    } else {
27351                        (false, String::new())
27352                    }
27353                } else {
27354                    (false, String::new())
27355                }
27356            } else {
27357                (false, String::new())
27358            };
27359
27360            if is_week {
27361                // Build: (N * INTERVAL '7' DAY)
27362                let count_expr = Expression::Literal(Literal::Number(count_str));
27363                let day_interval = Expression::Interval(Box::new(Interval {
27364                    this: Some(Expression::Literal(Literal::String("7".to_string()))),
27365                    unit: Some(IntervalUnitSpec::Simple {
27366                        unit: IntervalUnit::Day,
27367                        use_plural: false,
27368                    }),
27369                }));
27370                let mul = Expression::Mul(Box::new(BinaryOp {
27371                    left: count_expr,
27372                    right: day_interval,
27373                    left_comments: vec![],
27374                    operator_comments: vec![],
27375                    trailing_comments: vec![],
27376                }));
27377                return Some(Expression::Paren(Box::new(Paren {
27378                    this: mul,
27379                    trailing_comments: vec![],
27380                })));
27381            }
27382        }
27383        None
27384    }
27385
27386    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
27387        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
27388        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
27389        self.write("(");
27390        let mut first = true;
27391        if let Some(start) = &e.start {
27392            self.generate_expression(start)?;
27393            first = false;
27394        }
27395        if let Some(end) = &e.end {
27396            if !first {
27397                self.write(", ");
27398            }
27399            self.generate_expression(end)?;
27400            first = false;
27401        }
27402        if let Some(step) = &e.step {
27403            if !first {
27404                self.write(", ");
27405            }
27406            self.generate_expression(step)?;
27407        }
27408        self.write(")");
27409        Ok(())
27410    }
27411
27412    fn generate_generated_as_identity_column_constraint(
27413        &mut self,
27414        e: &GeneratedAsIdentityColumnConstraint,
27415    ) -> Result<()> {
27416        use crate::dialects::DialectType;
27417
27418        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
27419        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
27420            self.write_keyword("AUTOINCREMENT");
27421            if let Some(start) = &e.start {
27422                self.write_keyword(" START ");
27423                self.generate_expression(start)?;
27424            }
27425            if let Some(increment) = &e.increment {
27426                self.write_keyword(" INCREMENT ");
27427                self.generate_expression(increment)?;
27428            }
27429            return Ok(());
27430        }
27431
27432        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
27433        self.write_keyword("GENERATED");
27434        if let Some(this) = &e.this {
27435            // Check if it's a truthy boolean expression
27436            if let Expression::Boolean(b) = this.as_ref() {
27437                if b.value {
27438                    self.write_keyword(" ALWAYS");
27439                } else {
27440                    self.write_keyword(" BY DEFAULT");
27441                    if e.on_null.is_some() {
27442                        self.write_keyword(" ON NULL");
27443                    }
27444                }
27445            } else {
27446                self.write_keyword(" ALWAYS");
27447            }
27448        }
27449        self.write_keyword(" AS IDENTITY");
27450        // Add sequence options if any
27451        let has_options = e.start.is_some()
27452            || e.increment.is_some()
27453            || e.minvalue.is_some()
27454            || e.maxvalue.is_some();
27455        if has_options {
27456            self.write(" (");
27457            let mut first = true;
27458            if let Some(start) = &e.start {
27459                self.write_keyword("START WITH ");
27460                self.generate_expression(start)?;
27461                first = false;
27462            }
27463            if let Some(increment) = &e.increment {
27464                if !first {
27465                    self.write(" ");
27466                }
27467                self.write_keyword("INCREMENT BY ");
27468                self.generate_expression(increment)?;
27469                first = false;
27470            }
27471            if let Some(minvalue) = &e.minvalue {
27472                if !first {
27473                    self.write(" ");
27474                }
27475                self.write_keyword("MINVALUE ");
27476                self.generate_expression(minvalue)?;
27477                first = false;
27478            }
27479            if let Some(maxvalue) = &e.maxvalue {
27480                if !first {
27481                    self.write(" ");
27482                }
27483                self.write_keyword("MAXVALUE ");
27484                self.generate_expression(maxvalue)?;
27485            }
27486            self.write(")");
27487        }
27488        Ok(())
27489    }
27490
27491    fn generate_generated_as_row_column_constraint(
27492        &mut self,
27493        e: &GeneratedAsRowColumnConstraint,
27494    ) -> Result<()> {
27495        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
27496        self.write_keyword("GENERATED ALWAYS AS ROW ");
27497        if e.start.is_some() {
27498            self.write_keyword("START");
27499        } else {
27500            self.write_keyword("END");
27501        }
27502        if e.hidden.is_some() {
27503            self.write_keyword(" HIDDEN");
27504        }
27505        Ok(())
27506    }
27507
27508    fn generate_get(&mut self, e: &Get) -> Result<()> {
27509        // GET this target properties
27510        self.write_keyword("GET");
27511        self.write_space();
27512        self.generate_expression(&e.this)?;
27513        if let Some(target) = &e.target {
27514            self.write_space();
27515            self.generate_expression(target)?;
27516        }
27517        for prop in &e.properties {
27518            self.write_space();
27519            self.generate_expression(prop)?;
27520        }
27521        Ok(())
27522    }
27523
27524    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
27525        // GetExtract generates bracket access: this[expression]
27526        self.generate_expression(&e.this)?;
27527        self.write("[");
27528        self.generate_expression(&e.expression)?;
27529        self.write("]");
27530        Ok(())
27531    }
27532
27533    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
27534        // GETBIT(this, expression) or GET_BIT(this, expression)
27535        self.write_keyword("GETBIT");
27536        self.write("(");
27537        self.generate_expression(&e.this)?;
27538        self.write(", ");
27539        self.generate_expression(&e.expression)?;
27540        self.write(")");
27541        Ok(())
27542    }
27543
27544    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
27545        // [ROLE|GROUP] name (e.g., "ROLE admin", "GROUP qa_users", or just "user1")
27546        if e.is_role {
27547            self.write_keyword("ROLE");
27548            self.write_space();
27549        } else if e.is_group {
27550            self.write_keyword("GROUP");
27551            self.write_space();
27552        }
27553        self.write(&e.name.name);
27554        Ok(())
27555    }
27556
27557    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
27558        // privilege(columns) or just privilege
27559        self.generate_expression(&e.this)?;
27560        if !e.expressions.is_empty() {
27561            self.write("(");
27562            for (i, expr) in e.expressions.iter().enumerate() {
27563                if i > 0 {
27564                    self.write(", ");
27565                }
27566                self.generate_expression(expr)?;
27567            }
27568            self.write(")");
27569        }
27570        Ok(())
27571    }
27572
27573    fn generate_group(&mut self, e: &Group) -> Result<()> {
27574        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
27575        self.write_keyword("GROUP BY");
27576        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
27577        match e.all {
27578            Some(true) => {
27579                self.write_space();
27580                self.write_keyword("ALL");
27581            }
27582            Some(false) => {
27583                self.write_space();
27584                self.write_keyword("DISTINCT");
27585            }
27586            None => {}
27587        }
27588        if !e.expressions.is_empty() {
27589            self.write_space();
27590            for (i, expr) in e.expressions.iter().enumerate() {
27591                if i > 0 {
27592                    self.write(", ");
27593                }
27594                self.generate_expression(expr)?;
27595            }
27596        }
27597        // Handle CUBE, ROLLUP, GROUPING SETS
27598        if let Some(cube) = &e.cube {
27599            if !e.expressions.is_empty() {
27600                self.write(", ");
27601            } else {
27602                self.write_space();
27603            }
27604            self.generate_expression(cube)?;
27605        }
27606        if let Some(rollup) = &e.rollup {
27607            if !e.expressions.is_empty() || e.cube.is_some() {
27608                self.write(", ");
27609            } else {
27610                self.write_space();
27611            }
27612            self.generate_expression(rollup)?;
27613        }
27614        if let Some(grouping_sets) = &e.grouping_sets {
27615            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
27616                self.write(", ");
27617            } else {
27618                self.write_space();
27619            }
27620            self.generate_expression(grouping_sets)?;
27621        }
27622        if let Some(totals) = &e.totals {
27623            self.write_space();
27624            self.write_keyword("WITH TOTALS");
27625            self.generate_expression(totals)?;
27626        }
27627        Ok(())
27628    }
27629
27630    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
27631        // GROUP BY expressions
27632        self.write_keyword("GROUP BY");
27633        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
27634        match e.all {
27635            Some(true) => {
27636                self.write_space();
27637                self.write_keyword("ALL");
27638            }
27639            Some(false) => {
27640                self.write_space();
27641                self.write_keyword("DISTINCT");
27642            }
27643            None => {}
27644        }
27645
27646        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
27647        // These are represented as Cube/Rollup expressions with empty expressions at the end
27648        let mut trailing_cube = false;
27649        let mut trailing_rollup = false;
27650        let mut regular_expressions: Vec<&Expression> = Vec::new();
27651
27652        for expr in &e.expressions {
27653            match expr {
27654                Expression::Cube(c) if c.expressions.is_empty() => {
27655                    trailing_cube = true;
27656                }
27657                Expression::Rollup(r) if r.expressions.is_empty() => {
27658                    trailing_rollup = true;
27659                }
27660                _ => {
27661                    regular_expressions.push(expr);
27662                }
27663            }
27664        }
27665
27666        // In pretty mode, put columns on separate lines
27667        if self.config.pretty {
27668            self.write_newline();
27669            self.indent_level += 1;
27670            for (i, expr) in regular_expressions.iter().enumerate() {
27671                if i > 0 {
27672                    self.write(",");
27673                    self.write_newline();
27674                }
27675                self.write_indent();
27676                self.generate_expression(expr)?;
27677            }
27678            self.indent_level -= 1;
27679        } else {
27680            self.write_space();
27681            for (i, expr) in regular_expressions.iter().enumerate() {
27682                if i > 0 {
27683                    self.write(", ");
27684                }
27685                self.generate_expression(expr)?;
27686            }
27687        }
27688
27689        // Output trailing WITH CUBE or WITH ROLLUP
27690        if trailing_cube {
27691            self.write_space();
27692            self.write_keyword("WITH CUBE");
27693        } else if trailing_rollup {
27694            self.write_space();
27695            self.write_keyword("WITH ROLLUP");
27696        }
27697
27698        // ClickHouse: WITH TOTALS
27699        if e.totals {
27700            self.write_space();
27701            self.write_keyword("WITH TOTALS");
27702        }
27703
27704        Ok(())
27705    }
27706
27707    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
27708        // GROUPING(col1, col2, ...)
27709        self.write_keyword("GROUPING");
27710        self.write("(");
27711        for (i, expr) in e.expressions.iter().enumerate() {
27712            if i > 0 {
27713                self.write(", ");
27714            }
27715            self.generate_expression(expr)?;
27716        }
27717        self.write(")");
27718        Ok(())
27719    }
27720
27721    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
27722        // GROUPING_ID(col1, col2, ...)
27723        self.write_keyword("GROUPING_ID");
27724        self.write("(");
27725        for (i, expr) in e.expressions.iter().enumerate() {
27726            if i > 0 {
27727                self.write(", ");
27728            }
27729            self.generate_expression(expr)?;
27730        }
27731        self.write(")");
27732        Ok(())
27733    }
27734
27735    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
27736        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
27737        self.write_keyword("GROUPING SETS");
27738        self.write(" (");
27739        for (i, expr) in e.expressions.iter().enumerate() {
27740            if i > 0 {
27741                self.write(", ");
27742            }
27743            self.generate_expression(expr)?;
27744        }
27745        self.write(")");
27746        Ok(())
27747    }
27748
27749    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
27750        // HASH_AGG(this, expressions...)
27751        self.write_keyword("HASH_AGG");
27752        self.write("(");
27753        self.generate_expression(&e.this)?;
27754        for expr in &e.expressions {
27755            self.write(", ");
27756            self.generate_expression(expr)?;
27757        }
27758        self.write(")");
27759        Ok(())
27760    }
27761
27762    fn generate_having(&mut self, e: &Having) -> Result<()> {
27763        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
27764        self.write_keyword("HAVING");
27765        self.write_space();
27766        self.generate_expression(&e.this)?;
27767        Ok(())
27768    }
27769
27770    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
27771        // Python: this HAVING MAX|MIN expression
27772        self.generate_expression(&e.this)?;
27773        self.write_space();
27774        self.write_keyword("HAVING");
27775        self.write_space();
27776        if e.max.is_some() {
27777            self.write_keyword("MAX");
27778        } else {
27779            self.write_keyword("MIN");
27780        }
27781        self.write_space();
27782        self.generate_expression(&e.expression)?;
27783        Ok(())
27784    }
27785
27786    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
27787        use crate::dialects::DialectType;
27788        // DuckDB: convert dollar-tagged strings to single-quoted
27789        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
27790            // Extract the string content and output as single-quoted
27791            if let Expression::Literal(Literal::String(ref s)) = *e.this {
27792                return self.generate_string_literal(s);
27793            }
27794        }
27795        // PostgreSQL: preserve dollar-quoting
27796        if matches!(
27797            self.config.dialect,
27798            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
27799        ) {
27800            self.write("$");
27801            if let Some(tag) = &e.tag {
27802                self.generate_expression(tag)?;
27803            }
27804            self.write("$");
27805            self.generate_expression(&e.this)?;
27806            self.write("$");
27807            if let Some(tag) = &e.tag {
27808                self.generate_expression(tag)?;
27809            }
27810            self.write("$");
27811            return Ok(());
27812        }
27813        // Default: output as dollar-tagged
27814        self.write("$");
27815        if let Some(tag) = &e.tag {
27816            self.generate_expression(tag)?;
27817        }
27818        self.write("$");
27819        self.generate_expression(&e.this)?;
27820        self.write("$");
27821        if let Some(tag) = &e.tag {
27822            self.generate_expression(tag)?;
27823        }
27824        self.write("$");
27825        Ok(())
27826    }
27827
27828    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
27829        // HEX_ENCODE(this)
27830        self.write_keyword("HEX_ENCODE");
27831        self.write("(");
27832        self.generate_expression(&e.this)?;
27833        self.write(")");
27834        Ok(())
27835    }
27836
27837    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
27838        // Python: this (kind => expression)
27839        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
27840        match e.this.as_ref() {
27841            Expression::Identifier(id) => self.write(&id.name),
27842            other => self.generate_expression(other)?,
27843        }
27844        self.write(" (");
27845        self.write(&e.kind);
27846        self.write(" => ");
27847        self.generate_expression(&e.expression)?;
27848        self.write(")");
27849        Ok(())
27850    }
27851
27852    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
27853        // HLL(this, expressions...)
27854        self.write_keyword("HLL");
27855        self.write("(");
27856        self.generate_expression(&e.this)?;
27857        for expr in &e.expressions {
27858            self.write(", ");
27859            self.generate_expression(expr)?;
27860        }
27861        self.write(")");
27862        Ok(())
27863    }
27864
27865    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
27866        // Python: IN|OUT|IN OUT
27867        if e.input_.is_some() && e.output.is_some() {
27868            self.write_keyword("IN OUT");
27869        } else if e.input_.is_some() {
27870            self.write_keyword("IN");
27871        } else if e.output.is_some() {
27872            self.write_keyword("OUT");
27873        }
27874        Ok(())
27875    }
27876
27877    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
27878        // Python: INCLUDE this [column_def] [AS alias]
27879        self.write_keyword("INCLUDE");
27880        self.write_space();
27881        self.generate_expression(&e.this)?;
27882        if let Some(column_def) = &e.column_def {
27883            self.write_space();
27884            self.generate_expression(column_def)?;
27885        }
27886        if let Some(alias) = &e.alias {
27887            self.write_space();
27888            self.write_keyword("AS");
27889            self.write_space();
27890            self.write(alias);
27891        }
27892        Ok(())
27893    }
27894
27895    fn generate_index(&mut self, e: &Index) -> Result<()> {
27896        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
27897        if e.unique {
27898            self.write_keyword("UNIQUE");
27899            self.write_space();
27900        }
27901        if e.primary.is_some() {
27902            self.write_keyword("PRIMARY");
27903            self.write_space();
27904        }
27905        if e.amp.is_some() {
27906            self.write_keyword("AMP");
27907            self.write_space();
27908        }
27909        if e.table.is_none() {
27910            self.write_keyword("INDEX");
27911            self.write_space();
27912        }
27913        if let Some(name) = &e.this {
27914            self.generate_expression(name)?;
27915            self.write_space();
27916        }
27917        if let Some(table) = &e.table {
27918            self.write_keyword("ON");
27919            self.write_space();
27920            self.generate_expression(table)?;
27921        }
27922        if !e.params.is_empty() {
27923            self.write("(");
27924            for (i, param) in e.params.iter().enumerate() {
27925                if i > 0 {
27926                    self.write(", ");
27927                }
27928                self.generate_expression(param)?;
27929            }
27930            self.write(")");
27931        }
27932        Ok(())
27933    }
27934
27935    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
27936        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
27937        if let Some(kind) = &e.kind {
27938            self.write(kind);
27939            self.write_space();
27940        }
27941        self.write_keyword("INDEX");
27942        if let Some(this) = &e.this {
27943            self.write_space();
27944            self.generate_expression(this)?;
27945        }
27946        if let Some(index_type) = &e.index_type {
27947            self.write_space();
27948            self.write_keyword("USING");
27949            self.write_space();
27950            self.generate_expression(index_type)?;
27951        }
27952        if !e.expressions.is_empty() {
27953            self.write(" (");
27954            for (i, expr) in e.expressions.iter().enumerate() {
27955                if i > 0 {
27956                    self.write(", ");
27957                }
27958                self.generate_expression(expr)?;
27959            }
27960            self.write(")");
27961        }
27962        for opt in &e.options {
27963            self.write_space();
27964            self.generate_expression(opt)?;
27965        }
27966        Ok(())
27967    }
27968
27969    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
27970        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
27971        if let Some(key_block_size) = &e.key_block_size {
27972            self.write_keyword("KEY_BLOCK_SIZE");
27973            self.write(" = ");
27974            self.generate_expression(key_block_size)?;
27975        } else if let Some(using) = &e.using {
27976            self.write_keyword("USING");
27977            self.write_space();
27978            self.generate_expression(using)?;
27979        } else if let Some(parser) = &e.parser {
27980            self.write_keyword("WITH PARSER");
27981            self.write_space();
27982            self.generate_expression(parser)?;
27983        } else if let Some(comment) = &e.comment {
27984            self.write_keyword("COMMENT");
27985            self.write_space();
27986            self.generate_expression(comment)?;
27987        } else if let Some(visible) = &e.visible {
27988            self.generate_expression(visible)?;
27989        } else if let Some(engine_attr) = &e.engine_attr {
27990            self.write_keyword("ENGINE_ATTRIBUTE");
27991            self.write(" = ");
27992            self.generate_expression(engine_attr)?;
27993        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
27994            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
27995            self.write(" = ");
27996            self.generate_expression(secondary_engine_attr)?;
27997        }
27998        Ok(())
27999    }
28000
28001    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
28002        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
28003        if let Some(using) = &e.using {
28004            self.write_keyword("USING");
28005            self.write_space();
28006            self.generate_expression(using)?;
28007        }
28008        if !e.columns.is_empty() {
28009            self.write("(");
28010            for (i, col) in e.columns.iter().enumerate() {
28011                if i > 0 {
28012                    self.write(", ");
28013                }
28014                self.generate_expression(col)?;
28015            }
28016            self.write(")");
28017        }
28018        if let Some(partition_by) = &e.partition_by {
28019            self.write_space();
28020            self.write_keyword("PARTITION BY");
28021            self.write_space();
28022            self.generate_expression(partition_by)?;
28023        }
28024        if let Some(where_) = &e.where_ {
28025            self.write_space();
28026            self.generate_expression(where_)?;
28027        }
28028        if let Some(include) = &e.include {
28029            self.write_space();
28030            self.write_keyword("INCLUDE");
28031            self.write(" (");
28032            self.generate_expression(include)?;
28033            self.write(")");
28034        }
28035        if let Some(with_storage) = &e.with_storage {
28036            self.write_space();
28037            self.write_keyword("WITH");
28038            self.write(" (");
28039            self.generate_expression(with_storage)?;
28040            self.write(")");
28041        }
28042        if let Some(tablespace) = &e.tablespace {
28043            self.write_space();
28044            self.write_keyword("USING INDEX TABLESPACE");
28045            self.write_space();
28046            self.generate_expression(tablespace)?;
28047        }
28048        Ok(())
28049    }
28050
28051    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
28052        // Python: this INDEX [FOR target] (expressions)
28053        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
28054        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
28055        if let Expression::Identifier(id) = &*e.this {
28056            self.write_keyword(&id.name);
28057        } else {
28058            self.generate_expression(&e.this)?;
28059        }
28060        self.write_space();
28061        self.write_keyword("INDEX");
28062        if let Some(target) = &e.target {
28063            self.write_space();
28064            self.write_keyword("FOR");
28065            self.write_space();
28066            if let Expression::Identifier(id) = &**target {
28067                self.write_keyword(&id.name);
28068            } else {
28069                self.generate_expression(target)?;
28070            }
28071        }
28072        // Always output parentheses (even if empty, e.g. USE INDEX ())
28073        self.write(" (");
28074        for (i, expr) in e.expressions.iter().enumerate() {
28075            if i > 0 {
28076                self.write(", ");
28077            }
28078            self.generate_expression(expr)?;
28079        }
28080        self.write(")");
28081        Ok(())
28082    }
28083
28084    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
28085        // INHERITS (table1, table2, ...)
28086        self.write_keyword("INHERITS");
28087        self.write(" (");
28088        for (i, expr) in e.expressions.iter().enumerate() {
28089            if i > 0 {
28090                self.write(", ");
28091            }
28092            self.generate_expression(expr)?;
28093        }
28094        self.write(")");
28095        Ok(())
28096    }
28097
28098    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
28099        // INPUT(model)
28100        self.write_keyword("INPUT");
28101        self.write("(");
28102        self.generate_expression(&e.this)?;
28103        self.write(")");
28104        Ok(())
28105    }
28106
28107    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
28108        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
28109        if let Some(input_format) = &e.input_format {
28110            self.write_keyword("INPUTFORMAT");
28111            self.write_space();
28112            self.generate_expression(input_format)?;
28113        }
28114        if let Some(output_format) = &e.output_format {
28115            if e.input_format.is_some() {
28116                self.write(" ");
28117            }
28118            self.write_keyword("OUTPUTFORMAT");
28119            self.write_space();
28120            self.generate_expression(output_format)?;
28121        }
28122        Ok(())
28123    }
28124
28125    fn generate_install(&mut self, e: &Install) -> Result<()> {
28126        // [FORCE] INSTALL extension [FROM source]
28127        if e.force.is_some() {
28128            self.write_keyword("FORCE");
28129            self.write_space();
28130        }
28131        self.write_keyword("INSTALL");
28132        self.write_space();
28133        self.generate_expression(&e.this)?;
28134        if let Some(from) = &e.from_ {
28135            self.write_space();
28136            self.write_keyword("FROM");
28137            self.write_space();
28138            self.generate_expression(from)?;
28139        }
28140        Ok(())
28141    }
28142
28143    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
28144        // INTERVAL 'expression' unit
28145        self.write_keyword("INTERVAL");
28146        self.write_space();
28147        // When a unit is specified and the expression is a number,
28148        self.generate_expression(&e.expression)?;
28149        if let Some(unit) = &e.unit {
28150            self.write_space();
28151            self.write(unit);
28152        }
28153        Ok(())
28154    }
28155
28156    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
28157        // unit TO unit (e.g., HOUR TO SECOND)
28158        self.write(&format!("{:?}", e.this).to_uppercase());
28159        self.write_space();
28160        self.write_keyword("TO");
28161        self.write_space();
28162        self.write(&format!("{:?}", e.expression).to_uppercase());
28163        Ok(())
28164    }
28165
28166    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
28167        // INTO [TEMPORARY|UNLOGGED] table
28168        self.write_keyword("INTO");
28169        if e.temporary {
28170            self.write_keyword(" TEMPORARY");
28171        }
28172        if e.unlogged.is_some() {
28173            self.write_keyword(" UNLOGGED");
28174        }
28175        if let Some(this) = &e.this {
28176            self.write_space();
28177            self.generate_expression(this)?;
28178        }
28179        if !e.expressions.is_empty() {
28180            self.write(" (");
28181            for (i, expr) in e.expressions.iter().enumerate() {
28182                if i > 0 {
28183                    self.write(", ");
28184                }
28185                self.generate_expression(expr)?;
28186            }
28187            self.write(")");
28188        }
28189        Ok(())
28190    }
28191
28192    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
28193        // Python: this expression (e.g., _utf8 'string')
28194        self.generate_expression(&e.this)?;
28195        self.write_space();
28196        self.generate_expression(&e.expression)?;
28197        Ok(())
28198    }
28199
28200    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
28201        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
28202        self.write_keyword("WITH");
28203        if e.no.is_some() {
28204            self.write_keyword(" NO");
28205        }
28206        if e.concurrent.is_some() {
28207            self.write_keyword(" CONCURRENT");
28208        }
28209        self.write_keyword(" ISOLATED LOADING");
28210        if let Some(target) = &e.target {
28211            self.write_space();
28212            self.generate_expression(target)?;
28213        }
28214        Ok(())
28215    }
28216
28217    fn generate_json(&mut self, e: &JSON) -> Result<()> {
28218        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
28219        self.write_keyword("JSON");
28220        if let Some(this) = &e.this {
28221            self.write_space();
28222            self.generate_expression(this)?;
28223        }
28224        if let Some(with_) = &e.with_ {
28225            // Check if it's a truthy boolean
28226            if let Expression::Boolean(b) = with_.as_ref() {
28227                if b.value {
28228                    self.write_keyword(" WITH");
28229                } else {
28230                    self.write_keyword(" WITHOUT");
28231                }
28232            }
28233        }
28234        if e.unique {
28235            self.write_keyword(" UNIQUE KEYS");
28236        }
28237        Ok(())
28238    }
28239
28240    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
28241        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
28242        self.write_keyword("JSON_ARRAY");
28243        self.write("(");
28244        for (i, expr) in e.expressions.iter().enumerate() {
28245            if i > 0 {
28246                self.write(", ");
28247            }
28248            self.generate_expression(expr)?;
28249        }
28250        if let Some(null_handling) = &e.null_handling {
28251            self.write_space();
28252            self.generate_expression(null_handling)?;
28253        }
28254        if let Some(return_type) = &e.return_type {
28255            self.write_space();
28256            self.write_keyword("RETURNING");
28257            self.write_space();
28258            self.generate_expression(return_type)?;
28259        }
28260        if e.strict.is_some() {
28261            self.write_space();
28262            self.write_keyword("STRICT");
28263        }
28264        self.write(")");
28265        Ok(())
28266    }
28267
28268    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
28269        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
28270        self.write_keyword("JSON_ARRAYAGG");
28271        self.write("(");
28272        self.generate_expression(&e.this)?;
28273        if let Some(order) = &e.order {
28274            self.write_space();
28275            // Order is stored as an OrderBy expression
28276            if let Expression::OrderBy(ob) = order.as_ref() {
28277                self.write_keyword("ORDER BY");
28278                self.write_space();
28279                for (i, ord) in ob.expressions.iter().enumerate() {
28280                    if i > 0 {
28281                        self.write(", ");
28282                    }
28283                    self.generate_ordered(ord)?;
28284                }
28285            } else {
28286                // Fallback: generate the expression directly
28287                self.generate_expression(order)?;
28288            }
28289        }
28290        if let Some(null_handling) = &e.null_handling {
28291            self.write_space();
28292            self.generate_expression(null_handling)?;
28293        }
28294        if let Some(return_type) = &e.return_type {
28295            self.write_space();
28296            self.write_keyword("RETURNING");
28297            self.write_space();
28298            self.generate_expression(return_type)?;
28299        }
28300        if e.strict.is_some() {
28301            self.write_space();
28302            self.write_keyword("STRICT");
28303        }
28304        self.write(")");
28305        Ok(())
28306    }
28307
28308    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
28309        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
28310        self.write_keyword("JSON_OBJECTAGG");
28311        self.write("(");
28312        for (i, expr) in e.expressions.iter().enumerate() {
28313            if i > 0 {
28314                self.write(", ");
28315            }
28316            self.generate_expression(expr)?;
28317        }
28318        if let Some(null_handling) = &e.null_handling {
28319            self.write_space();
28320            self.generate_expression(null_handling)?;
28321        }
28322        if let Some(unique_keys) = &e.unique_keys {
28323            self.write_space();
28324            if let Expression::Boolean(b) = unique_keys.as_ref() {
28325                if b.value {
28326                    self.write_keyword("WITH UNIQUE KEYS");
28327                } else {
28328                    self.write_keyword("WITHOUT UNIQUE KEYS");
28329                }
28330            }
28331        }
28332        if let Some(return_type) = &e.return_type {
28333            self.write_space();
28334            self.write_keyword("RETURNING");
28335            self.write_space();
28336            self.generate_expression(return_type)?;
28337        }
28338        self.write(")");
28339        Ok(())
28340    }
28341
28342    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
28343        // JSON_ARRAY_APPEND(this, path, value, ...)
28344        self.write_keyword("JSON_ARRAY_APPEND");
28345        self.write("(");
28346        self.generate_expression(&e.this)?;
28347        for expr in &e.expressions {
28348            self.write(", ");
28349            self.generate_expression(expr)?;
28350        }
28351        self.write(")");
28352        Ok(())
28353    }
28354
28355    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
28356        // JSON_ARRAY_CONTAINS(this, expression)
28357        self.write_keyword("JSON_ARRAY_CONTAINS");
28358        self.write("(");
28359        self.generate_expression(&e.this)?;
28360        self.write(", ");
28361        self.generate_expression(&e.expression)?;
28362        self.write(")");
28363        Ok(())
28364    }
28365
28366    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
28367        // JSON_ARRAY_INSERT(this, path, value, ...)
28368        self.write_keyword("JSON_ARRAY_INSERT");
28369        self.write("(");
28370        self.generate_expression(&e.this)?;
28371        for expr in &e.expressions {
28372            self.write(", ");
28373            self.generate_expression(expr)?;
28374        }
28375        self.write(")");
28376        Ok(())
28377    }
28378
28379    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
28380        // JSONB_EXISTS(this, path)
28381        self.write_keyword("JSONB_EXISTS");
28382        self.write("(");
28383        self.generate_expression(&e.this)?;
28384        if let Some(path) = &e.path {
28385            self.write(", ");
28386            self.generate_expression(path)?;
28387        }
28388        self.write(")");
28389        Ok(())
28390    }
28391
28392    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
28393        // JSONB_EXTRACT_SCALAR(this, expression)
28394        self.write_keyword("JSONB_EXTRACT_SCALAR");
28395        self.write("(");
28396        self.generate_expression(&e.this)?;
28397        self.write(", ");
28398        self.generate_expression(&e.expression)?;
28399        self.write(")");
28400        Ok(())
28401    }
28402
28403    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
28404        // JSONB_OBJECT_AGG(this, expression)
28405        self.write_keyword("JSONB_OBJECT_AGG");
28406        self.write("(");
28407        self.generate_expression(&e.this)?;
28408        self.write(", ");
28409        self.generate_expression(&e.expression)?;
28410        self.write(")");
28411        Ok(())
28412    }
28413
28414    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
28415        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
28416        if let Some(nested_schema) = &e.nested_schema {
28417            self.write_keyword("NESTED");
28418            if let Some(path) = &e.path {
28419                self.write_space();
28420                self.write_keyword("PATH");
28421                self.write_space();
28422                self.generate_expression(path)?;
28423            }
28424            self.write_space();
28425            self.generate_expression(nested_schema)?;
28426        } else {
28427            if let Some(this) = &e.this {
28428                self.generate_expression(this)?;
28429            }
28430            if let Some(kind) = &e.kind {
28431                self.write_space();
28432                self.write(kind);
28433            }
28434            if let Some(path) = &e.path {
28435                self.write_space();
28436                self.write_keyword("PATH");
28437                self.write_space();
28438                self.generate_expression(path)?;
28439            }
28440            if e.ordinality.is_some() {
28441                self.write_keyword(" FOR ORDINALITY");
28442            }
28443        }
28444        Ok(())
28445    }
28446
28447    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
28448        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
28449        self.write_keyword("JSON_EXISTS");
28450        self.write("(");
28451        self.generate_expression(&e.this)?;
28452        if let Some(path) = &e.path {
28453            self.write(", ");
28454            self.generate_expression(path)?;
28455        }
28456        if let Some(passing) = &e.passing {
28457            self.write_space();
28458            self.write_keyword("PASSING");
28459            self.write_space();
28460            self.generate_expression(passing)?;
28461        }
28462        if let Some(on_condition) = &e.on_condition {
28463            self.write_space();
28464            self.generate_expression(on_condition)?;
28465        }
28466        self.write(")");
28467        Ok(())
28468    }
28469
28470    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
28471        self.generate_expression(&e.this)?;
28472        self.write(".:");
28473        self.generate_data_type(&e.to)?;
28474        Ok(())
28475    }
28476
28477    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
28478        // JSON_EXTRACT_ARRAY(this, expression)
28479        self.write_keyword("JSON_EXTRACT_ARRAY");
28480        self.write("(");
28481        self.generate_expression(&e.this)?;
28482        if let Some(expr) = &e.expression {
28483            self.write(", ");
28484            self.generate_expression(expr)?;
28485        }
28486        self.write(")");
28487        Ok(())
28488    }
28489
28490    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
28491        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
28492        if let Some(option) = &e.option {
28493            self.generate_expression(option)?;
28494            self.write_space();
28495        }
28496        self.write_keyword("QUOTES");
28497        if e.scalar.is_some() {
28498            self.write_keyword(" SCALAR_ONLY");
28499        }
28500        Ok(())
28501    }
28502
28503    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
28504        // JSON_EXTRACT_SCALAR(this, expression)
28505        self.write_keyword("JSON_EXTRACT_SCALAR");
28506        self.write("(");
28507        self.generate_expression(&e.this)?;
28508        self.write(", ");
28509        self.generate_expression(&e.expression)?;
28510        self.write(")");
28511        Ok(())
28512    }
28513
28514    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
28515        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
28516        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
28517        // Otherwise output JSON_EXTRACT(this, expression)
28518        if e.variant_extract.is_some() {
28519            use crate::dialects::DialectType;
28520            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
28521                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
28522                self.generate_expression(&e.this)?;
28523                self.write(":");
28524                // The expression is a string literal containing the path (e.g., 'price' or 'price.foo')
28525                // We need to output it without quotes
28526                match e.expression.as_ref() {
28527                    Expression::Literal(Literal::String(s)) => {
28528                        self.write(s);
28529                    }
28530                    _ => {
28531                        // Fallback: generate as-is (shouldn't happen in typical cases)
28532                        self.generate_expression(&e.expression)?;
28533                    }
28534                }
28535            } else {
28536                // Snowflake and others: use GET_PATH(col, 'path')
28537                self.write_keyword("GET_PATH");
28538                self.write("(");
28539                self.generate_expression(&e.this)?;
28540                self.write(", ");
28541                self.generate_expression(&e.expression)?;
28542                self.write(")");
28543            }
28544        } else {
28545            self.write_keyword("JSON_EXTRACT");
28546            self.write("(");
28547            self.generate_expression(&e.this)?;
28548            self.write(", ");
28549            self.generate_expression(&e.expression)?;
28550            for expr in &e.expressions {
28551                self.write(", ");
28552                self.generate_expression(expr)?;
28553            }
28554            self.write(")");
28555        }
28556        Ok(())
28557    }
28558
28559    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
28560        // Output: {expr} FORMAT JSON
28561        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
28562        if let Some(this) = &e.this {
28563            self.generate_expression(this)?;
28564            self.write_space();
28565        }
28566        self.write_keyword("FORMAT JSON");
28567        Ok(())
28568    }
28569
28570    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
28571        // key: value (for JSON objects)
28572        self.generate_expression(&e.this)?;
28573        self.write(": ");
28574        self.generate_expression(&e.expression)?;
28575        Ok(())
28576    }
28577
28578    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
28579        // JSON_KEYS(this, expression, expressions...)
28580        self.write_keyword("JSON_KEYS");
28581        self.write("(");
28582        self.generate_expression(&e.this)?;
28583        if let Some(expr) = &e.expression {
28584            self.write(", ");
28585            self.generate_expression(expr)?;
28586        }
28587        for expr in &e.expressions {
28588            self.write(", ");
28589            self.generate_expression(expr)?;
28590        }
28591        self.write(")");
28592        Ok(())
28593    }
28594
28595    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
28596        // JSON_KEYS(this, expression)
28597        self.write_keyword("JSON_KEYS");
28598        self.write("(");
28599        self.generate_expression(&e.this)?;
28600        if let Some(expr) = &e.expression {
28601            self.write(", ");
28602            self.generate_expression(expr)?;
28603        }
28604        self.write(")");
28605        Ok(())
28606    }
28607
28608    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
28609        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
28610        // The path components are concatenated without spaces
28611        let mut path_str = String::new();
28612        for expr in &e.expressions {
28613            match expr {
28614                Expression::JSONPathRoot(_) => {
28615                    path_str.push('$');
28616                }
28617                Expression::JSONPathKey(k) => {
28618                    // .key or ."key" (quote if key has special characters)
28619                    if let Expression::Literal(crate::expressions::Literal::String(s)) =
28620                        k.this.as_ref()
28621                    {
28622                        path_str.push('.');
28623                        // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
28624                        let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
28625                        if needs_quoting {
28626                            path_str.push('"');
28627                            path_str.push_str(s);
28628                            path_str.push('"');
28629                        } else {
28630                            path_str.push_str(s);
28631                        }
28632                    }
28633                }
28634                Expression::JSONPathSubscript(s) => {
28635                    // [index]
28636                    if let Expression::Literal(crate::expressions::Literal::Number(n)) =
28637                        s.this.as_ref()
28638                    {
28639                        path_str.push('[');
28640                        path_str.push_str(n);
28641                        path_str.push(']');
28642                    }
28643                }
28644                _ => {
28645                    // For other path parts, try to generate them
28646                    let mut temp_gen = Self::with_config(self.config.clone());
28647                    temp_gen.generate_expression(expr)?;
28648                    path_str.push_str(&temp_gen.output);
28649                }
28650            }
28651        }
28652        // Output as quoted string
28653        self.write("'");
28654        self.write(&path_str);
28655        self.write("'");
28656        Ok(())
28657    }
28658
28659    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
28660        // JSON path filter: ?(predicate)
28661        self.write("?(");
28662        self.generate_expression(&e.this)?;
28663        self.write(")");
28664        Ok(())
28665    }
28666
28667    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
28668        // JSON path key: .key or ["key"]
28669        self.write(".");
28670        self.generate_expression(&e.this)?;
28671        Ok(())
28672    }
28673
28674    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
28675        // JSON path recursive descent: ..
28676        self.write("..");
28677        if let Some(this) = &e.this {
28678            self.generate_expression(this)?;
28679        }
28680        Ok(())
28681    }
28682
28683    fn generate_json_path_root(&mut self) -> Result<()> {
28684        // JSON path root: $
28685        self.write("$");
28686        Ok(())
28687    }
28688
28689    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
28690        // JSON path script: (expression)
28691        self.write("(");
28692        self.generate_expression(&e.this)?;
28693        self.write(")");
28694        Ok(())
28695    }
28696
28697    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
28698        // JSON path selector: *
28699        self.generate_expression(&e.this)?;
28700        Ok(())
28701    }
28702
28703    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
28704        // JSON path slice: [start:end:step]
28705        self.write("[");
28706        if let Some(start) = &e.start {
28707            self.generate_expression(start)?;
28708        }
28709        self.write(":");
28710        if let Some(end) = &e.end {
28711            self.generate_expression(end)?;
28712        }
28713        if let Some(step) = &e.step {
28714            self.write(":");
28715            self.generate_expression(step)?;
28716        }
28717        self.write("]");
28718        Ok(())
28719    }
28720
28721    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
28722        // JSON path subscript: [index] or [*]
28723        self.write("[");
28724        self.generate_expression(&e.this)?;
28725        self.write("]");
28726        Ok(())
28727    }
28728
28729    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
28730        // JSON path union: [key1, key2, ...]
28731        self.write("[");
28732        for (i, expr) in e.expressions.iter().enumerate() {
28733            if i > 0 {
28734                self.write(", ");
28735            }
28736            self.generate_expression(expr)?;
28737        }
28738        self.write("]");
28739        Ok(())
28740    }
28741
28742    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
28743        // JSON_REMOVE(this, path1, path2, ...)
28744        self.write_keyword("JSON_REMOVE");
28745        self.write("(");
28746        self.generate_expression(&e.this)?;
28747        for expr in &e.expressions {
28748            self.write(", ");
28749            self.generate_expression(expr)?;
28750        }
28751        self.write(")");
28752        Ok(())
28753    }
28754
28755    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
28756        // COLUMNS(col1 type, col2 type, ...)
28757        // When pretty printing and content is too wide, format with each column on a separate line
28758        self.write_keyword("COLUMNS");
28759        self.write("(");
28760
28761        if self.config.pretty && !e.expressions.is_empty() {
28762            // First, generate all expressions into strings to check width
28763            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
28764            for expr in &e.expressions {
28765                let mut temp_gen = Generator::with_config(self.config.clone());
28766                temp_gen.generate_expression(expr)?;
28767                expr_strings.push(temp_gen.output);
28768            }
28769
28770            // Check if total width exceeds max_text_width
28771            if self.too_wide(&expr_strings) {
28772                // Pretty print: each column on its own line
28773                self.write_newline();
28774                self.indent_level += 1;
28775                for (i, expr_str) in expr_strings.iter().enumerate() {
28776                    if i > 0 {
28777                        self.write(",");
28778                        self.write_newline();
28779                    }
28780                    self.write_indent();
28781                    self.write(expr_str);
28782                }
28783                self.write_newline();
28784                self.indent_level -= 1;
28785                self.write_indent();
28786            } else {
28787                // Compact: all on one line
28788                for (i, expr_str) in expr_strings.iter().enumerate() {
28789                    if i > 0 {
28790                        self.write(", ");
28791                    }
28792                    self.write(expr_str);
28793                }
28794            }
28795        } else {
28796            // Non-pretty mode: compact format
28797            for (i, expr) in e.expressions.iter().enumerate() {
28798                if i > 0 {
28799                    self.write(", ");
28800                }
28801                self.generate_expression(expr)?;
28802            }
28803        }
28804        self.write(")");
28805        Ok(())
28806    }
28807
28808    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
28809        // JSON_SET(this, path, value, ...)
28810        self.write_keyword("JSON_SET");
28811        self.write("(");
28812        self.generate_expression(&e.this)?;
28813        for expr in &e.expressions {
28814            self.write(", ");
28815            self.generate_expression(expr)?;
28816        }
28817        self.write(")");
28818        Ok(())
28819    }
28820
28821    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
28822        // JSON_STRIP_NULLS(this, expression)
28823        self.write_keyword("JSON_STRIP_NULLS");
28824        self.write("(");
28825        self.generate_expression(&e.this)?;
28826        if let Some(expr) = &e.expression {
28827            self.write(", ");
28828            self.generate_expression(expr)?;
28829        }
28830        self.write(")");
28831        Ok(())
28832    }
28833
28834    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
28835        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
28836        self.write_keyword("JSON_TABLE");
28837        self.write("(");
28838        self.generate_expression(&e.this)?;
28839        if let Some(path) = &e.path {
28840            self.write(", ");
28841            self.generate_expression(path)?;
28842        }
28843        if let Some(error_handling) = &e.error_handling {
28844            self.write_space();
28845            self.generate_expression(error_handling)?;
28846        }
28847        if let Some(empty_handling) = &e.empty_handling {
28848            self.write_space();
28849            self.generate_expression(empty_handling)?;
28850        }
28851        if let Some(schema) = &e.schema {
28852            self.write_space();
28853            self.generate_expression(schema)?;
28854        }
28855        self.write(")");
28856        Ok(())
28857    }
28858
28859    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
28860        // JSON_TYPE(this)
28861        self.write_keyword("JSON_TYPE");
28862        self.write("(");
28863        self.generate_expression(&e.this)?;
28864        self.write(")");
28865        Ok(())
28866    }
28867
28868    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
28869        // JSON_VALUE(this, path RETURNING type ON condition)
28870        self.write_keyword("JSON_VALUE");
28871        self.write("(");
28872        self.generate_expression(&e.this)?;
28873        if let Some(path) = &e.path {
28874            self.write(", ");
28875            self.generate_expression(path)?;
28876        }
28877        if let Some(returning) = &e.returning {
28878            self.write_space();
28879            self.write_keyword("RETURNING");
28880            self.write_space();
28881            self.generate_expression(returning)?;
28882        }
28883        if let Some(on_condition) = &e.on_condition {
28884            self.write_space();
28885            self.generate_expression(on_condition)?;
28886        }
28887        self.write(")");
28888        Ok(())
28889    }
28890
28891    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
28892        // JSON_VALUE_ARRAY(this)
28893        self.write_keyword("JSON_VALUE_ARRAY");
28894        self.write("(");
28895        self.generate_expression(&e.this)?;
28896        self.write(")");
28897        Ok(())
28898    }
28899
28900    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
28901        // JAROWINKLER_SIMILARITY(str1, str2)
28902        self.write_keyword("JAROWINKLER_SIMILARITY");
28903        self.write("(");
28904        self.generate_expression(&e.this)?;
28905        self.write(", ");
28906        self.generate_expression(&e.expression)?;
28907        self.write(")");
28908        Ok(())
28909    }
28910
28911    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
28912        // Python: this(expressions)
28913        self.generate_expression(&e.this)?;
28914        self.write("(");
28915        for (i, expr) in e.expressions.iter().enumerate() {
28916            if i > 0 {
28917                self.write(", ");
28918            }
28919            self.generate_expression(expr)?;
28920        }
28921        self.write(")");
28922        Ok(())
28923    }
28924
28925    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
28926        // Python: {no}{local}{dual}{before}{after}JOURNAL
28927        if e.no.is_some() {
28928            self.write_keyword("NO ");
28929        }
28930        if let Some(local) = &e.local {
28931            self.generate_expression(local)?;
28932            self.write_space();
28933        }
28934        if e.dual.is_some() {
28935            self.write_keyword("DUAL ");
28936        }
28937        if e.before.is_some() {
28938            self.write_keyword("BEFORE ");
28939        }
28940        if e.after.is_some() {
28941            self.write_keyword("AFTER ");
28942        }
28943        self.write_keyword("JOURNAL");
28944        Ok(())
28945    }
28946
28947    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
28948        // LANGUAGE language_name
28949        self.write_keyword("LANGUAGE");
28950        self.write_space();
28951        self.generate_expression(&e.this)?;
28952        Ok(())
28953    }
28954
28955    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
28956        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
28957        if e.view.is_some() {
28958            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
28959            self.write_keyword("LATERAL VIEW");
28960            if e.outer.is_some() {
28961                self.write_space();
28962                self.write_keyword("OUTER");
28963            }
28964            self.write_space();
28965            self.generate_expression(&e.this)?;
28966            if let Some(alias) = &e.alias {
28967                self.write_space();
28968                self.write(alias);
28969            }
28970        } else {
28971            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
28972            self.write_keyword("LATERAL");
28973            self.write_space();
28974            self.generate_expression(&e.this)?;
28975            if e.ordinality.is_some() {
28976                self.write_space();
28977                self.write_keyword("WITH ORDINALITY");
28978            }
28979            if let Some(alias) = &e.alias {
28980                self.write_space();
28981                self.write_keyword("AS");
28982                self.write_space();
28983                self.write(alias);
28984                if !e.column_aliases.is_empty() {
28985                    self.write("(");
28986                    for (i, col) in e.column_aliases.iter().enumerate() {
28987                        if i > 0 {
28988                            self.write(", ");
28989                        }
28990                        self.write(col);
28991                    }
28992                    self.write(")");
28993                }
28994            }
28995        }
28996        Ok(())
28997    }
28998
28999    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
29000        // Python: LIKE this [options]
29001        self.write_keyword("LIKE");
29002        self.write_space();
29003        self.generate_expression(&e.this)?;
29004        for expr in &e.expressions {
29005            self.write_space();
29006            self.generate_expression(expr)?;
29007        }
29008        Ok(())
29009    }
29010
29011    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
29012        self.write_keyword("LIMIT");
29013        self.write_space();
29014        self.write_limit_expr(&e.this)?;
29015        if e.percent {
29016            self.write_space();
29017            self.write_keyword("PERCENT");
29018        }
29019        // Emit any comments that were captured from before the LIMIT keyword
29020        for comment in &e.comments {
29021            self.write(" ");
29022            self.write_formatted_comment(comment);
29023        }
29024        Ok(())
29025    }
29026
29027    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
29028        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
29029        if e.percent.is_some() {
29030            self.write_keyword(" PERCENT");
29031        }
29032        if e.rows.is_some() {
29033            self.write_keyword(" ROWS");
29034        }
29035        if e.with_ties.is_some() {
29036            self.write_keyword(" WITH TIES");
29037        } else if e.rows.is_some() {
29038            self.write_keyword(" ONLY");
29039        }
29040        Ok(())
29041    }
29042
29043    fn generate_list(&mut self, e: &List) -> Result<()> {
29044        use crate::dialects::DialectType;
29045        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
29046
29047        // Check if this is a subquery-based list (LIST(SELECT ...))
29048        if e.expressions.len() == 1 {
29049            if let Expression::Select(_) = &e.expressions[0] {
29050                self.write_keyword("LIST");
29051                self.write("(");
29052                self.generate_expression(&e.expressions[0])?;
29053                self.write(")");
29054                return Ok(());
29055            }
29056        }
29057
29058        // For Materialize, output as LIST[expr, expr, ...]
29059        if is_materialize {
29060            self.write_keyword("LIST");
29061            self.write("[");
29062            for (i, expr) in e.expressions.iter().enumerate() {
29063                if i > 0 {
29064                    self.write(", ");
29065                }
29066                self.generate_expression(expr)?;
29067            }
29068            self.write("]");
29069        } else {
29070            // For other dialects, output as LIST(expr, expr, ...)
29071            self.write_keyword("LIST");
29072            self.write("(");
29073            for (i, expr) in e.expressions.iter().enumerate() {
29074                if i > 0 {
29075                    self.write(", ");
29076                }
29077                self.generate_expression(expr)?;
29078            }
29079            self.write(")");
29080        }
29081        Ok(())
29082    }
29083
29084    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
29085        // Check if this is a subquery-based map (MAP(SELECT ...))
29086        if let Expression::Select(_) = &*e.this {
29087            self.write_keyword("MAP");
29088            self.write("(");
29089            self.generate_expression(&e.this)?;
29090            self.write(")");
29091            return Ok(());
29092        }
29093
29094        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
29095
29096        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
29097        self.write_keyword("MAP");
29098        if is_duckdb {
29099            self.write(" {");
29100        } else {
29101            self.write("[");
29102        }
29103        if let Expression::Struct(s) = &*e.this {
29104            for (i, (_, expr)) in s.fields.iter().enumerate() {
29105                if i > 0 {
29106                    self.write(", ");
29107                }
29108                if let Expression::PropertyEQ(op) = expr {
29109                    self.generate_expression(&op.left)?;
29110                    if is_duckdb {
29111                        self.write(": ");
29112                    } else {
29113                        self.write(" => ");
29114                    }
29115                    self.generate_expression(&op.right)?;
29116                } else {
29117                    self.generate_expression(expr)?;
29118                }
29119            }
29120        }
29121        if is_duckdb {
29122            self.write("}");
29123        } else {
29124            self.write("]");
29125        }
29126        Ok(())
29127    }
29128
29129    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
29130        // Python: LOCALTIME or LOCALTIME(precision)
29131        self.write_keyword("LOCALTIME");
29132        if let Some(precision) = &e.this {
29133            self.write("(");
29134            self.generate_expression(precision)?;
29135            self.write(")");
29136        }
29137        Ok(())
29138    }
29139
29140    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
29141        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
29142        self.write_keyword("LOCALTIMESTAMP");
29143        if let Some(precision) = &e.this {
29144            self.write("(");
29145            self.generate_expression(precision)?;
29146            self.write(")");
29147        }
29148        Ok(())
29149    }
29150
29151    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
29152        // LOCATION 'path'
29153        self.write_keyword("LOCATION");
29154        self.write_space();
29155        self.generate_expression(&e.this)?;
29156        Ok(())
29157    }
29158
29159    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
29160        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
29161        if e.update.is_some() {
29162            if e.key.is_some() {
29163                self.write_keyword("FOR NO KEY UPDATE");
29164            } else {
29165                self.write_keyword("FOR UPDATE");
29166            }
29167        } else {
29168            if e.key.is_some() {
29169                self.write_keyword("FOR KEY SHARE");
29170            } else {
29171                self.write_keyword("FOR SHARE");
29172            }
29173        }
29174        if !e.expressions.is_empty() {
29175            self.write_keyword(" OF ");
29176            for (i, expr) in e.expressions.iter().enumerate() {
29177                if i > 0 {
29178                    self.write(", ");
29179                }
29180                self.generate_expression(expr)?;
29181            }
29182        }
29183        // Handle wait option following Python sqlglot convention:
29184        // - Boolean(true) -> NOWAIT
29185        // - Boolean(false) -> SKIP LOCKED
29186        // - Literal (number) -> WAIT n
29187        if let Some(wait) = &e.wait {
29188            match wait.as_ref() {
29189                Expression::Boolean(b) => {
29190                    if b.value {
29191                        self.write_keyword(" NOWAIT");
29192                    } else {
29193                        self.write_keyword(" SKIP LOCKED");
29194                    }
29195                }
29196                _ => {
29197                    // It's a literal (number), output WAIT n
29198                    self.write_keyword(" WAIT ");
29199                    self.generate_expression(wait)?;
29200                }
29201            }
29202        }
29203        Ok(())
29204    }
29205
29206    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
29207        // LOCK property
29208        self.write_keyword("LOCK");
29209        self.write_space();
29210        self.generate_expression(&e.this)?;
29211        Ok(())
29212    }
29213
29214    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
29215        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
29216        self.write_keyword("LOCKING");
29217        self.write_space();
29218        self.write(&e.kind);
29219        if let Some(this) = &e.this {
29220            self.write_space();
29221            self.generate_expression(this)?;
29222        }
29223        if let Some(for_or_in) = &e.for_or_in {
29224            self.write_space();
29225            self.generate_expression(for_or_in)?;
29226        }
29227        if let Some(lock_type) = &e.lock_type {
29228            self.write_space();
29229            self.generate_expression(lock_type)?;
29230        }
29231        if e.override_.is_some() {
29232            self.write_keyword(" OVERRIDE");
29233        }
29234        Ok(())
29235    }
29236
29237    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
29238        // this expression
29239        self.generate_expression(&e.this)?;
29240        self.write_space();
29241        self.generate_expression(&e.expression)?;
29242        Ok(())
29243    }
29244
29245    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
29246        // [NO] LOG
29247        if e.no.is_some() {
29248            self.write_keyword("NO ");
29249        }
29250        self.write_keyword("LOG");
29251        Ok(())
29252    }
29253
29254    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
29255        // MD5(this, expressions...)
29256        self.write_keyword("MD5");
29257        self.write("(");
29258        self.generate_expression(&e.this)?;
29259        for expr in &e.expressions {
29260            self.write(", ");
29261            self.generate_expression(expr)?;
29262        }
29263        self.write(")");
29264        Ok(())
29265    }
29266
29267    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
29268        // ML.FORECAST(model, [params])
29269        self.write_keyword("ML.FORECAST");
29270        self.write("(");
29271        self.generate_expression(&e.this)?;
29272        if let Some(expression) = &e.expression {
29273            self.write(", ");
29274            self.generate_expression(expression)?;
29275        }
29276        if let Some(params) = &e.params_struct {
29277            self.write(", ");
29278            self.generate_expression(params)?;
29279        }
29280        self.write(")");
29281        Ok(())
29282    }
29283
29284    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
29285        // ML.TRANSLATE(model, input, [params])
29286        self.write_keyword("ML.TRANSLATE");
29287        self.write("(");
29288        self.generate_expression(&e.this)?;
29289        self.write(", ");
29290        self.generate_expression(&e.expression)?;
29291        if let Some(params) = &e.params_struct {
29292            self.write(", ");
29293            self.generate_expression(params)?;
29294        }
29295        self.write(")");
29296        Ok(())
29297    }
29298
29299    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
29300        // MAKE_INTERVAL(years => x, months => y, ...)
29301        self.write_keyword("MAKE_INTERVAL");
29302        self.write("(");
29303        let mut first = true;
29304        if let Some(year) = &e.year {
29305            self.write("years => ");
29306            self.generate_expression(year)?;
29307            first = false;
29308        }
29309        if let Some(month) = &e.month {
29310            if !first {
29311                self.write(", ");
29312            }
29313            self.write("months => ");
29314            self.generate_expression(month)?;
29315            first = false;
29316        }
29317        if let Some(week) = &e.week {
29318            if !first {
29319                self.write(", ");
29320            }
29321            self.write("weeks => ");
29322            self.generate_expression(week)?;
29323            first = false;
29324        }
29325        if let Some(day) = &e.day {
29326            if !first {
29327                self.write(", ");
29328            }
29329            self.write("days => ");
29330            self.generate_expression(day)?;
29331            first = false;
29332        }
29333        if let Some(hour) = &e.hour {
29334            if !first {
29335                self.write(", ");
29336            }
29337            self.write("hours => ");
29338            self.generate_expression(hour)?;
29339            first = false;
29340        }
29341        if let Some(minute) = &e.minute {
29342            if !first {
29343                self.write(", ");
29344            }
29345            self.write("mins => ");
29346            self.generate_expression(minute)?;
29347            first = false;
29348        }
29349        if let Some(second) = &e.second {
29350            if !first {
29351                self.write(", ");
29352            }
29353            self.write("secs => ");
29354            self.generate_expression(second)?;
29355        }
29356        self.write(")");
29357        Ok(())
29358    }
29359
29360    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
29361        // MANHATTAN_DISTANCE(vector1, vector2)
29362        self.write_keyword("MANHATTAN_DISTANCE");
29363        self.write("(");
29364        self.generate_expression(&e.this)?;
29365        self.write(", ");
29366        self.generate_expression(&e.expression)?;
29367        self.write(")");
29368        Ok(())
29369    }
29370
29371    fn generate_map(&mut self, e: &Map) -> Result<()> {
29372        // MAP(key1, value1, key2, value2, ...)
29373        self.write_keyword("MAP");
29374        self.write("(");
29375        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
29376            if i > 0 {
29377                self.write(", ");
29378            }
29379            self.generate_expression(key)?;
29380            self.write(", ");
29381            self.generate_expression(value)?;
29382        }
29383        self.write(")");
29384        Ok(())
29385    }
29386
29387    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
29388        // MAP_CAT(map1, map2)
29389        self.write_keyword("MAP_CAT");
29390        self.write("(");
29391        self.generate_expression(&e.this)?;
29392        self.write(", ");
29393        self.generate_expression(&e.expression)?;
29394        self.write(")");
29395        Ok(())
29396    }
29397
29398    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
29399        // MAP_DELETE(map, key1, key2, ...)
29400        self.write_keyword("MAP_DELETE");
29401        self.write("(");
29402        self.generate_expression(&e.this)?;
29403        for expr in &e.expressions {
29404            self.write(", ");
29405            self.generate_expression(expr)?;
29406        }
29407        self.write(")");
29408        Ok(())
29409    }
29410
29411    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
29412        // MAP_INSERT(map, key, value, [update_flag])
29413        self.write_keyword("MAP_INSERT");
29414        self.write("(");
29415        self.generate_expression(&e.this)?;
29416        if let Some(key) = &e.key {
29417            self.write(", ");
29418            self.generate_expression(key)?;
29419        }
29420        if let Some(value) = &e.value {
29421            self.write(", ");
29422            self.generate_expression(value)?;
29423        }
29424        if let Some(update_flag) = &e.update_flag {
29425            self.write(", ");
29426            self.generate_expression(update_flag)?;
29427        }
29428        self.write(")");
29429        Ok(())
29430    }
29431
29432    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
29433        // MAP_PICK(map, key1, key2, ...)
29434        self.write_keyword("MAP_PICK");
29435        self.write("(");
29436        self.generate_expression(&e.this)?;
29437        for expr in &e.expressions {
29438            self.write(", ");
29439            self.generate_expression(expr)?;
29440        }
29441        self.write(")");
29442        Ok(())
29443    }
29444
29445    fn generate_masking_policy_column_constraint(
29446        &mut self,
29447        e: &MaskingPolicyColumnConstraint,
29448    ) -> Result<()> {
29449        // Python: MASKING POLICY name [USING (cols)]
29450        self.write_keyword("MASKING POLICY");
29451        self.write_space();
29452        self.generate_expression(&e.this)?;
29453        if !e.expressions.is_empty() {
29454            self.write_keyword(" USING");
29455            self.write(" (");
29456            for (i, expr) in e.expressions.iter().enumerate() {
29457                if i > 0 {
29458                    self.write(", ");
29459                }
29460                self.generate_expression(expr)?;
29461            }
29462            self.write(")");
29463        }
29464        Ok(())
29465    }
29466
29467    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
29468        if matches!(
29469            self.config.dialect,
29470            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
29471        ) {
29472            if e.expressions.len() > 1 {
29473                self.write("(");
29474            }
29475            for (i, expr) in e.expressions.iter().enumerate() {
29476                if i > 0 {
29477                    self.write_keyword(" OR ");
29478                }
29479                self.generate_expression(expr)?;
29480                self.write_space();
29481                self.write("@@");
29482                self.write_space();
29483                self.generate_expression(&e.this)?;
29484            }
29485            if e.expressions.len() > 1 {
29486                self.write(")");
29487            }
29488            return Ok(());
29489        }
29490
29491        // MATCH(columns) AGAINST(expr [modifier])
29492        self.write_keyword("MATCH");
29493        self.write("(");
29494        for (i, expr) in e.expressions.iter().enumerate() {
29495            if i > 0 {
29496                self.write(", ");
29497            }
29498            self.generate_expression(expr)?;
29499        }
29500        self.write(")");
29501        self.write_keyword(" AGAINST");
29502        self.write("(");
29503        self.generate_expression(&e.this)?;
29504        if let Some(modifier) = &e.modifier {
29505            self.write_space();
29506            self.generate_expression(modifier)?;
29507        }
29508        self.write(")");
29509        Ok(())
29510    }
29511
29512    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
29513        // Python: [window_frame] this
29514        if let Some(window_frame) = &e.window_frame {
29515            self.write(&format!("{:?}", window_frame).to_uppercase());
29516            self.write_space();
29517        }
29518        self.generate_expression(&e.this)?;
29519        Ok(())
29520    }
29521
29522    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
29523        // MATERIALIZED [this]
29524        self.write_keyword("MATERIALIZED");
29525        if let Some(this) = &e.this {
29526            self.write_space();
29527            self.generate_expression(this)?;
29528        }
29529        Ok(())
29530    }
29531
29532    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
29533        // MERGE INTO target USING source ON condition WHEN ...
29534        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
29535        if let Some(with_) = &e.with_ {
29536            self.generate_expression(with_)?;
29537            self.write_space();
29538        }
29539        self.write_keyword("MERGE INTO");
29540        self.write_space();
29541        self.generate_expression(&e.this)?;
29542
29543        // USING clause - newline before in pretty mode
29544        if self.config.pretty {
29545            self.write_newline();
29546            self.write_indent();
29547        } else {
29548            self.write_space();
29549        }
29550        self.write_keyword("USING");
29551        self.write_space();
29552        self.generate_expression(&e.using)?;
29553
29554        // ON clause - newline before in pretty mode
29555        if let Some(on) = &e.on {
29556            if self.config.pretty {
29557                self.write_newline();
29558                self.write_indent();
29559            } else {
29560                self.write_space();
29561            }
29562            self.write_keyword("ON");
29563            self.write_space();
29564            self.generate_expression(on)?;
29565        }
29566        // DuckDB USING (key_columns) clause
29567        if let Some(using_cond) = &e.using_cond {
29568            self.write_space();
29569            self.write_keyword("USING");
29570            self.write_space();
29571            self.write("(");
29572            // using_cond is a Tuple containing the column identifiers
29573            if let Expression::Tuple(tuple) = using_cond.as_ref() {
29574                for (i, col) in tuple.expressions.iter().enumerate() {
29575                    if i > 0 {
29576                        self.write(", ");
29577                    }
29578                    self.generate_expression(col)?;
29579                }
29580            } else {
29581                self.generate_expression(using_cond)?;
29582            }
29583            self.write(")");
29584        }
29585        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
29586        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
29587        if matches!(
29588            self.config.dialect,
29589            Some(crate::DialectType::PostgreSQL)
29590                | Some(crate::DialectType::Redshift)
29591                | Some(crate::DialectType::Trino)
29592                | Some(crate::DialectType::Presto)
29593                | Some(crate::DialectType::Athena)
29594        ) {
29595            let mut names = Vec::new();
29596            match e.this.as_ref() {
29597                Expression::Alias(a) => {
29598                    // e.g., "x AS z" -> strip both "x" and "z"
29599                    if let Expression::Table(t) = &a.this {
29600                        names.push(t.name.name.clone());
29601                    } else if let Expression::Identifier(id) = &a.this {
29602                        names.push(id.name.clone());
29603                    }
29604                    names.push(a.alias.name.clone());
29605                }
29606                Expression::Table(t) => {
29607                    names.push(t.name.name.clone());
29608                }
29609                Expression::Identifier(id) => {
29610                    names.push(id.name.clone());
29611                }
29612                _ => {}
29613            }
29614            self.merge_strip_qualifiers = names;
29615        }
29616
29617        // WHEN clauses - newline before each in pretty mode
29618        if let Some(whens) = &e.whens {
29619            if self.config.pretty {
29620                self.write_newline();
29621                self.write_indent();
29622            } else {
29623                self.write_space();
29624            }
29625            self.generate_expression(whens)?;
29626        }
29627
29628        // Restore merge_strip_qualifiers
29629        self.merge_strip_qualifiers = saved_merge_strip;
29630
29631        // OUTPUT/RETURNING clause - newline before in pretty mode
29632        if let Some(returning) = &e.returning {
29633            if self.config.pretty {
29634                self.write_newline();
29635                self.write_indent();
29636            } else {
29637                self.write_space();
29638            }
29639            self.generate_expression(returning)?;
29640        }
29641        Ok(())
29642    }
29643
29644    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
29645        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
29646        if e.no.is_some() {
29647            self.write_keyword("NO MERGEBLOCKRATIO");
29648        } else if e.default.is_some() {
29649            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
29650        } else {
29651            self.write_keyword("MERGEBLOCKRATIO");
29652            self.write("=");
29653            if let Some(this) = &e.this {
29654                self.generate_expression(this)?;
29655            }
29656            if e.percent.is_some() {
29657                self.write_keyword(" PERCENT");
29658            }
29659        }
29660        Ok(())
29661    }
29662
29663    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
29664        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
29665        self.write_keyword("TTL");
29666        let pretty_clickhouse = self.config.pretty
29667            && matches!(
29668                self.config.dialect,
29669                Some(crate::dialects::DialectType::ClickHouse)
29670            );
29671
29672        if pretty_clickhouse {
29673            self.write_newline();
29674            self.indent_level += 1;
29675            for (i, expr) in e.expressions.iter().enumerate() {
29676                if i > 0 {
29677                    self.write(",");
29678                    self.write_newline();
29679                }
29680                self.write_indent();
29681                self.generate_expression(expr)?;
29682            }
29683            self.indent_level -= 1;
29684        } else {
29685            self.write_space();
29686            for (i, expr) in e.expressions.iter().enumerate() {
29687                if i > 0 {
29688                    self.write(", ");
29689                }
29690                self.generate_expression(expr)?;
29691            }
29692        }
29693
29694        if let Some(where_) = &e.where_ {
29695            if pretty_clickhouse {
29696                self.write_newline();
29697                if let Expression::Where(w) = where_.as_ref() {
29698                    self.write_indent();
29699                    self.write_keyword("WHERE");
29700                    self.write_newline();
29701                    self.indent_level += 1;
29702                    self.write_indent();
29703                    self.generate_expression(&w.this)?;
29704                    self.indent_level -= 1;
29705                } else {
29706                    self.write_indent();
29707                    self.generate_expression(where_)?;
29708                }
29709            } else {
29710                self.write_space();
29711                self.generate_expression(where_)?;
29712            }
29713        }
29714        if let Some(group) = &e.group {
29715            if pretty_clickhouse {
29716                self.write_newline();
29717                if let Expression::Group(g) = group.as_ref() {
29718                    self.write_indent();
29719                    self.write_keyword("GROUP BY");
29720                    self.write_newline();
29721                    self.indent_level += 1;
29722                    for (i, expr) in g.expressions.iter().enumerate() {
29723                        if i > 0 {
29724                            self.write(",");
29725                            self.write_newline();
29726                        }
29727                        self.write_indent();
29728                        self.generate_expression(expr)?;
29729                    }
29730                    self.indent_level -= 1;
29731                } else {
29732                    self.write_indent();
29733                    self.generate_expression(group)?;
29734                }
29735            } else {
29736                self.write_space();
29737                self.generate_expression(group)?;
29738            }
29739        }
29740        if let Some(aggregates) = &e.aggregates {
29741            if pretty_clickhouse {
29742                self.write_newline();
29743                self.write_indent();
29744                self.write_keyword("SET");
29745                self.write_newline();
29746                self.indent_level += 1;
29747                if let Expression::Tuple(t) = aggregates.as_ref() {
29748                    for (i, agg) in t.expressions.iter().enumerate() {
29749                        if i > 0 {
29750                            self.write(",");
29751                            self.write_newline();
29752                        }
29753                        self.write_indent();
29754                        self.generate_expression(agg)?;
29755                    }
29756                } else {
29757                    self.write_indent();
29758                    self.generate_expression(aggregates)?;
29759                }
29760                self.indent_level -= 1;
29761            } else {
29762                self.write_space();
29763                self.write_keyword("SET");
29764                self.write_space();
29765                self.generate_expression(aggregates)?;
29766            }
29767        }
29768        Ok(())
29769    }
29770
29771    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
29772        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
29773        self.generate_expression(&e.this)?;
29774        if e.delete.is_some() {
29775            self.write_keyword(" DELETE");
29776        }
29777        if let Some(recompress) = &e.recompress {
29778            self.write_keyword(" RECOMPRESS ");
29779            self.generate_expression(recompress)?;
29780        }
29781        if let Some(to_disk) = &e.to_disk {
29782            self.write_keyword(" TO DISK ");
29783            self.generate_expression(to_disk)?;
29784        }
29785        if let Some(to_volume) = &e.to_volume {
29786            self.write_keyword(" TO VOLUME ");
29787            self.generate_expression(to_volume)?;
29788        }
29789        Ok(())
29790    }
29791
29792    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
29793        // MINHASH(this, expressions...)
29794        self.write_keyword("MINHASH");
29795        self.write("(");
29796        self.generate_expression(&e.this)?;
29797        for expr in &e.expressions {
29798            self.write(", ");
29799            self.generate_expression(expr)?;
29800        }
29801        self.write(")");
29802        Ok(())
29803    }
29804
29805    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
29806        // model!attribute - Snowflake syntax
29807        self.generate_expression(&e.this)?;
29808        self.write("!");
29809        self.generate_expression(&e.expression)?;
29810        Ok(())
29811    }
29812
29813    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
29814        // MONTHNAME(this)
29815        self.write_keyword("MONTHNAME");
29816        self.write("(");
29817        self.generate_expression(&e.this)?;
29818        self.write(")");
29819        Ok(())
29820    }
29821
29822    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
29823        // Output leading comments
29824        for comment in &e.leading_comments {
29825            self.write_formatted_comment(comment);
29826            if self.config.pretty {
29827                self.write_newline();
29828                self.write_indent();
29829            } else {
29830                self.write_space();
29831            }
29832        }
29833        // Python: INSERT kind expressions source
29834        self.write_keyword("INSERT");
29835        self.write_space();
29836        self.write(&e.kind);
29837        if self.config.pretty {
29838            self.indent_level += 1;
29839            for expr in &e.expressions {
29840                self.write_newline();
29841                self.write_indent();
29842                self.generate_expression(expr)?;
29843            }
29844            self.indent_level -= 1;
29845        } else {
29846            for expr in &e.expressions {
29847                self.write_space();
29848                self.generate_expression(expr)?;
29849            }
29850        }
29851        if let Some(source) = &e.source {
29852            if self.config.pretty {
29853                self.write_newline();
29854                self.write_indent();
29855            } else {
29856                self.write_space();
29857            }
29858            self.generate_expression(source)?;
29859        }
29860        Ok(())
29861    }
29862
29863    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
29864        // Python: NEXT VALUE FOR this [OVER (order)]
29865        self.write_keyword("NEXT VALUE FOR");
29866        self.write_space();
29867        self.generate_expression(&e.this)?;
29868        if let Some(order) = &e.order {
29869            self.write_space();
29870            self.write_keyword("OVER");
29871            self.write(" (");
29872            self.generate_expression(order)?;
29873            self.write(")");
29874        }
29875        Ok(())
29876    }
29877
29878    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
29879        // NORMAL(mean, stddev, gen)
29880        self.write_keyword("NORMAL");
29881        self.write("(");
29882        self.generate_expression(&e.this)?;
29883        if let Some(stddev) = &e.stddev {
29884            self.write(", ");
29885            self.generate_expression(stddev)?;
29886        }
29887        if let Some(gen) = &e.gen {
29888            self.write(", ");
29889            self.generate_expression(gen)?;
29890        }
29891        self.write(")");
29892        Ok(())
29893    }
29894
29895    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
29896        // NORMALIZE(this, form) or CASEFOLD version
29897        if e.is_casefold.is_some() {
29898            self.write_keyword("NORMALIZE_AND_CASEFOLD");
29899        } else {
29900            self.write_keyword("NORMALIZE");
29901        }
29902        self.write("(");
29903        self.generate_expression(&e.this)?;
29904        if let Some(form) = &e.form {
29905            self.write(", ");
29906            self.generate_expression(form)?;
29907        }
29908        self.write(")");
29909        Ok(())
29910    }
29911
29912    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
29913        // Python: [NOT ]NULL
29914        if e.allow_null.is_none() {
29915            self.write_keyword("NOT ");
29916        }
29917        self.write_keyword("NULL");
29918        Ok(())
29919    }
29920
29921    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
29922        // NULLIF(this, expression)
29923        self.write_keyword("NULLIF");
29924        self.write("(");
29925        self.generate_expression(&e.this)?;
29926        self.write(", ");
29927        self.generate_expression(&e.expression)?;
29928        self.write(")");
29929        Ok(())
29930    }
29931
29932    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
29933        // FORMAT(this, format, culture)
29934        self.write_keyword("FORMAT");
29935        self.write("(");
29936        self.generate_expression(&e.this)?;
29937        self.write(", '");
29938        self.write(&e.format);
29939        self.write("'");
29940        if let Some(culture) = &e.culture {
29941            self.write(", ");
29942            self.generate_expression(culture)?;
29943        }
29944        self.write(")");
29945        Ok(())
29946    }
29947
29948    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
29949        // OBJECT_AGG(key, value)
29950        self.write_keyword("OBJECT_AGG");
29951        self.write("(");
29952        self.generate_expression(&e.this)?;
29953        self.write(", ");
29954        self.generate_expression(&e.expression)?;
29955        self.write(")");
29956        Ok(())
29957    }
29958
29959    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
29960        // Python: Just returns the name
29961        self.generate_expression(&e.this)?;
29962        Ok(())
29963    }
29964
29965    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
29966        // OBJECT_INSERT(obj, key, value, [update_flag])
29967        self.write_keyword("OBJECT_INSERT");
29968        self.write("(");
29969        self.generate_expression(&e.this)?;
29970        if let Some(key) = &e.key {
29971            self.write(", ");
29972            self.generate_expression(key)?;
29973        }
29974        if let Some(value) = &e.value {
29975            self.write(", ");
29976            self.generate_expression(value)?;
29977        }
29978        if let Some(update_flag) = &e.update_flag {
29979            self.write(", ");
29980            self.generate_expression(update_flag)?;
29981        }
29982        self.write(")");
29983        Ok(())
29984    }
29985
29986    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
29987        // OFFSET value [ROW|ROWS]
29988        self.write_keyword("OFFSET");
29989        self.write_space();
29990        self.generate_expression(&e.this)?;
29991        // Output ROWS keyword only for TSQL/Oracle targets
29992        if e.rows == Some(true)
29993            && matches!(
29994                self.config.dialect,
29995                Some(crate::dialects::DialectType::TSQL)
29996                    | Some(crate::dialects::DialectType::Oracle)
29997            )
29998        {
29999            self.write_space();
30000            self.write_keyword("ROWS");
30001        }
30002        Ok(())
30003    }
30004
30005    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
30006        // QUALIFY condition (Snowflake/BigQuery)
30007        self.write_keyword("QUALIFY");
30008        self.write_space();
30009        self.generate_expression(&e.this)?;
30010        Ok(())
30011    }
30012
30013    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
30014        // ON CLUSTER cluster_name
30015        self.write_keyword("ON CLUSTER");
30016        self.write_space();
30017        self.generate_expression(&e.this)?;
30018        Ok(())
30019    }
30020
30021    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
30022        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
30023        self.write_keyword("ON COMMIT");
30024        if e.delete.is_some() {
30025            self.write_keyword(" DELETE ROWS");
30026        } else {
30027            self.write_keyword(" PRESERVE ROWS");
30028        }
30029        Ok(())
30030    }
30031
30032    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
30033        // Python: error/empty/null handling
30034        if let Some(empty) = &e.empty {
30035            self.generate_expression(empty)?;
30036            self.write_keyword(" ON EMPTY");
30037        }
30038        if let Some(error) = &e.error {
30039            if e.empty.is_some() {
30040                self.write_space();
30041            }
30042            self.generate_expression(error)?;
30043            self.write_keyword(" ON ERROR");
30044        }
30045        if let Some(null) = &e.null {
30046            if e.empty.is_some() || e.error.is_some() {
30047                self.write_space();
30048            }
30049            self.generate_expression(null)?;
30050            self.write_keyword(" ON NULL");
30051        }
30052        Ok(())
30053    }
30054
30055    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
30056        // Materialize doesn't support ON CONFLICT - skip entirely
30057        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
30058            return Ok(());
30059        }
30060        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
30061        if e.duplicate.is_some() {
30062            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
30063            self.write_keyword("ON DUPLICATE KEY UPDATE");
30064            for (i, expr) in e.expressions.iter().enumerate() {
30065                if i > 0 {
30066                    self.write(",");
30067                }
30068                self.write_space();
30069                self.generate_expression(expr)?;
30070            }
30071            return Ok(());
30072        } else {
30073            self.write_keyword("ON CONFLICT");
30074        }
30075        if let Some(constraint) = &e.constraint {
30076            self.write_keyword(" ON CONSTRAINT ");
30077            self.generate_expression(constraint)?;
30078        }
30079        if let Some(conflict_keys) = &e.conflict_keys {
30080            // conflict_keys can be a Tuple containing expressions
30081            if let Expression::Tuple(t) = conflict_keys.as_ref() {
30082                self.write("(");
30083                for (i, expr) in t.expressions.iter().enumerate() {
30084                    if i > 0 {
30085                        self.write(", ");
30086                    }
30087                    self.generate_expression(expr)?;
30088                }
30089                self.write(")");
30090            } else {
30091                self.write("(");
30092                self.generate_expression(conflict_keys)?;
30093                self.write(")");
30094            }
30095        }
30096        if let Some(index_predicate) = &e.index_predicate {
30097            self.write_keyword(" WHERE ");
30098            self.generate_expression(index_predicate)?;
30099        }
30100        if let Some(action) = &e.action {
30101            // Check if action is "NOTHING" or an UPDATE set
30102            if let Expression::Identifier(id) = action.as_ref() {
30103                if id.name == "NOTHING" || id.name.to_uppercase() == "NOTHING" {
30104                    self.write_keyword(" DO NOTHING");
30105                } else {
30106                    self.write_keyword(" DO ");
30107                    self.generate_expression(action)?;
30108                }
30109            } else if let Expression::Tuple(t) = action.as_ref() {
30110                // DO UPDATE SET col1 = val1, col2 = val2
30111                self.write_keyword(" DO UPDATE SET ");
30112                for (i, expr) in t.expressions.iter().enumerate() {
30113                    if i > 0 {
30114                        self.write(", ");
30115                    }
30116                    self.generate_expression(expr)?;
30117                }
30118            } else {
30119                self.write_keyword(" DO ");
30120                self.generate_expression(action)?;
30121            }
30122        }
30123        // WHERE clause for the UPDATE action
30124        if let Some(where_) = &e.where_ {
30125            self.write_keyword(" WHERE ");
30126            self.generate_expression(where_)?;
30127        }
30128        Ok(())
30129    }
30130
30131    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
30132        // ON property_value
30133        self.write_keyword("ON");
30134        self.write_space();
30135        self.generate_expression(&e.this)?;
30136        Ok(())
30137    }
30138
30139    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
30140        // Python: this expression (e.g., column opclass)
30141        self.generate_expression(&e.this)?;
30142        self.write_space();
30143        self.generate_expression(&e.expression)?;
30144        Ok(())
30145    }
30146
30147    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
30148        // Python: OPENJSON(this[, path]) [WITH (columns)]
30149        self.write_keyword("OPENJSON");
30150        self.write("(");
30151        self.generate_expression(&e.this)?;
30152        if let Some(path) = &e.path {
30153            self.write(", ");
30154            self.generate_expression(path)?;
30155        }
30156        self.write(")");
30157        if !e.expressions.is_empty() {
30158            self.write_keyword(" WITH");
30159            if self.config.pretty {
30160                self.write(" (\n");
30161                self.indent_level += 2;
30162                for (i, expr) in e.expressions.iter().enumerate() {
30163                    if i > 0 {
30164                        self.write(",\n");
30165                    }
30166                    self.write_indent();
30167                    self.generate_expression(expr)?;
30168                }
30169                self.write("\n");
30170                self.indent_level -= 2;
30171                self.write(")");
30172            } else {
30173                self.write(" (");
30174                for (i, expr) in e.expressions.iter().enumerate() {
30175                    if i > 0 {
30176                        self.write(", ");
30177                    }
30178                    self.generate_expression(expr)?;
30179                }
30180                self.write(")");
30181            }
30182        }
30183        Ok(())
30184    }
30185
30186    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
30187        // Python: this kind [path] [AS JSON]
30188        self.generate_expression(&e.this)?;
30189        self.write_space();
30190        // Use parsed data_type if available, otherwise fall back to kind string
30191        if let Some(ref dt) = e.data_type {
30192            self.generate_data_type(dt)?;
30193        } else if !e.kind.is_empty() {
30194            self.write(&e.kind);
30195        }
30196        if let Some(path) = &e.path {
30197            self.write_space();
30198            self.generate_expression(path)?;
30199        }
30200        if e.as_json.is_some() {
30201            self.write_keyword(" AS JSON");
30202        }
30203        Ok(())
30204    }
30205
30206    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
30207        // this OPERATOR(op) expression
30208        self.generate_expression(&e.this)?;
30209        self.write_space();
30210        if let Some(op) = &e.operator {
30211            self.write_keyword("OPERATOR");
30212            self.write("(");
30213            self.generate_expression(op)?;
30214            self.write(")");
30215        }
30216        // Emit inline comments between OPERATOR() and the RHS
30217        for comment in &e.comments {
30218            self.write_space();
30219            self.write_formatted_comment(comment);
30220        }
30221        self.write_space();
30222        self.generate_expression(&e.expression)?;
30223        Ok(())
30224    }
30225
30226    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
30227        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
30228        self.write_keyword("ORDER BY");
30229        let pretty_clickhouse_single_paren = self.config.pretty
30230            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
30231            && e.expressions.len() == 1
30232            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
30233        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
30234            && e.expressions.len() == 1
30235            && matches!(e.expressions[0].this, Expression::Tuple(_))
30236            && !e.expressions[0].desc
30237            && e.expressions[0].nulls_first.is_none();
30238
30239        if pretty_clickhouse_single_paren {
30240            self.write_space();
30241            if let Expression::Paren(p) = &e.expressions[0].this {
30242                self.write("(");
30243                self.write_newline();
30244                self.indent_level += 1;
30245                self.write_indent();
30246                self.generate_expression(&p.this)?;
30247                self.indent_level -= 1;
30248                self.write_newline();
30249                self.write(")");
30250            }
30251            return Ok(());
30252        }
30253
30254        if clickhouse_single_tuple {
30255            self.write_space();
30256            if let Expression::Tuple(t) = &e.expressions[0].this {
30257                self.write("(");
30258                for (i, expr) in t.expressions.iter().enumerate() {
30259                    if i > 0 {
30260                        self.write(", ");
30261                    }
30262                    self.generate_expression(expr)?;
30263                }
30264                self.write(")");
30265            }
30266            return Ok(());
30267        }
30268
30269        self.write_space();
30270        for (i, ordered) in e.expressions.iter().enumerate() {
30271            if i > 0 {
30272                self.write(", ");
30273            }
30274            self.generate_expression(&ordered.this)?;
30275            if ordered.desc {
30276                self.write_space();
30277                self.write_keyword("DESC");
30278            } else if ordered.explicit_asc {
30279                self.write_space();
30280                self.write_keyword("ASC");
30281            }
30282            if let Some(nulls_first) = ordered.nulls_first {
30283                // In Dremio, NULLS LAST is the default, so skip generating it
30284                let skip_nulls_last =
30285                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
30286                if !skip_nulls_last {
30287                    self.write_space();
30288                    self.write_keyword("NULLS");
30289                    self.write_space();
30290                    if nulls_first {
30291                        self.write_keyword("FIRST");
30292                    } else {
30293                        self.write_keyword("LAST");
30294                    }
30295                }
30296            }
30297        }
30298        Ok(())
30299    }
30300
30301    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
30302        // OUTPUT(model)
30303        self.write_keyword("OUTPUT");
30304        self.write("(");
30305        if self.config.pretty {
30306            self.indent_level += 1;
30307            self.write_newline();
30308            self.write_indent();
30309            self.generate_expression(&e.this)?;
30310            self.indent_level -= 1;
30311            self.write_newline();
30312        } else {
30313            self.generate_expression(&e.this)?;
30314        }
30315        self.write(")");
30316        Ok(())
30317    }
30318
30319    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
30320        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
30321        self.write_keyword("TRUNCATE");
30322        if let Some(this) = &e.this {
30323            self.write_space();
30324            self.generate_expression(this)?;
30325        }
30326        if e.with_count.is_some() {
30327            self.write_keyword(" WITH COUNT");
30328        } else {
30329            self.write_keyword(" WITHOUT COUNT");
30330        }
30331        Ok(())
30332    }
30333
30334    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
30335        // Python: name(expressions)(params)
30336        self.generate_expression(&e.this)?;
30337        self.write("(");
30338        for (i, expr) in e.expressions.iter().enumerate() {
30339            if i > 0 {
30340                self.write(", ");
30341            }
30342            self.generate_expression(expr)?;
30343        }
30344        self.write(")(");
30345        for (i, param) in e.params.iter().enumerate() {
30346            if i > 0 {
30347                self.write(", ");
30348            }
30349            self.generate_expression(param)?;
30350        }
30351        self.write(")");
30352        Ok(())
30353    }
30354
30355    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
30356        // PARSE_DATETIME(format, this) or similar
30357        self.write_keyword("PARSE_DATETIME");
30358        self.write("(");
30359        if let Some(format) = &e.format {
30360            self.write("'");
30361            self.write(format);
30362            self.write("', ");
30363        }
30364        self.generate_expression(&e.this)?;
30365        if let Some(zone) = &e.zone {
30366            self.write(", ");
30367            self.generate_expression(zone)?;
30368        }
30369        self.write(")");
30370        Ok(())
30371    }
30372
30373    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
30374        // PARSE_IP(this, type, permissive)
30375        self.write_keyword("PARSE_IP");
30376        self.write("(");
30377        self.generate_expression(&e.this)?;
30378        if let Some(type_) = &e.type_ {
30379            self.write(", ");
30380            self.generate_expression(type_)?;
30381        }
30382        if let Some(permissive) = &e.permissive {
30383            self.write(", ");
30384            self.generate_expression(permissive)?;
30385        }
30386        self.write(")");
30387        Ok(())
30388    }
30389
30390    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
30391        // PARSE_JSON(this, [expression])
30392        self.write_keyword("PARSE_JSON");
30393        self.write("(");
30394        self.generate_expression(&e.this)?;
30395        if let Some(expression) = &e.expression {
30396            self.write(", ");
30397            self.generate_expression(expression)?;
30398        }
30399        self.write(")");
30400        Ok(())
30401    }
30402
30403    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
30404        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
30405        self.write_keyword("PARSE_TIME");
30406        self.write("(");
30407        self.write(&format!("'{}'", e.format));
30408        self.write(", ");
30409        self.generate_expression(&e.this)?;
30410        self.write(")");
30411        Ok(())
30412    }
30413
30414    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
30415        // PARSE_URL(this, [part_to_extract], [key], [permissive])
30416        self.write_keyword("PARSE_URL");
30417        self.write("(");
30418        self.generate_expression(&e.this)?;
30419        if let Some(part) = &e.part_to_extract {
30420            self.write(", ");
30421            self.generate_expression(part)?;
30422        }
30423        if let Some(key) = &e.key {
30424            self.write(", ");
30425            self.generate_expression(key)?;
30426        }
30427        if let Some(permissive) = &e.permissive {
30428            self.write(", ");
30429            self.generate_expression(permissive)?;
30430        }
30431        self.write(")");
30432        Ok(())
30433    }
30434
30435    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
30436        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
30437        if e.subpartition {
30438            self.write_keyword("SUBPARTITION");
30439        } else {
30440            self.write_keyword("PARTITION");
30441        }
30442        self.write("(");
30443        for (i, expr) in e.expressions.iter().enumerate() {
30444            if i > 0 {
30445                self.write(", ");
30446            }
30447            self.generate_expression(expr)?;
30448        }
30449        self.write(")");
30450        Ok(())
30451    }
30452
30453    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
30454        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
30455        if let Some(this) = &e.this {
30456            if let Some(expression) = &e.expression {
30457                // WITH (MODULUS this, REMAINDER expression)
30458                self.write_keyword("WITH");
30459                self.write(" (");
30460                self.write_keyword("MODULUS");
30461                self.write_space();
30462                self.generate_expression(this)?;
30463                self.write(", ");
30464                self.write_keyword("REMAINDER");
30465                self.write_space();
30466                self.generate_expression(expression)?;
30467                self.write(")");
30468            } else {
30469                // IN (this) - this could be a list
30470                self.write_keyword("IN");
30471                self.write(" (");
30472                self.generate_partition_bound_values(this)?;
30473                self.write(")");
30474            }
30475        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
30476            // FROM (from_expressions) TO (to_expressions)
30477            self.write_keyword("FROM");
30478            self.write(" (");
30479            self.generate_partition_bound_values(from)?;
30480            self.write(") ");
30481            self.write_keyword("TO");
30482            self.write(" (");
30483            self.generate_partition_bound_values(to)?;
30484            self.write(")");
30485        }
30486        Ok(())
30487    }
30488
30489    /// Generate partition bound values - handles Tuple expressions by outputting
30490    /// contents without wrapping parens (since caller provides the parens)
30491    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
30492        if let Expression::Tuple(t) = expr {
30493            for (i, e) in t.expressions.iter().enumerate() {
30494                if i > 0 {
30495                    self.write(", ");
30496                }
30497                self.generate_expression(e)?;
30498            }
30499            Ok(())
30500        } else {
30501            self.generate_expression(expr)
30502        }
30503    }
30504
30505    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
30506        // PARTITION BY LIST (partition_expressions) (create_expressions)
30507        self.write_keyword("PARTITION BY LIST");
30508        if let Some(partition_exprs) = &e.partition_expressions {
30509            self.write(" (");
30510            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
30511            self.generate_doris_partition_expressions(partition_exprs)?;
30512            self.write(")");
30513        }
30514        if let Some(create_exprs) = &e.create_expressions {
30515            self.write(" (");
30516            // Unwrap Tuple for partition definitions
30517            self.generate_doris_partition_definitions(create_exprs)?;
30518            self.write(")");
30519        }
30520        Ok(())
30521    }
30522
30523    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
30524        // PARTITION BY RANGE (partition_expressions) (create_expressions)
30525        self.write_keyword("PARTITION BY RANGE");
30526        if let Some(partition_exprs) = &e.partition_expressions {
30527            self.write(" (");
30528            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
30529            self.generate_doris_partition_expressions(partition_exprs)?;
30530            self.write(")");
30531        }
30532        if let Some(create_exprs) = &e.create_expressions {
30533            self.write(" (");
30534            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
30535            self.generate_doris_partition_definitions(create_exprs)?;
30536            self.write(")");
30537        }
30538        Ok(())
30539    }
30540
30541    /// Generate Doris partition column expressions (unwrap Tuple)
30542    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
30543        if let Expression::Tuple(t) = expr {
30544            for (i, e) in t.expressions.iter().enumerate() {
30545                if i > 0 {
30546                    self.write(", ");
30547                }
30548                self.generate_expression(e)?;
30549            }
30550        } else {
30551            self.generate_expression(expr)?;
30552        }
30553        Ok(())
30554    }
30555
30556    /// Generate Doris partition definitions (comma-separated Partition expressions)
30557    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
30558        match expr {
30559            Expression::Tuple(t) => {
30560                // Multiple partitions, comma-separated
30561                for (i, part) in t.expressions.iter().enumerate() {
30562                    if i > 0 {
30563                        self.write(", ");
30564                    }
30565                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
30566                    if let Expression::Partition(p) = part {
30567                        for (j, inner) in p.expressions.iter().enumerate() {
30568                            if j > 0 {
30569                                self.write(", ");
30570                            }
30571                            self.generate_expression(inner)?;
30572                        }
30573                    } else {
30574                        self.generate_expression(part)?;
30575                    }
30576                }
30577            }
30578            Expression::PartitionByRangePropertyDynamic(_) => {
30579                // Dynamic partition - FROM/TO/INTERVAL
30580                self.generate_expression(expr)?;
30581            }
30582            _ => {
30583                self.generate_expression(expr)?;
30584            }
30585        }
30586        Ok(())
30587    }
30588
30589    fn generate_partition_by_range_property_dynamic(
30590        &mut self,
30591        e: &PartitionByRangePropertyDynamic,
30592    ) -> Result<()> {
30593        if e.use_start_end {
30594            // StarRocks: START ('val') END ('val') EVERY (expr)
30595            if let Some(start) = &e.start {
30596                self.write_keyword("START");
30597                self.write(" (");
30598                self.generate_expression(start)?;
30599                self.write(")");
30600            }
30601            if let Some(end) = &e.end {
30602                self.write_space();
30603                self.write_keyword("END");
30604                self.write(" (");
30605                self.generate_expression(end)?;
30606                self.write(")");
30607            }
30608            if let Some(every) = &e.every {
30609                self.write_space();
30610                self.write_keyword("EVERY");
30611                self.write(" (");
30612                // Use unquoted interval format for StarRocks
30613                self.generate_doris_interval(every)?;
30614                self.write(")");
30615            }
30616        } else {
30617            // Doris: FROM (start) TO (end) INTERVAL n UNIT
30618            if let Some(start) = &e.start {
30619                self.write_keyword("FROM");
30620                self.write(" (");
30621                self.generate_expression(start)?;
30622                self.write(")");
30623            }
30624            if let Some(end) = &e.end {
30625                self.write_space();
30626                self.write_keyword("TO");
30627                self.write(" (");
30628                self.generate_expression(end)?;
30629                self.write(")");
30630            }
30631            if let Some(every) = &e.every {
30632                self.write_space();
30633                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
30634                self.generate_doris_interval(every)?;
30635            }
30636        }
30637        Ok(())
30638    }
30639
30640    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
30641    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
30642        if let Expression::Interval(interval) = expr {
30643            self.write_keyword("INTERVAL");
30644            if let Some(ref value) = interval.this {
30645                self.write_space();
30646                // If the value is a string literal that looks like a number,
30647                // output it without quotes (matching Python sqlglot's
30648                // partitionbyrangepropertydynamic_sql which converts back to number)
30649                match value {
30650                    Expression::Literal(Literal::String(s))
30651                        if s.chars()
30652                            .all(|c| c.is_ascii_digit() || c == '.' || c == '-')
30653                            && !s.is_empty() =>
30654                    {
30655                        self.write(s);
30656                    }
30657                    _ => {
30658                        self.generate_expression(value)?;
30659                    }
30660                }
30661            }
30662            if let Some(ref unit_spec) = interval.unit {
30663                self.write_space();
30664                self.write_interval_unit_spec(unit_spec)?;
30665            }
30666            Ok(())
30667        } else {
30668            self.generate_expression(expr)
30669        }
30670    }
30671
30672    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
30673        // TRUNCATE(expression, this)
30674        self.write_keyword("TRUNCATE");
30675        self.write("(");
30676        self.generate_expression(&e.expression)?;
30677        self.write(", ");
30678        self.generate_expression(&e.this)?;
30679        self.write(")");
30680        Ok(())
30681    }
30682
30683    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
30684        // Doris: PARTITION name VALUES IN (val1, val2)
30685        self.write_keyword("PARTITION");
30686        self.write_space();
30687        self.generate_expression(&e.this)?;
30688        self.write_space();
30689        self.write_keyword("VALUES IN");
30690        self.write(" (");
30691        for (i, expr) in e.expressions.iter().enumerate() {
30692            if i > 0 {
30693                self.write(", ");
30694            }
30695            self.generate_expression(expr)?;
30696        }
30697        self.write(")");
30698        Ok(())
30699    }
30700
30701    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
30702        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
30703        // TSQL ranges have no expressions and just use `this TO expression`
30704        if e.expressions.is_empty() && e.expression.is_some() {
30705            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
30706            self.generate_expression(&e.this)?;
30707            self.write_space();
30708            self.write_keyword("TO");
30709            self.write_space();
30710            self.generate_expression(e.expression.as_ref().unwrap())?;
30711            return Ok(());
30712        }
30713
30714        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
30715        self.write_keyword("PARTITION");
30716        self.write_space();
30717        self.generate_expression(&e.this)?;
30718        self.write_space();
30719
30720        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
30721        if e.expressions.len() == 1 {
30722            // Single value: VALUES LESS THAN (val)
30723            self.write_keyword("VALUES LESS THAN");
30724            self.write(" (");
30725            self.generate_expression(&e.expressions[0])?;
30726            self.write(")");
30727        } else if !e.expressions.is_empty() {
30728            // Multiple values with Tuple: VALUES [(val1), (val2))
30729            self.write_keyword("VALUES");
30730            self.write(" [");
30731            for (i, expr) in e.expressions.iter().enumerate() {
30732                if i > 0 {
30733                    self.write(", ");
30734                }
30735                // If the expr is a Tuple, generate its contents wrapped in parens
30736                if let Expression::Tuple(t) = expr {
30737                    self.write("(");
30738                    for (j, inner) in t.expressions.iter().enumerate() {
30739                        if j > 0 {
30740                            self.write(", ");
30741                        }
30742                        self.generate_expression(inner)?;
30743                    }
30744                    self.write(")");
30745                } else {
30746                    self.write("(");
30747                    self.generate_expression(expr)?;
30748                    self.write(")");
30749                }
30750            }
30751            self.write(")");
30752        }
30753        Ok(())
30754    }
30755
30756    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
30757        // BUCKET(this, expression)
30758        self.write_keyword("BUCKET");
30759        self.write("(");
30760        self.generate_expression(&e.this)?;
30761        self.write(", ");
30762        self.generate_expression(&e.expression)?;
30763        self.write(")");
30764        Ok(())
30765    }
30766
30767    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
30768        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
30769        if matches!(
30770            self.config.dialect,
30771            Some(crate::dialects::DialectType::Teradata)
30772                | Some(crate::dialects::DialectType::ClickHouse)
30773        ) {
30774            self.write_keyword("PARTITION BY");
30775        } else {
30776            self.write_keyword("PARTITIONED BY");
30777        }
30778        self.write_space();
30779        // In pretty mode, always use multiline tuple format for PARTITIONED BY
30780        if self.config.pretty {
30781            if let Expression::Tuple(ref tuple) = *e.this {
30782                self.write("(");
30783                self.write_newline();
30784                self.indent_level += 1;
30785                for (i, expr) in tuple.expressions.iter().enumerate() {
30786                    if i > 0 {
30787                        self.write(",");
30788                        self.write_newline();
30789                    }
30790                    self.write_indent();
30791                    self.generate_expression(expr)?;
30792                }
30793                self.indent_level -= 1;
30794                self.write_newline();
30795                self.write(")");
30796            } else {
30797                self.generate_expression(&e.this)?;
30798            }
30799        } else {
30800            self.generate_expression(&e.this)?;
30801        }
30802        Ok(())
30803    }
30804
30805    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
30806        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
30807        self.write_keyword("PARTITION OF");
30808        self.write_space();
30809        self.generate_expression(&e.this)?;
30810        // Check if expression is a PartitionBoundSpec
30811        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
30812            self.write_space();
30813            self.write_keyword("FOR VALUES");
30814            self.write_space();
30815            self.generate_expression(&e.expression)?;
30816        } else {
30817            self.write_space();
30818            self.write_keyword("DEFAULT");
30819        }
30820        Ok(())
30821    }
30822
30823    fn generate_period_for_system_time_constraint(
30824        &mut self,
30825        e: &PeriodForSystemTimeConstraint,
30826    ) -> Result<()> {
30827        // PERIOD FOR SYSTEM_TIME (this, expression)
30828        self.write_keyword("PERIOD FOR SYSTEM_TIME");
30829        self.write(" (");
30830        self.generate_expression(&e.this)?;
30831        self.write(", ");
30832        self.generate_expression(&e.expression)?;
30833        self.write(")");
30834        Ok(())
30835    }
30836
30837    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
30838        // value AS alias
30839        // The alias can be an identifier or an expression (e.g., string concatenation)
30840        self.generate_expression(&e.this)?;
30841        self.write_space();
30842        self.write_keyword("AS");
30843        self.write_space();
30844        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
30845        if self.config.unpivot_aliases_are_identifiers {
30846            match &e.alias {
30847                Expression::Literal(Literal::String(s)) => {
30848                    // Convert string literal to identifier
30849                    self.generate_identifier(&Identifier::new(s.clone()))?;
30850                }
30851                Expression::Literal(Literal::Number(n)) => {
30852                    // Convert number literal to quoted identifier
30853                    let mut id = Identifier::new(n.clone());
30854                    id.quoted = true;
30855                    self.generate_identifier(&id)?;
30856                }
30857                other => {
30858                    self.generate_expression(other)?;
30859                }
30860            }
30861        } else {
30862            self.generate_expression(&e.alias)?;
30863        }
30864        Ok(())
30865    }
30866
30867    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
30868        // ANY or ANY [expression]
30869        self.write_keyword("ANY");
30870        if let Some(this) = &e.this {
30871            self.write_space();
30872            self.generate_expression(this)?;
30873        }
30874        Ok(())
30875    }
30876
30877    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
30878        // ML.PREDICT(MODEL this, expression, [params_struct])
30879        self.write_keyword("ML.PREDICT");
30880        self.write("(");
30881        self.write_keyword("MODEL");
30882        self.write_space();
30883        self.generate_expression(&e.this)?;
30884        self.write(", ");
30885        self.generate_expression(&e.expression)?;
30886        if let Some(params) = &e.params_struct {
30887            self.write(", ");
30888            self.generate_expression(params)?;
30889        }
30890        self.write(")");
30891        Ok(())
30892    }
30893
30894    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
30895        // PREVIOUS_DAY(this, expression)
30896        self.write_keyword("PREVIOUS_DAY");
30897        self.write("(");
30898        self.generate_expression(&e.this)?;
30899        self.write(", ");
30900        self.generate_expression(&e.expression)?;
30901        self.write(")");
30902        Ok(())
30903    }
30904
30905    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
30906        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
30907        self.write_keyword("PRIMARY KEY");
30908        if let Some(name) = &e.this {
30909            self.write_space();
30910            self.generate_expression(name)?;
30911        }
30912        if !e.expressions.is_empty() {
30913            self.write(" (");
30914            for (i, expr) in e.expressions.iter().enumerate() {
30915                if i > 0 {
30916                    self.write(", ");
30917                }
30918                self.generate_expression(expr)?;
30919            }
30920            self.write(")");
30921        }
30922        if let Some(include) = &e.include {
30923            self.write_space();
30924            self.generate_expression(include)?;
30925        }
30926        if !e.options.is_empty() {
30927            self.write_space();
30928            for (i, opt) in e.options.iter().enumerate() {
30929                if i > 0 {
30930                    self.write_space();
30931                }
30932                self.generate_expression(opt)?;
30933            }
30934        }
30935        Ok(())
30936    }
30937
30938    fn generate_primary_key_column_constraint(
30939        &mut self,
30940        _e: &PrimaryKeyColumnConstraint,
30941    ) -> Result<()> {
30942        // PRIMARY KEY constraint at column level
30943        self.write_keyword("PRIMARY KEY");
30944        Ok(())
30945    }
30946
30947    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
30948        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
30949        self.write_keyword("PATH");
30950        self.write_space();
30951        self.generate_expression(&e.this)?;
30952        Ok(())
30953    }
30954
30955    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
30956        // PROJECTION this (expression)
30957        self.write_keyword("PROJECTION");
30958        self.write_space();
30959        self.generate_expression(&e.this)?;
30960        self.write(" (");
30961        self.generate_expression(&e.expression)?;
30962        self.write(")");
30963        Ok(())
30964    }
30965
30966    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
30967        // Properties list
30968        for (i, prop) in e.expressions.iter().enumerate() {
30969            if i > 0 {
30970                self.write(", ");
30971            }
30972            self.generate_expression(prop)?;
30973        }
30974        Ok(())
30975    }
30976
30977    fn generate_property(&mut self, e: &Property) -> Result<()> {
30978        // name=value
30979        self.generate_expression(&e.this)?;
30980        if let Some(value) = &e.value {
30981            self.write("=");
30982            self.generate_expression(value)?;
30983        }
30984        Ok(())
30985    }
30986
30987    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
30988    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
30989        self.write_keyword("OPTIONS");
30990        self.write(" (");
30991        for (i, opt) in options.iter().enumerate() {
30992            if i > 0 {
30993                self.write(", ");
30994            }
30995            self.generate_option_expression(opt)?;
30996        }
30997        self.write(")");
30998        Ok(())
30999    }
31000
31001    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
31002    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
31003        self.write_keyword("PROPERTIES");
31004        self.write(" (");
31005        for (i, prop) in properties.iter().enumerate() {
31006            if i > 0 {
31007                self.write(", ");
31008            }
31009            self.generate_option_expression(prop)?;
31010        }
31011        self.write(")");
31012        Ok(())
31013    }
31014
31015    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
31016    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
31017        self.write_keyword("ENVIRONMENT");
31018        self.write(" (");
31019        for (i, env_item) in environment.iter().enumerate() {
31020            if i > 0 {
31021                self.write(", ");
31022            }
31023            self.generate_environment_expression(env_item)?;
31024        }
31025        self.write(")");
31026        Ok(())
31027    }
31028
31029    /// Generate an environment expression with spaces around =
31030    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
31031        match expr {
31032            Expression::Eq(eq) => {
31033                // Generate key = value with spaces (Databricks ENVIRONMENT style)
31034                self.generate_expression(&eq.left)?;
31035                self.write(" = ");
31036                self.generate_expression(&eq.right)?;
31037                Ok(())
31038            }
31039            _ => self.generate_expression(expr),
31040        }
31041    }
31042
31043    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
31044    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
31045        self.write_keyword("TBLPROPERTIES");
31046        if self.config.pretty {
31047            self.write(" (");
31048            self.write_newline();
31049            self.indent_level += 1;
31050            for (i, opt) in options.iter().enumerate() {
31051                if i > 0 {
31052                    self.write(",");
31053                    self.write_newline();
31054                }
31055                self.write_indent();
31056                self.generate_option_expression(opt)?;
31057            }
31058            self.indent_level -= 1;
31059            self.write_newline();
31060            self.write(")");
31061        } else {
31062            self.write(" (");
31063            for (i, opt) in options.iter().enumerate() {
31064                if i > 0 {
31065                    self.write(", ");
31066                }
31067                self.generate_option_expression(opt)?;
31068            }
31069            self.write(")");
31070        }
31071        Ok(())
31072    }
31073
31074    /// Generate an option expression without spaces around =
31075    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
31076        match expr {
31077            Expression::Eq(eq) => {
31078                // Generate key=value without spaces
31079                self.generate_expression(&eq.left)?;
31080                self.write("=");
31081                self.generate_expression(&eq.right)?;
31082                Ok(())
31083            }
31084            _ => self.generate_expression(expr),
31085        }
31086    }
31087
31088    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
31089        // Just output the name
31090        self.generate_expression(&e.this)?;
31091        Ok(())
31092    }
31093
31094    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
31095        // PUT source_file @stage [options]
31096        self.write_keyword("PUT");
31097        self.write_space();
31098
31099        // Source file path - preserve original quoting
31100        if e.source_quoted {
31101            self.write("'");
31102            self.write(&e.source);
31103            self.write("'");
31104        } else {
31105            self.write(&e.source);
31106        }
31107
31108        self.write_space();
31109
31110        // Target stage reference - output the string directly (includes @)
31111        if let Expression::Literal(Literal::String(s)) = &e.target {
31112            self.write(s);
31113        } else {
31114            self.generate_expression(&e.target)?;
31115        }
31116
31117        // Optional parameters: KEY=VALUE
31118        for param in &e.params {
31119            self.write_space();
31120            self.write(&param.name);
31121            if let Some(ref value) = param.value {
31122                self.write("=");
31123                self.generate_expression(value)?;
31124            }
31125        }
31126
31127        Ok(())
31128    }
31129
31130    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
31131        // QUANTILE(this, quantile)
31132        self.write_keyword("QUANTILE");
31133        self.write("(");
31134        self.generate_expression(&e.this)?;
31135        if let Some(quantile) = &e.quantile {
31136            self.write(", ");
31137            self.generate_expression(quantile)?;
31138        }
31139        self.write(")");
31140        Ok(())
31141    }
31142
31143    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
31144        // QUERY_BAND = this [UPDATE] [FOR scope]
31145        if matches!(
31146            self.config.dialect,
31147            Some(crate::dialects::DialectType::Teradata)
31148        ) {
31149            self.write_keyword("SET");
31150            self.write_space();
31151        }
31152        self.write_keyword("QUERY_BAND");
31153        self.write(" = ");
31154        self.generate_expression(&e.this)?;
31155        if e.update.is_some() {
31156            self.write_space();
31157            self.write_keyword("UPDATE");
31158        }
31159        if let Some(scope) = &e.scope {
31160            self.write_space();
31161            self.write_keyword("FOR");
31162            self.write_space();
31163            self.generate_expression(scope)?;
31164        }
31165        Ok(())
31166    }
31167
31168    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
31169        // this = expression
31170        self.generate_expression(&e.this)?;
31171        if let Some(expression) = &e.expression {
31172            self.write(" = ");
31173            self.generate_expression(expression)?;
31174        }
31175        Ok(())
31176    }
31177
31178    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
31179        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
31180        self.write_keyword("TRANSFORM");
31181        self.write("(");
31182        for (i, expr) in e.expressions.iter().enumerate() {
31183            if i > 0 {
31184                self.write(", ");
31185            }
31186            self.generate_expression(expr)?;
31187        }
31188        self.write(")");
31189        if let Some(row_format_before) = &e.row_format_before {
31190            self.write_space();
31191            self.generate_expression(row_format_before)?;
31192        }
31193        if let Some(record_writer) = &e.record_writer {
31194            self.write_space();
31195            self.write_keyword("RECORDWRITER");
31196            self.write_space();
31197            self.generate_expression(record_writer)?;
31198        }
31199        if let Some(command_script) = &e.command_script {
31200            self.write_space();
31201            self.write_keyword("USING");
31202            self.write_space();
31203            self.generate_expression(command_script)?;
31204        }
31205        if let Some(schema) = &e.schema {
31206            self.write_space();
31207            self.write_keyword("AS");
31208            self.write_space();
31209            self.generate_expression(schema)?;
31210        }
31211        if let Some(row_format_after) = &e.row_format_after {
31212            self.write_space();
31213            self.generate_expression(row_format_after)?;
31214        }
31215        if let Some(record_reader) = &e.record_reader {
31216            self.write_space();
31217            self.write_keyword("RECORDREADER");
31218            self.write_space();
31219            self.generate_expression(record_reader)?;
31220        }
31221        Ok(())
31222    }
31223
31224    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
31225        // RANDN([seed])
31226        self.write_keyword("RANDN");
31227        self.write("(");
31228        if let Some(this) = &e.this {
31229            self.generate_expression(this)?;
31230        }
31231        self.write(")");
31232        Ok(())
31233    }
31234
31235    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
31236        // RANDSTR(this, [generator])
31237        self.write_keyword("RANDSTR");
31238        self.write("(");
31239        self.generate_expression(&e.this)?;
31240        if let Some(generator) = &e.generator {
31241            self.write(", ");
31242            self.generate_expression(generator)?;
31243        }
31244        self.write(")");
31245        Ok(())
31246    }
31247
31248    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
31249        // RANGE_BUCKET(this, expression)
31250        self.write_keyword("RANGE_BUCKET");
31251        self.write("(");
31252        self.generate_expression(&e.this)?;
31253        self.write(", ");
31254        self.generate_expression(&e.expression)?;
31255        self.write(")");
31256        Ok(())
31257    }
31258
31259    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
31260        // RANGE_N(this BETWEEN expressions [EACH each])
31261        self.write_keyword("RANGE_N");
31262        self.write("(");
31263        self.generate_expression(&e.this)?;
31264        self.write_space();
31265        self.write_keyword("BETWEEN");
31266        self.write_space();
31267        for (i, expr) in e.expressions.iter().enumerate() {
31268            if i > 0 {
31269                self.write(", ");
31270            }
31271            self.generate_expression(expr)?;
31272        }
31273        if let Some(each) = &e.each {
31274            self.write_space();
31275            self.write_keyword("EACH");
31276            self.write_space();
31277            self.generate_expression(each)?;
31278        }
31279        self.write(")");
31280        Ok(())
31281    }
31282
31283    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
31284        // READ_CSV(this, expressions...)
31285        self.write_keyword("READ_CSV");
31286        self.write("(");
31287        self.generate_expression(&e.this)?;
31288        for expr in &e.expressions {
31289            self.write(", ");
31290            self.generate_expression(expr)?;
31291        }
31292        self.write(")");
31293        Ok(())
31294    }
31295
31296    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
31297        // READ_PARQUET(expressions...)
31298        self.write_keyword("READ_PARQUET");
31299        self.write("(");
31300        for (i, expr) in e.expressions.iter().enumerate() {
31301            if i > 0 {
31302                self.write(", ");
31303            }
31304            self.generate_expression(expr)?;
31305        }
31306        self.write(")");
31307        Ok(())
31308    }
31309
31310    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
31311        // SEARCH kind FIRST BY this SET expression [USING using]
31312        // or CYCLE this SET expression [USING using]
31313        if e.kind == "CYCLE" {
31314            self.write_keyword("CYCLE");
31315        } else {
31316            self.write_keyword("SEARCH");
31317            self.write_space();
31318            self.write(&e.kind);
31319            self.write_space();
31320            self.write_keyword("FIRST BY");
31321        }
31322        self.write_space();
31323        self.generate_expression(&e.this)?;
31324        self.write_space();
31325        self.write_keyword("SET");
31326        self.write_space();
31327        self.generate_expression(&e.expression)?;
31328        if let Some(using) = &e.using {
31329            self.write_space();
31330            self.write_keyword("USING");
31331            self.write_space();
31332            self.generate_expression(using)?;
31333        }
31334        Ok(())
31335    }
31336
31337    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
31338        // REDUCE(this, initial, merge, [finish])
31339        self.write_keyword("REDUCE");
31340        self.write("(");
31341        self.generate_expression(&e.this)?;
31342        if let Some(initial) = &e.initial {
31343            self.write(", ");
31344            self.generate_expression(initial)?;
31345        }
31346        if let Some(merge) = &e.merge {
31347            self.write(", ");
31348            self.generate_expression(merge)?;
31349        }
31350        if let Some(finish) = &e.finish {
31351            self.write(", ");
31352            self.generate_expression(finish)?;
31353        }
31354        self.write(")");
31355        Ok(())
31356    }
31357
31358    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
31359        // REFERENCES this (expressions) [options]
31360        self.write_keyword("REFERENCES");
31361        self.write_space();
31362        self.generate_expression(&e.this)?;
31363        if !e.expressions.is_empty() {
31364            self.write(" (");
31365            for (i, expr) in e.expressions.iter().enumerate() {
31366                if i > 0 {
31367                    self.write(", ");
31368                }
31369                self.generate_expression(expr)?;
31370            }
31371            self.write(")");
31372        }
31373        for opt in &e.options {
31374            self.write_space();
31375            self.generate_expression(opt)?;
31376        }
31377        Ok(())
31378    }
31379
31380    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
31381        // REFRESH [kind] this
31382        self.write_keyword("REFRESH");
31383        if !e.kind.is_empty() {
31384            self.write_space();
31385            self.write_keyword(&e.kind);
31386        }
31387        self.write_space();
31388        self.generate_expression(&e.this)?;
31389        Ok(())
31390    }
31391
31392    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
31393        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
31394        self.write_keyword("REFRESH");
31395        self.write_space();
31396        self.write_keyword(&e.method);
31397
31398        if let Some(ref kind) = e.kind {
31399            self.write_space();
31400            self.write_keyword("ON");
31401            self.write_space();
31402            self.write_keyword(kind);
31403
31404            // EVERY n UNIT
31405            if let Some(ref every) = e.every {
31406                self.write_space();
31407                self.write_keyword("EVERY");
31408                self.write_space();
31409                self.generate_expression(every)?;
31410                if let Some(ref unit) = e.unit {
31411                    self.write_space();
31412                    self.write_keyword(unit);
31413                }
31414            }
31415
31416            // STARTS 'datetime'
31417            if let Some(ref starts) = e.starts {
31418                self.write_space();
31419                self.write_keyword("STARTS");
31420                self.write_space();
31421                self.generate_expression(starts)?;
31422            }
31423        }
31424        Ok(())
31425    }
31426
31427    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
31428        // REGEXP_COUNT(this, expression, position, parameters)
31429        self.write_keyword("REGEXP_COUNT");
31430        self.write("(");
31431        self.generate_expression(&e.this)?;
31432        self.write(", ");
31433        self.generate_expression(&e.expression)?;
31434        if let Some(position) = &e.position {
31435            self.write(", ");
31436            self.generate_expression(position)?;
31437        }
31438        if let Some(parameters) = &e.parameters {
31439            self.write(", ");
31440            self.generate_expression(parameters)?;
31441        }
31442        self.write(")");
31443        Ok(())
31444    }
31445
31446    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
31447        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
31448        self.write_keyword("REGEXP_EXTRACT_ALL");
31449        self.write("(");
31450        self.generate_expression(&e.this)?;
31451        self.write(", ");
31452        self.generate_expression(&e.expression)?;
31453        if let Some(group) = &e.group {
31454            self.write(", ");
31455            self.generate_expression(group)?;
31456        }
31457        self.write(")");
31458        Ok(())
31459    }
31460
31461    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
31462        // REGEXP_FULL_MATCH(this, expression)
31463        self.write_keyword("REGEXP_FULL_MATCH");
31464        self.write("(");
31465        self.generate_expression(&e.this)?;
31466        self.write(", ");
31467        self.generate_expression(&e.expression)?;
31468        self.write(")");
31469        Ok(())
31470    }
31471
31472    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
31473        use crate::dialects::DialectType;
31474        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
31475        if matches!(
31476            self.config.dialect,
31477            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
31478        ) && e.flag.is_none()
31479        {
31480            self.generate_expression(&e.this)?;
31481            self.write(" ~* ");
31482            self.generate_expression(&e.expression)?;
31483        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
31484            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
31485            self.write_keyword("REGEXP_LIKE");
31486            self.write("(");
31487            self.generate_expression(&e.this)?;
31488            self.write(", ");
31489            self.generate_expression(&e.expression)?;
31490            self.write(", ");
31491            if let Some(flag) = &e.flag {
31492                self.generate_expression(flag)?;
31493            } else {
31494                self.write("'i'");
31495            }
31496            self.write(")");
31497        } else {
31498            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
31499            self.generate_expression(&e.this)?;
31500            self.write_space();
31501            self.write_keyword("REGEXP_ILIKE");
31502            self.write_space();
31503            self.generate_expression(&e.expression)?;
31504            if let Some(flag) = &e.flag {
31505                self.write(", ");
31506                self.generate_expression(flag)?;
31507            }
31508        }
31509        Ok(())
31510    }
31511
31512    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
31513        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
31514        self.write_keyword("REGEXP_INSTR");
31515        self.write("(");
31516        self.generate_expression(&e.this)?;
31517        self.write(", ");
31518        self.generate_expression(&e.expression)?;
31519        if let Some(position) = &e.position {
31520            self.write(", ");
31521            self.generate_expression(position)?;
31522        }
31523        if let Some(occurrence) = &e.occurrence {
31524            self.write(", ");
31525            self.generate_expression(occurrence)?;
31526        }
31527        if let Some(option) = &e.option {
31528            self.write(", ");
31529            self.generate_expression(option)?;
31530        }
31531        if let Some(parameters) = &e.parameters {
31532            self.write(", ");
31533            self.generate_expression(parameters)?;
31534        }
31535        if let Some(group) = &e.group {
31536            self.write(", ");
31537            self.generate_expression(group)?;
31538        }
31539        self.write(")");
31540        Ok(())
31541    }
31542
31543    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
31544        // REGEXP_SPLIT(this, expression, limit)
31545        self.write_keyword("REGEXP_SPLIT");
31546        self.write("(");
31547        self.generate_expression(&e.this)?;
31548        self.write(", ");
31549        self.generate_expression(&e.expression)?;
31550        if let Some(limit) = &e.limit {
31551            self.write(", ");
31552            self.generate_expression(limit)?;
31553        }
31554        self.write(")");
31555        Ok(())
31556    }
31557
31558    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
31559        // REGR_AVGX(this, expression)
31560        self.write_keyword("REGR_AVGX");
31561        self.write("(");
31562        self.generate_expression(&e.this)?;
31563        self.write(", ");
31564        self.generate_expression(&e.expression)?;
31565        self.write(")");
31566        Ok(())
31567    }
31568
31569    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
31570        // REGR_AVGY(this, expression)
31571        self.write_keyword("REGR_AVGY");
31572        self.write("(");
31573        self.generate_expression(&e.this)?;
31574        self.write(", ");
31575        self.generate_expression(&e.expression)?;
31576        self.write(")");
31577        Ok(())
31578    }
31579
31580    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
31581        // REGR_COUNT(this, expression)
31582        self.write_keyword("REGR_COUNT");
31583        self.write("(");
31584        self.generate_expression(&e.this)?;
31585        self.write(", ");
31586        self.generate_expression(&e.expression)?;
31587        self.write(")");
31588        Ok(())
31589    }
31590
31591    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
31592        // REGR_INTERCEPT(this, expression)
31593        self.write_keyword("REGR_INTERCEPT");
31594        self.write("(");
31595        self.generate_expression(&e.this)?;
31596        self.write(", ");
31597        self.generate_expression(&e.expression)?;
31598        self.write(")");
31599        Ok(())
31600    }
31601
31602    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
31603        // REGR_R2(this, expression)
31604        self.write_keyword("REGR_R2");
31605        self.write("(");
31606        self.generate_expression(&e.this)?;
31607        self.write(", ");
31608        self.generate_expression(&e.expression)?;
31609        self.write(")");
31610        Ok(())
31611    }
31612
31613    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
31614        // REGR_SLOPE(this, expression)
31615        self.write_keyword("REGR_SLOPE");
31616        self.write("(");
31617        self.generate_expression(&e.this)?;
31618        self.write(", ");
31619        self.generate_expression(&e.expression)?;
31620        self.write(")");
31621        Ok(())
31622    }
31623
31624    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
31625        // REGR_SXX(this, expression)
31626        self.write_keyword("REGR_SXX");
31627        self.write("(");
31628        self.generate_expression(&e.this)?;
31629        self.write(", ");
31630        self.generate_expression(&e.expression)?;
31631        self.write(")");
31632        Ok(())
31633    }
31634
31635    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
31636        // REGR_SXY(this, expression)
31637        self.write_keyword("REGR_SXY");
31638        self.write("(");
31639        self.generate_expression(&e.this)?;
31640        self.write(", ");
31641        self.generate_expression(&e.expression)?;
31642        self.write(")");
31643        Ok(())
31644    }
31645
31646    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
31647        // REGR_SYY(this, expression)
31648        self.write_keyword("REGR_SYY");
31649        self.write("(");
31650        self.generate_expression(&e.this)?;
31651        self.write(", ");
31652        self.generate_expression(&e.expression)?;
31653        self.write(")");
31654        Ok(())
31655    }
31656
31657    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
31658        // REGR_VALX(this, expression)
31659        self.write_keyword("REGR_VALX");
31660        self.write("(");
31661        self.generate_expression(&e.this)?;
31662        self.write(", ");
31663        self.generate_expression(&e.expression)?;
31664        self.write(")");
31665        Ok(())
31666    }
31667
31668    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
31669        // REGR_VALY(this, expression)
31670        self.write_keyword("REGR_VALY");
31671        self.write("(");
31672        self.generate_expression(&e.this)?;
31673        self.write(", ");
31674        self.generate_expression(&e.expression)?;
31675        self.write(")");
31676        Ok(())
31677    }
31678
31679    fn generate_remote_with_connection_model_property(
31680        &mut self,
31681        e: &RemoteWithConnectionModelProperty,
31682    ) -> Result<()> {
31683        // REMOTE WITH CONNECTION this
31684        self.write_keyword("REMOTE WITH CONNECTION");
31685        self.write_space();
31686        self.generate_expression(&e.this)?;
31687        Ok(())
31688    }
31689
31690    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
31691        // RENAME COLUMN [IF EXISTS] this TO new_name
31692        self.write_keyword("RENAME COLUMN");
31693        if e.exists {
31694            self.write_space();
31695            self.write_keyword("IF EXISTS");
31696        }
31697        self.write_space();
31698        self.generate_expression(&e.this)?;
31699        if let Some(to) = &e.to {
31700            self.write_space();
31701            self.write_keyword("TO");
31702            self.write_space();
31703            self.generate_expression(to)?;
31704        }
31705        Ok(())
31706    }
31707
31708    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
31709        // REPLACE PARTITION expression [FROM source]
31710        self.write_keyword("REPLACE PARTITION");
31711        self.write_space();
31712        self.generate_expression(&e.expression)?;
31713        if let Some(source) = &e.source {
31714            self.write_space();
31715            self.write_keyword("FROM");
31716            self.write_space();
31717            self.generate_expression(source)?;
31718        }
31719        Ok(())
31720    }
31721
31722    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
31723        // RETURNING expressions [INTO into]
31724        // TSQL and Fabric use OUTPUT instead of RETURNING
31725        let keyword = match self.config.dialect {
31726            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
31727            _ => "RETURNING",
31728        };
31729        self.write_keyword(keyword);
31730        self.write_space();
31731        for (i, expr) in e.expressions.iter().enumerate() {
31732            if i > 0 {
31733                self.write(", ");
31734            }
31735            self.generate_expression(expr)?;
31736        }
31737        if let Some(into) = &e.into {
31738            self.write_space();
31739            self.write_keyword("INTO");
31740            self.write_space();
31741            self.generate_expression(into)?;
31742        }
31743        Ok(())
31744    }
31745
31746    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
31747        // OUTPUT expressions [INTO into_table]
31748        self.write_space();
31749        self.write_keyword("OUTPUT");
31750        self.write_space();
31751        for (i, expr) in output.columns.iter().enumerate() {
31752            if i > 0 {
31753                self.write(", ");
31754            }
31755            self.generate_expression(expr)?;
31756        }
31757        if let Some(into_table) = &output.into_table {
31758            self.write_space();
31759            self.write_keyword("INTO");
31760            self.write_space();
31761            self.generate_expression(into_table)?;
31762        }
31763        Ok(())
31764    }
31765
31766    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
31767        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
31768        self.write_keyword("RETURNS");
31769        if e.is_table.is_some() {
31770            self.write_space();
31771            self.write_keyword("TABLE");
31772        }
31773        if let Some(table) = &e.table {
31774            self.write_space();
31775            self.generate_expression(table)?;
31776        } else if let Some(this) = &e.this {
31777            self.write_space();
31778            self.generate_expression(this)?;
31779        }
31780        if e.null.is_some() {
31781            self.write_space();
31782            self.write_keyword("NULL ON NULL INPUT");
31783        }
31784        Ok(())
31785    }
31786
31787    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
31788        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
31789        self.write_keyword("ROLLBACK");
31790
31791        // TSQL always uses ROLLBACK TRANSACTION
31792        if e.this.is_none()
31793            && matches!(
31794                self.config.dialect,
31795                Some(DialectType::TSQL) | Some(DialectType::Fabric)
31796            )
31797        {
31798            self.write_space();
31799            self.write_keyword("TRANSACTION");
31800        }
31801
31802        // Check if this has TRANSACTION keyword or transaction name
31803        if let Some(this) = &e.this {
31804            // Check if it's just the "TRANSACTION" marker or an actual transaction name
31805            let is_transaction_marker = matches!(
31806                this.as_ref(),
31807                Expression::Identifier(id) if id.name == "TRANSACTION"
31808            );
31809
31810            self.write_space();
31811            self.write_keyword("TRANSACTION");
31812
31813            // If it's a real transaction name, output it
31814            if !is_transaction_marker {
31815                self.write_space();
31816                self.generate_expression(this)?;
31817            }
31818        }
31819
31820        // Output TO savepoint
31821        if let Some(savepoint) = &e.savepoint {
31822            self.write_space();
31823            self.write_keyword("TO");
31824            self.write_space();
31825            self.generate_expression(savepoint)?;
31826        }
31827        Ok(())
31828    }
31829
31830    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
31831        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
31832        if e.expressions.is_empty() {
31833            self.write_keyword("WITH ROLLUP");
31834        } else {
31835            self.write_keyword("ROLLUP");
31836            self.write("(");
31837            for (i, expr) in e.expressions.iter().enumerate() {
31838                if i > 0 {
31839                    self.write(", ");
31840                }
31841                self.generate_expression(expr)?;
31842            }
31843            self.write(")");
31844        }
31845        Ok(())
31846    }
31847
31848    fn generate_row_format_delimited_property(
31849        &mut self,
31850        e: &RowFormatDelimitedProperty,
31851    ) -> Result<()> {
31852        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
31853        self.write_keyword("ROW FORMAT DELIMITED");
31854        if let Some(fields) = &e.fields {
31855            self.write_space();
31856            self.write_keyword("FIELDS TERMINATED BY");
31857            self.write_space();
31858            self.generate_expression(fields)?;
31859        }
31860        if let Some(escaped) = &e.escaped {
31861            self.write_space();
31862            self.write_keyword("ESCAPED BY");
31863            self.write_space();
31864            self.generate_expression(escaped)?;
31865        }
31866        if let Some(items) = &e.collection_items {
31867            self.write_space();
31868            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
31869            self.write_space();
31870            self.generate_expression(items)?;
31871        }
31872        if let Some(keys) = &e.map_keys {
31873            self.write_space();
31874            self.write_keyword("MAP KEYS TERMINATED BY");
31875            self.write_space();
31876            self.generate_expression(keys)?;
31877        }
31878        if let Some(lines) = &e.lines {
31879            self.write_space();
31880            self.write_keyword("LINES TERMINATED BY");
31881            self.write_space();
31882            self.generate_expression(lines)?;
31883        }
31884        if let Some(null) = &e.null {
31885            self.write_space();
31886            self.write_keyword("NULL DEFINED AS");
31887            self.write_space();
31888            self.generate_expression(null)?;
31889        }
31890        if let Some(serde) = &e.serde {
31891            self.write_space();
31892            self.generate_expression(serde)?;
31893        }
31894        Ok(())
31895    }
31896
31897    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
31898        // ROW FORMAT this
31899        self.write_keyword("ROW FORMAT");
31900        self.write_space();
31901        self.generate_expression(&e.this)?;
31902        Ok(())
31903    }
31904
31905    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
31906        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
31907        self.write_keyword("ROW FORMAT SERDE");
31908        self.write_space();
31909        self.generate_expression(&e.this)?;
31910        if let Some(props) = &e.serde_properties {
31911            self.write_space();
31912            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
31913            self.generate_expression(props)?;
31914        }
31915        Ok(())
31916    }
31917
31918    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
31919        // SHA2(this, length)
31920        self.write_keyword("SHA2");
31921        self.write("(");
31922        self.generate_expression(&e.this)?;
31923        if let Some(length) = e.length {
31924            self.write(", ");
31925            self.write(&length.to_string());
31926        }
31927        self.write(")");
31928        Ok(())
31929    }
31930
31931    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
31932        // SHA2_DIGEST(this, length)
31933        self.write_keyword("SHA2_DIGEST");
31934        self.write("(");
31935        self.generate_expression(&e.this)?;
31936        if let Some(length) = e.length {
31937            self.write(", ");
31938            self.write(&length.to_string());
31939        }
31940        self.write(")");
31941        Ok(())
31942    }
31943
31944    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
31945        let name = if matches!(
31946            self.config.dialect,
31947            Some(crate::dialects::DialectType::Spark)
31948                | Some(crate::dialects::DialectType::Databricks)
31949        ) {
31950            "TRY_ADD"
31951        } else {
31952            "SAFE_ADD"
31953        };
31954        self.write_keyword(name);
31955        self.write("(");
31956        self.generate_expression(&e.this)?;
31957        self.write(", ");
31958        self.generate_expression(&e.expression)?;
31959        self.write(")");
31960        Ok(())
31961    }
31962
31963    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
31964        // SAFE_DIVIDE(this, expression)
31965        self.write_keyword("SAFE_DIVIDE");
31966        self.write("(");
31967        self.generate_expression(&e.this)?;
31968        self.write(", ");
31969        self.generate_expression(&e.expression)?;
31970        self.write(")");
31971        Ok(())
31972    }
31973
31974    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
31975        let name = if matches!(
31976            self.config.dialect,
31977            Some(crate::dialects::DialectType::Spark)
31978                | Some(crate::dialects::DialectType::Databricks)
31979        ) {
31980            "TRY_MULTIPLY"
31981        } else {
31982            "SAFE_MULTIPLY"
31983        };
31984        self.write_keyword(name);
31985        self.write("(");
31986        self.generate_expression(&e.this)?;
31987        self.write(", ");
31988        self.generate_expression(&e.expression)?;
31989        self.write(")");
31990        Ok(())
31991    }
31992
31993    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
31994        let name = if matches!(
31995            self.config.dialect,
31996            Some(crate::dialects::DialectType::Spark)
31997                | Some(crate::dialects::DialectType::Databricks)
31998        ) {
31999            "TRY_SUBTRACT"
32000        } else {
32001            "SAFE_SUBTRACT"
32002        };
32003        self.write_keyword(name);
32004        self.write("(");
32005        self.generate_expression(&e.this)?;
32006        self.write(", ");
32007        self.generate_expression(&e.expression)?;
32008        self.write(")");
32009        Ok(())
32010    }
32011
32012    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
32013    /// METHOD (size UNIT) [REPEATABLE (seed)]
32014    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
32015        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
32016        if matches!(sample.method, SampleMethod::Bucket) {
32017            self.write(" (");
32018            self.write_keyword("BUCKET");
32019            self.write_space();
32020            if let Some(ref num) = sample.bucket_numerator {
32021                self.generate_expression(num)?;
32022            }
32023            self.write_space();
32024            self.write_keyword("OUT OF");
32025            self.write_space();
32026            if let Some(ref denom) = sample.bucket_denominator {
32027                self.generate_expression(denom)?;
32028            }
32029            if let Some(ref field) = sample.bucket_field {
32030                self.write_space();
32031                self.write_keyword("ON");
32032                self.write_space();
32033                self.generate_expression(field)?;
32034            }
32035            self.write(")");
32036            return Ok(());
32037        }
32038
32039        // Output method name if explicitly specified, or for dialects that always require it
32040        let is_snowflake = matches!(
32041            self.config.dialect,
32042            Some(crate::dialects::DialectType::Snowflake)
32043        );
32044        let is_postgres = matches!(
32045            self.config.dialect,
32046            Some(crate::dialects::DialectType::PostgreSQL)
32047                | Some(crate::dialects::DialectType::Redshift)
32048        );
32049        // Databricks and Spark don't output method names
32050        let is_databricks = matches!(
32051            self.config.dialect,
32052            Some(crate::dialects::DialectType::Databricks)
32053        );
32054        let is_spark = matches!(
32055            self.config.dialect,
32056            Some(crate::dialects::DialectType::Spark)
32057        );
32058        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
32059        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
32060        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
32061        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
32062            self.write_space();
32063            if !sample.explicit_method && (is_snowflake || force_method) {
32064                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
32065                self.write_keyword("BERNOULLI");
32066            } else {
32067                match sample.method {
32068                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
32069                    SampleMethod::System => self.write_keyword("SYSTEM"),
32070                    SampleMethod::Block => self.write_keyword("BLOCK"),
32071                    SampleMethod::Row => self.write_keyword("ROW"),
32072                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
32073                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
32074                    SampleMethod::Bucket => {} // handled above
32075                }
32076            }
32077        }
32078
32079        // Output size, with or without parentheses depending on dialect
32080        let emit_size_no_parens = !self.config.tablesample_requires_parens;
32081        if emit_size_no_parens {
32082            self.write_space();
32083            match &sample.size {
32084                Expression::Tuple(tuple) => {
32085                    for (i, expr) in tuple.expressions.iter().enumerate() {
32086                        if i > 0 {
32087                            self.write(", ");
32088                        }
32089                        self.generate_expression(expr)?;
32090                    }
32091                }
32092                expr => self.generate_expression(expr)?,
32093            }
32094        } else {
32095            self.write(" (");
32096            self.generate_expression(&sample.size)?;
32097        }
32098
32099        // Determine unit
32100        let is_rows_method = matches!(
32101            sample.method,
32102            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
32103        );
32104        let is_percent = matches!(
32105            sample.method,
32106            SampleMethod::Percent
32107                | SampleMethod::System
32108                | SampleMethod::Bernoulli
32109                | SampleMethod::Block
32110        );
32111
32112        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
32113        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
32114        // For Databricks and Spark, always output PERCENT for percentage samples.
32115        let is_presto = matches!(
32116            self.config.dialect,
32117            Some(crate::dialects::DialectType::Presto)
32118                | Some(crate::dialects::DialectType::Trino)
32119                | Some(crate::dialects::DialectType::Athena)
32120        );
32121        let should_output_unit = if is_databricks || is_spark {
32122            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
32123            is_percent || is_rows_method || sample.unit_after_size
32124        } else if is_snowflake || is_postgres || is_presto {
32125            sample.unit_after_size
32126        } else {
32127            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
32128        };
32129
32130        if should_output_unit {
32131            self.write_space();
32132            if sample.is_percent {
32133                self.write_keyword("PERCENT");
32134            } else if is_rows_method && !sample.unit_after_size {
32135                self.write_keyword("ROWS");
32136            } else if sample.unit_after_size {
32137                match sample.method {
32138                    SampleMethod::Percent
32139                    | SampleMethod::System
32140                    | SampleMethod::Bernoulli
32141                    | SampleMethod::Block => {
32142                        self.write_keyword("PERCENT");
32143                    }
32144                    SampleMethod::Row | SampleMethod::Reservoir => {
32145                        self.write_keyword("ROWS");
32146                    }
32147                    _ => self.write_keyword("ROWS"),
32148                }
32149            } else {
32150                self.write_keyword("PERCENT");
32151            }
32152        }
32153
32154        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32155            if let Some(ref offset) = sample.offset {
32156                self.write_space();
32157                self.write_keyword("OFFSET");
32158                self.write_space();
32159                self.generate_expression(offset)?;
32160            }
32161        }
32162        if !emit_size_no_parens {
32163            self.write(")");
32164        }
32165
32166        Ok(())
32167    }
32168
32169    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
32170        // SAMPLE this (ClickHouse uses SAMPLE BY)
32171        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32172            self.write_keyword("SAMPLE BY");
32173        } else {
32174            self.write_keyword("SAMPLE");
32175        }
32176        self.write_space();
32177        self.generate_expression(&e.this)?;
32178        Ok(())
32179    }
32180
32181    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
32182        // this (expressions...)
32183        if let Some(this) = &e.this {
32184            self.generate_expression(this)?;
32185        }
32186        if !e.expressions.is_empty() {
32187            // Add space before column list if there's a preceding expression
32188            if e.this.is_some() {
32189                self.write_space();
32190            }
32191            self.write("(");
32192            for (i, expr) in e.expressions.iter().enumerate() {
32193                if i > 0 {
32194                    self.write(", ");
32195                }
32196                self.generate_expression(expr)?;
32197            }
32198            self.write(")");
32199        }
32200        Ok(())
32201    }
32202
32203    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
32204        // COMMENT this
32205        self.write_keyword("COMMENT");
32206        self.write_space();
32207        self.generate_expression(&e.this)?;
32208        Ok(())
32209    }
32210
32211    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
32212        // [this::]expression
32213        if let Some(this) = &e.this {
32214            self.generate_expression(this)?;
32215            self.write("::");
32216        }
32217        self.generate_expression(&e.expression)?;
32218        Ok(())
32219    }
32220
32221    fn generate_search(&mut self, e: &Search) -> Result<()> {
32222        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
32223        self.write_keyword("SEARCH");
32224        self.write("(");
32225        self.generate_expression(&e.this)?;
32226        self.write(", ");
32227        self.generate_expression(&e.expression)?;
32228        if let Some(json_scope) = &e.json_scope {
32229            self.write(", ");
32230            self.generate_expression(json_scope)?;
32231        }
32232        if let Some(analyzer) = &e.analyzer {
32233            self.write(", ");
32234            self.generate_expression(analyzer)?;
32235        }
32236        if let Some(analyzer_options) = &e.analyzer_options {
32237            self.write(", ");
32238            self.generate_expression(analyzer_options)?;
32239        }
32240        if let Some(search_mode) = &e.search_mode {
32241            self.write(", ");
32242            self.generate_expression(search_mode)?;
32243        }
32244        self.write(")");
32245        Ok(())
32246    }
32247
32248    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
32249        // SEARCH_IP(this, expression)
32250        self.write_keyword("SEARCH_IP");
32251        self.write("(");
32252        self.generate_expression(&e.this)?;
32253        self.write(", ");
32254        self.generate_expression(&e.expression)?;
32255        self.write(")");
32256        Ok(())
32257    }
32258
32259    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
32260        // SECURITY this
32261        self.write_keyword("SECURITY");
32262        self.write_space();
32263        self.generate_expression(&e.this)?;
32264        Ok(())
32265    }
32266
32267    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
32268        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
32269        self.write("SEMANTIC_VIEW(");
32270
32271        if self.config.pretty {
32272            // Pretty print: each clause on its own line
32273            self.write_newline();
32274            self.indent_level += 1;
32275            self.write_indent();
32276            self.generate_expression(&e.this)?;
32277
32278            if let Some(metrics) = &e.metrics {
32279                self.write_newline();
32280                self.write_indent();
32281                self.write_keyword("METRICS");
32282                self.write_space();
32283                self.generate_semantic_view_tuple(metrics)?;
32284            }
32285            if let Some(dimensions) = &e.dimensions {
32286                self.write_newline();
32287                self.write_indent();
32288                self.write_keyword("DIMENSIONS");
32289                self.write_space();
32290                self.generate_semantic_view_tuple(dimensions)?;
32291            }
32292            if let Some(facts) = &e.facts {
32293                self.write_newline();
32294                self.write_indent();
32295                self.write_keyword("FACTS");
32296                self.write_space();
32297                self.generate_semantic_view_tuple(facts)?;
32298            }
32299            if let Some(where_) = &e.where_ {
32300                self.write_newline();
32301                self.write_indent();
32302                self.write_keyword("WHERE");
32303                self.write_space();
32304                self.generate_expression(where_)?;
32305            }
32306            self.write_newline();
32307            self.indent_level -= 1;
32308            self.write_indent();
32309        } else {
32310            // Compact: all on one line
32311            self.generate_expression(&e.this)?;
32312            if let Some(metrics) = &e.metrics {
32313                self.write_space();
32314                self.write_keyword("METRICS");
32315                self.write_space();
32316                self.generate_semantic_view_tuple(metrics)?;
32317            }
32318            if let Some(dimensions) = &e.dimensions {
32319                self.write_space();
32320                self.write_keyword("DIMENSIONS");
32321                self.write_space();
32322                self.generate_semantic_view_tuple(dimensions)?;
32323            }
32324            if let Some(facts) = &e.facts {
32325                self.write_space();
32326                self.write_keyword("FACTS");
32327                self.write_space();
32328                self.generate_semantic_view_tuple(facts)?;
32329            }
32330            if let Some(where_) = &e.where_ {
32331                self.write_space();
32332                self.write_keyword("WHERE");
32333                self.write_space();
32334                self.generate_expression(where_)?;
32335            }
32336        }
32337        self.write(")");
32338        Ok(())
32339    }
32340
32341    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
32342    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
32343        if let Expression::Tuple(t) = expr {
32344            for (i, e) in t.expressions.iter().enumerate() {
32345                if i > 0 {
32346                    self.write(", ");
32347                }
32348                self.generate_expression(e)?;
32349            }
32350        } else {
32351            self.generate_expression(expr)?;
32352        }
32353        Ok(())
32354    }
32355
32356    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
32357        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
32358        if let Some(start) = &e.start {
32359            self.write_keyword("START WITH");
32360            self.write_space();
32361            self.generate_expression(start)?;
32362        }
32363        if let Some(increment) = &e.increment {
32364            self.write_space();
32365            self.write_keyword("INCREMENT BY");
32366            self.write_space();
32367            self.generate_expression(increment)?;
32368        }
32369        if let Some(minvalue) = &e.minvalue {
32370            self.write_space();
32371            self.write_keyword("MINVALUE");
32372            self.write_space();
32373            self.generate_expression(minvalue)?;
32374        }
32375        if let Some(maxvalue) = &e.maxvalue {
32376            self.write_space();
32377            self.write_keyword("MAXVALUE");
32378            self.write_space();
32379            self.generate_expression(maxvalue)?;
32380        }
32381        if let Some(cache) = &e.cache {
32382            self.write_space();
32383            self.write_keyword("CACHE");
32384            self.write_space();
32385            self.generate_expression(cache)?;
32386        }
32387        if let Some(owned) = &e.owned {
32388            self.write_space();
32389            self.write_keyword("OWNED BY");
32390            self.write_space();
32391            self.generate_expression(owned)?;
32392        }
32393        for opt in &e.options {
32394            self.write_space();
32395            self.generate_expression(opt)?;
32396        }
32397        Ok(())
32398    }
32399
32400    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
32401        // [WITH] SERDEPROPERTIES (expressions)
32402        if e.with_.is_some() {
32403            self.write_keyword("WITH");
32404            self.write_space();
32405        }
32406        self.write_keyword("SERDEPROPERTIES");
32407        self.write(" (");
32408        for (i, expr) in e.expressions.iter().enumerate() {
32409            if i > 0 {
32410                self.write(", ");
32411            }
32412            // Generate key=value without spaces around =
32413            match expr {
32414                Expression::Eq(eq) => {
32415                    self.generate_expression(&eq.left)?;
32416                    self.write("=");
32417                    self.generate_expression(&eq.right)?;
32418                }
32419                _ => self.generate_expression(expr)?,
32420            }
32421        }
32422        self.write(")");
32423        Ok(())
32424    }
32425
32426    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
32427        // @@[kind.]this
32428        self.write("@@");
32429        if let Some(kind) = &e.kind {
32430            self.write(kind);
32431            self.write(".");
32432        }
32433        self.generate_expression(&e.this)?;
32434        Ok(())
32435    }
32436
32437    fn generate_set(&mut self, e: &Set) -> Result<()> {
32438        // SET/UNSET [TAG] expressions
32439        if e.unset.is_some() {
32440            self.write_keyword("UNSET");
32441        } else {
32442            self.write_keyword("SET");
32443        }
32444        if e.tag.is_some() {
32445            self.write_space();
32446            self.write_keyword("TAG");
32447        }
32448        if !e.expressions.is_empty() {
32449            self.write_space();
32450            for (i, expr) in e.expressions.iter().enumerate() {
32451                if i > 0 {
32452                    self.write(", ");
32453                }
32454                self.generate_expression(expr)?;
32455            }
32456        }
32457        Ok(())
32458    }
32459
32460    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
32461        // SET this or SETCONFIG this
32462        self.write_keyword("SET");
32463        self.write_space();
32464        self.generate_expression(&e.this)?;
32465        Ok(())
32466    }
32467
32468    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
32469        // [kind] name = value
32470        if let Some(kind) = &e.kind {
32471            self.write_keyword(kind);
32472            self.write_space();
32473        }
32474        self.generate_expression(&e.name)?;
32475        self.write(" = ");
32476        self.generate_expression(&e.value)?;
32477        Ok(())
32478    }
32479
32480    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
32481        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
32482        if let Some(with_) = &e.with_ {
32483            self.generate_expression(with_)?;
32484            self.write_space();
32485        }
32486        self.generate_expression(&e.this)?;
32487        self.write_space();
32488        // kind should be UNION, INTERSECT, EXCEPT, etc.
32489        if let Some(kind) = &e.kind {
32490            self.write_keyword(kind);
32491        }
32492        if e.distinct {
32493            self.write_space();
32494            self.write_keyword("DISTINCT");
32495        } else {
32496            self.write_space();
32497            self.write_keyword("ALL");
32498        }
32499        if e.by_name.is_some() {
32500            self.write_space();
32501            self.write_keyword("BY NAME");
32502        }
32503        self.write_space();
32504        self.generate_expression(&e.expression)?;
32505        Ok(())
32506    }
32507
32508    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
32509        // SET or MULTISET
32510        if e.multi.is_some() {
32511            self.write_keyword("MULTISET");
32512        } else {
32513            self.write_keyword("SET");
32514        }
32515        Ok(())
32516    }
32517
32518    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
32519        // SETTINGS expressions
32520        self.write_keyword("SETTINGS");
32521        if self.config.pretty && e.expressions.len() > 1 {
32522            // Pretty print: each setting on its own line, indented
32523            self.indent_level += 1;
32524            for (i, expr) in e.expressions.iter().enumerate() {
32525                if i > 0 {
32526                    self.write(",");
32527                }
32528                self.write_newline();
32529                self.write_indent();
32530                self.generate_expression(expr)?;
32531            }
32532            self.indent_level -= 1;
32533        } else {
32534            self.write_space();
32535            for (i, expr) in e.expressions.iter().enumerate() {
32536                if i > 0 {
32537                    self.write(", ");
32538                }
32539                self.generate_expression(expr)?;
32540            }
32541        }
32542        Ok(())
32543    }
32544
32545    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
32546        // SHARING = this
32547        self.write_keyword("SHARING");
32548        if let Some(this) = &e.this {
32549            self.write(" = ");
32550            self.generate_expression(this)?;
32551        }
32552        Ok(())
32553    }
32554
32555    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
32556        // Python array slicing: begin:end:step
32557        if let Some(begin) = &e.this {
32558            self.generate_expression(begin)?;
32559        }
32560        self.write(":");
32561        if let Some(end) = &e.expression {
32562            self.generate_expression(end)?;
32563        }
32564        if let Some(step) = &e.step {
32565            self.write(":");
32566            self.generate_expression(step)?;
32567        }
32568        Ok(())
32569    }
32570
32571    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
32572        // SORT_ARRAY(this, asc)
32573        self.write_keyword("SORT_ARRAY");
32574        self.write("(");
32575        self.generate_expression(&e.this)?;
32576        if let Some(asc) = &e.asc {
32577            self.write(", ");
32578            self.generate_expression(asc)?;
32579        }
32580        self.write(")");
32581        Ok(())
32582    }
32583
32584    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
32585        // SORT BY expressions
32586        self.write_keyword("SORT BY");
32587        self.write_space();
32588        for (i, expr) in e.expressions.iter().enumerate() {
32589            if i > 0 {
32590                self.write(", ");
32591            }
32592            self.generate_ordered(expr)?;
32593        }
32594        Ok(())
32595    }
32596
32597    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
32598        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
32599        if e.compound.is_some() {
32600            self.write_keyword("COMPOUND");
32601            self.write_space();
32602        }
32603        self.write_keyword("SORTKEY");
32604        self.write("(");
32605        // If this is a Tuple, unwrap its contents to avoid double parentheses
32606        if let Expression::Tuple(t) = e.this.as_ref() {
32607            for (i, expr) in t.expressions.iter().enumerate() {
32608                if i > 0 {
32609                    self.write(", ");
32610                }
32611                self.generate_expression(expr)?;
32612            }
32613        } else {
32614            self.generate_expression(&e.this)?;
32615        }
32616        self.write(")");
32617        Ok(())
32618    }
32619
32620    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
32621        // SPLIT_PART(this, delimiter, part_index)
32622        self.write_keyword("SPLIT_PART");
32623        self.write("(");
32624        self.generate_expression(&e.this)?;
32625        if let Some(delimiter) = &e.delimiter {
32626            self.write(", ");
32627            self.generate_expression(delimiter)?;
32628        }
32629        if let Some(part_index) = &e.part_index {
32630            self.write(", ");
32631            self.generate_expression(part_index)?;
32632        }
32633        self.write(")");
32634        Ok(())
32635    }
32636
32637    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
32638        // READS SQL DATA or MODIFIES SQL DATA, etc.
32639        self.generate_expression(&e.this)?;
32640        Ok(())
32641    }
32642
32643    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
32644        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
32645        self.write_keyword("SQL SECURITY");
32646        self.write_space();
32647        self.generate_expression(&e.this)?;
32648        Ok(())
32649    }
32650
32651    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
32652        // ST_DISTANCE(this, expression, [use_spheroid])
32653        self.write_keyword("ST_DISTANCE");
32654        self.write("(");
32655        self.generate_expression(&e.this)?;
32656        self.write(", ");
32657        self.generate_expression(&e.expression)?;
32658        if let Some(use_spheroid) = &e.use_spheroid {
32659            self.write(", ");
32660            self.generate_expression(use_spheroid)?;
32661        }
32662        self.write(")");
32663        Ok(())
32664    }
32665
32666    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
32667        // ST_POINT(this, expression)
32668        self.write_keyword("ST_POINT");
32669        self.write("(");
32670        self.generate_expression(&e.this)?;
32671        self.write(", ");
32672        self.generate_expression(&e.expression)?;
32673        self.write(")");
32674        Ok(())
32675    }
32676
32677    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
32678        // IMMUTABLE, STABLE, VOLATILE
32679        self.generate_expression(&e.this)?;
32680        Ok(())
32681    }
32682
32683    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
32684        // STANDARD_HASH(this, [expression])
32685        self.write_keyword("STANDARD_HASH");
32686        self.write("(");
32687        self.generate_expression(&e.this)?;
32688        if let Some(expression) = &e.expression {
32689            self.write(", ");
32690            self.generate_expression(expression)?;
32691        }
32692        self.write(")");
32693        Ok(())
32694    }
32695
32696    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
32697        // STORED BY this
32698        self.write_keyword("STORED BY");
32699        self.write_space();
32700        self.generate_expression(&e.this)?;
32701        Ok(())
32702    }
32703
32704    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
32705        // STRPOS(this, substr) or STRPOS(this, substr, position)
32706        // Different dialects have different function names
32707        use crate::dialects::DialectType;
32708        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
32709            // Snowflake: CHARINDEX(substr, str[, position])
32710            self.write_keyword("CHARINDEX");
32711            self.write("(");
32712            if let Some(substr) = &e.substr {
32713                self.generate_expression(substr)?;
32714                self.write(", ");
32715            }
32716            self.generate_expression(&e.this)?;
32717            if let Some(position) = &e.position {
32718                self.write(", ");
32719                self.generate_expression(position)?;
32720            }
32721            self.write(")");
32722        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
32723            self.write_keyword("POSITION");
32724            self.write("(");
32725            self.generate_expression(&e.this)?;
32726            if let Some(substr) = &e.substr {
32727                self.write(", ");
32728                self.generate_expression(substr)?;
32729            }
32730            if let Some(position) = &e.position {
32731                self.write(", ");
32732                self.generate_expression(position)?;
32733            }
32734            if let Some(occurrence) = &e.occurrence {
32735                self.write(", ");
32736                self.generate_expression(occurrence)?;
32737            }
32738            self.write(")");
32739        } else if matches!(
32740            self.config.dialect,
32741            Some(DialectType::SQLite)
32742                | Some(DialectType::Oracle)
32743                | Some(DialectType::BigQuery)
32744                | Some(DialectType::Teradata)
32745        ) {
32746            self.write_keyword("INSTR");
32747            self.write("(");
32748            self.generate_expression(&e.this)?;
32749            if let Some(substr) = &e.substr {
32750                self.write(", ");
32751                self.generate_expression(substr)?;
32752            }
32753            if let Some(position) = &e.position {
32754                self.write(", ");
32755                self.generate_expression(position)?;
32756            } else if e.occurrence.is_some() {
32757                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
32758                // Default start position is 1
32759                self.write(", 1");
32760            }
32761            if let Some(occurrence) = &e.occurrence {
32762                self.write(", ");
32763                self.generate_expression(occurrence)?;
32764            }
32765            self.write(")");
32766        } else if matches!(
32767            self.config.dialect,
32768            Some(DialectType::MySQL)
32769                | Some(DialectType::SingleStore)
32770                | Some(DialectType::Doris)
32771                | Some(DialectType::StarRocks)
32772                | Some(DialectType::Hive)
32773                | Some(DialectType::Spark)
32774                | Some(DialectType::Databricks)
32775        ) {
32776            // LOCATE(substr, str[, position]) - substr first
32777            self.write_keyword("LOCATE");
32778            self.write("(");
32779            if let Some(substr) = &e.substr {
32780                self.generate_expression(substr)?;
32781                self.write(", ");
32782            }
32783            self.generate_expression(&e.this)?;
32784            if let Some(position) = &e.position {
32785                self.write(", ");
32786                self.generate_expression(position)?;
32787            }
32788            self.write(")");
32789        } else if matches!(self.config.dialect, Some(DialectType::TSQL)) {
32790            // CHARINDEX(substr, str[, position])
32791            self.write_keyword("CHARINDEX");
32792            self.write("(");
32793            if let Some(substr) = &e.substr {
32794                self.generate_expression(substr)?;
32795                self.write(", ");
32796            }
32797            self.generate_expression(&e.this)?;
32798            if let Some(position) = &e.position {
32799                self.write(", ");
32800                self.generate_expression(position)?;
32801            }
32802            self.write(")");
32803        } else if matches!(
32804            self.config.dialect,
32805            Some(DialectType::PostgreSQL)
32806                | Some(DialectType::Materialize)
32807                | Some(DialectType::RisingWave)
32808                | Some(DialectType::Redshift)
32809        ) {
32810            // POSITION(substr IN str) syntax
32811            self.write_keyword("POSITION");
32812            self.write("(");
32813            if let Some(substr) = &e.substr {
32814                self.generate_expression(substr)?;
32815                self.write(" IN ");
32816            }
32817            self.generate_expression(&e.this)?;
32818            self.write(")");
32819        } else {
32820            self.write_keyword("STRPOS");
32821            self.write("(");
32822            self.generate_expression(&e.this)?;
32823            if let Some(substr) = &e.substr {
32824                self.write(", ");
32825                self.generate_expression(substr)?;
32826            }
32827            if let Some(position) = &e.position {
32828                self.write(", ");
32829                self.generate_expression(position)?;
32830            }
32831            if let Some(occurrence) = &e.occurrence {
32832                self.write(", ");
32833                self.generate_expression(occurrence)?;
32834            }
32835            self.write(")");
32836        }
32837        Ok(())
32838    }
32839
32840    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
32841        match self.config.dialect {
32842            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
32843                // TO_DATE(this, java_format)
32844                self.write_keyword("TO_DATE");
32845                self.write("(");
32846                self.generate_expression(&e.this)?;
32847                if let Some(format) = &e.format {
32848                    self.write(", '");
32849                    self.write(&Self::strftime_to_java_format(format));
32850                    self.write("'");
32851                }
32852                self.write(")");
32853            }
32854            Some(DialectType::DuckDB) => {
32855                // CAST(STRPTIME(this, format) AS DATE)
32856                self.write_keyword("CAST");
32857                self.write("(");
32858                self.write_keyword("STRPTIME");
32859                self.write("(");
32860                self.generate_expression(&e.this)?;
32861                if let Some(format) = &e.format {
32862                    self.write(", '");
32863                    self.write(format);
32864                    self.write("'");
32865                }
32866                self.write(")");
32867                self.write_keyword(" AS ");
32868                self.write_keyword("DATE");
32869                self.write(")");
32870            }
32871            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
32872                // TO_DATE(this, pg_format)
32873                self.write_keyword("TO_DATE");
32874                self.write("(");
32875                self.generate_expression(&e.this)?;
32876                if let Some(format) = &e.format {
32877                    self.write(", '");
32878                    self.write(&Self::strftime_to_postgres_format(format));
32879                    self.write("'");
32880                }
32881                self.write(")");
32882            }
32883            Some(DialectType::BigQuery) => {
32884                // PARSE_DATE(format, this) - note: format comes first for BigQuery
32885                self.write_keyword("PARSE_DATE");
32886                self.write("(");
32887                if let Some(format) = &e.format {
32888                    self.write("'");
32889                    self.write(format);
32890                    self.write("'");
32891                    self.write(", ");
32892                }
32893                self.generate_expression(&e.this)?;
32894                self.write(")");
32895            }
32896            Some(DialectType::Teradata) => {
32897                // CAST(this AS DATE FORMAT 'teradata_fmt')
32898                self.write_keyword("CAST");
32899                self.write("(");
32900                self.generate_expression(&e.this)?;
32901                self.write_keyword(" AS ");
32902                self.write_keyword("DATE");
32903                if let Some(format) = &e.format {
32904                    self.write_keyword(" FORMAT ");
32905                    self.write("'");
32906                    self.write(&Self::strftime_to_teradata_format(format));
32907                    self.write("'");
32908                }
32909                self.write(")");
32910            }
32911            _ => {
32912                // STR_TO_DATE(this, format) - MySQL default
32913                self.write_keyword("STR_TO_DATE");
32914                self.write("(");
32915                self.generate_expression(&e.this)?;
32916                if let Some(format) = &e.format {
32917                    self.write(", '");
32918                    self.write(format);
32919                    self.write("'");
32920                }
32921                self.write(")");
32922            }
32923        }
32924        Ok(())
32925    }
32926
32927    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
32928    fn strftime_to_teradata_format(fmt: &str) -> String {
32929        let mut result = fmt.to_string();
32930        result = result.replace("%Y", "YYYY");
32931        result = result.replace("%y", "YY");
32932        result = result.replace("%m", "MM");
32933        result = result.replace("%B", "MMMM");
32934        result = result.replace("%b", "MMM");
32935        result = result.replace("%d", "DD");
32936        result = result.replace("%j", "DDD");
32937        result = result.replace("%H", "HH");
32938        result = result.replace("%M", "MI");
32939        result = result.replace("%S", "SS");
32940        result = result.replace("%f", "SSSSSS");
32941        result = result.replace("%A", "EEEE");
32942        result = result.replace("%a", "EEE");
32943        result
32944    }
32945
32946    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
32947    /// Public static version for use by other modules
32948    pub fn strftime_to_java_format_static(fmt: &str) -> String {
32949        Self::strftime_to_java_format(fmt)
32950    }
32951
32952    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
32953    fn strftime_to_java_format(fmt: &str) -> String {
32954        let mut result = fmt.to_string();
32955        // Handle non-padded variants BEFORE their padded counterparts
32956        result = result.replace("%-d", "d");
32957        result = result.replace("%-m", "M");
32958        result = result.replace("%-H", "H");
32959        result = result.replace("%-M", "m");
32960        result = result.replace("%-S", "s");
32961        result = result.replace("%Y", "yyyy");
32962        result = result.replace("%y", "yy");
32963        result = result.replace("%m", "MM");
32964        result = result.replace("%B", "MMMM");
32965        result = result.replace("%b", "MMM");
32966        result = result.replace("%d", "dd");
32967        result = result.replace("%j", "DDD");
32968        result = result.replace("%H", "HH");
32969        result = result.replace("%M", "mm");
32970        result = result.replace("%S", "ss");
32971        result = result.replace("%f", "SSSSSS");
32972        result = result.replace("%A", "EEEE");
32973        result = result.replace("%a", "EEE");
32974        result
32975    }
32976
32977    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
32978    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
32979    fn strftime_to_tsql_format(fmt: &str) -> String {
32980        let mut result = fmt.to_string();
32981        // Handle non-padded variants BEFORE their padded counterparts
32982        result = result.replace("%-d", "d");
32983        result = result.replace("%-m", "M");
32984        result = result.replace("%-H", "H");
32985        result = result.replace("%-M", "m");
32986        result = result.replace("%-S", "s");
32987        result = result.replace("%Y", "yyyy");
32988        result = result.replace("%y", "yy");
32989        result = result.replace("%m", "MM");
32990        result = result.replace("%B", "MMMM");
32991        result = result.replace("%b", "MMM");
32992        result = result.replace("%d", "dd");
32993        result = result.replace("%j", "DDD");
32994        result = result.replace("%H", "HH");
32995        result = result.replace("%M", "mm");
32996        result = result.replace("%S", "ss");
32997        result = result.replace("%f", "ffffff");
32998        result = result.replace("%A", "dddd");
32999        result = result.replace("%a", "ddd");
33000        result
33001    }
33002
33003    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
33004    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
33005    fn decompose_json_path(path: &str) -> Vec<String> {
33006        let mut parts = Vec::new();
33007        // Strip leading $ and optional .
33008        let path = if path.starts_with("$.") {
33009            &path[2..]
33010        } else if path.starts_with('$') {
33011            &path[1..]
33012        } else {
33013            path
33014        };
33015        if path.is_empty() {
33016            return parts;
33017        }
33018        let mut current = String::new();
33019        let chars: Vec<char> = path.chars().collect();
33020        let mut i = 0;
33021        while i < chars.len() {
33022            match chars[i] {
33023                '.' => {
33024                    if !current.is_empty() {
33025                        parts.push(current.clone());
33026                        current.clear();
33027                    }
33028                    i += 1;
33029                }
33030                '[' => {
33031                    if !current.is_empty() {
33032                        parts.push(current.clone());
33033                        current.clear();
33034                    }
33035                    i += 1;
33036                    // Read the content inside brackets
33037                    let mut bracket_content = String::new();
33038                    while i < chars.len() && chars[i] != ']' {
33039                        // Skip quotes inside brackets
33040                        if chars[i] == '"' || chars[i] == '\'' {
33041                            let quote = chars[i];
33042                            i += 1;
33043                            while i < chars.len() && chars[i] != quote {
33044                                bracket_content.push(chars[i]);
33045                                i += 1;
33046                            }
33047                            if i < chars.len() {
33048                                i += 1;
33049                            } // skip closing quote
33050                        } else {
33051                            bracket_content.push(chars[i]);
33052                            i += 1;
33053                        }
33054                    }
33055                    if i < chars.len() {
33056                        i += 1;
33057                    } // skip ]
33058                      // Skip wildcard [*] - don't add as a part
33059                    if bracket_content != "*" {
33060                        parts.push(bracket_content);
33061                    }
33062                }
33063                _ => {
33064                    current.push(chars[i]);
33065                    i += 1;
33066                }
33067            }
33068        }
33069        if !current.is_empty() {
33070            parts.push(current);
33071        }
33072        parts
33073    }
33074
33075    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
33076    fn strftime_to_postgres_format(fmt: &str) -> String {
33077        let mut result = fmt.to_string();
33078        // Handle non-padded variants BEFORE their padded counterparts
33079        result = result.replace("%-d", "FMDD");
33080        result = result.replace("%-m", "FMMM");
33081        result = result.replace("%-H", "FMHH24");
33082        result = result.replace("%-M", "FMMI");
33083        result = result.replace("%-S", "FMSS");
33084        result = result.replace("%Y", "YYYY");
33085        result = result.replace("%y", "YY");
33086        result = result.replace("%m", "MM");
33087        result = result.replace("%B", "Month");
33088        result = result.replace("%b", "Mon");
33089        result = result.replace("%d", "DD");
33090        result = result.replace("%j", "DDD");
33091        result = result.replace("%H", "HH24");
33092        result = result.replace("%M", "MI");
33093        result = result.replace("%S", "SS");
33094        result = result.replace("%f", "US");
33095        result = result.replace("%A", "Day");
33096        result = result.replace("%a", "Dy");
33097        result
33098    }
33099
33100    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
33101    fn strftime_to_snowflake_format(fmt: &str) -> String {
33102        let mut result = fmt.to_string();
33103        // Handle %-d (non-padded day) before %d (padded day)
33104        result = result.replace("%-d", "dd");
33105        result = result.replace("%-m", "mm"); // non-padded month
33106        result = result.replace("%Y", "yyyy");
33107        result = result.replace("%y", "yy");
33108        result = result.replace("%m", "mm");
33109        result = result.replace("%d", "DD");
33110        result = result.replace("%H", "hh24");
33111        result = result.replace("%M", "mi");
33112        result = result.replace("%S", "ss");
33113        result = result.replace("%f", "ff");
33114        result
33115    }
33116
33117    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
33118        // STR_TO_MAP(this, pair_delim, key_value_delim)
33119        self.write_keyword("STR_TO_MAP");
33120        self.write("(");
33121        self.generate_expression(&e.this)?;
33122        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
33123        let needs_defaults = matches!(
33124            self.config.dialect,
33125            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
33126        );
33127        if let Some(pair_delim) = &e.pair_delim {
33128            self.write(", ");
33129            self.generate_expression(pair_delim)?;
33130        } else if needs_defaults {
33131            self.write(", ','");
33132        }
33133        if let Some(key_value_delim) = &e.key_value_delim {
33134            self.write(", ");
33135            self.generate_expression(key_value_delim)?;
33136        } else if needs_defaults {
33137            self.write(", ':'");
33138        }
33139        self.write(")");
33140        Ok(())
33141    }
33142
33143    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
33144        // Detect format style: strftime (starts with %) vs Snowflake/Java
33145        let is_strftime = e.format.contains('%');
33146        // Helper: get strftime format from whatever style is stored
33147        let to_strftime = |f: &str| -> String {
33148            if is_strftime {
33149                f.to_string()
33150            } else {
33151                Self::snowflake_format_to_strftime(f)
33152            }
33153        };
33154        // Helper: get Java format
33155        let to_java = |f: &str| -> String {
33156            if is_strftime {
33157                Self::strftime_to_java_format(f)
33158            } else {
33159                Self::snowflake_format_to_spark(f)
33160            }
33161        };
33162        // Helper: get PG format
33163        let to_pg = |f: &str| -> String {
33164            if is_strftime {
33165                Self::strftime_to_postgres_format(f)
33166            } else {
33167                Self::convert_strptime_to_postgres_format(f)
33168            }
33169        };
33170
33171        match self.config.dialect {
33172            Some(DialectType::Exasol) => {
33173                self.write_keyword("TO_DATE");
33174                self.write("(");
33175                self.generate_expression(&e.this)?;
33176                self.write(", '");
33177                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
33178                self.write("'");
33179                self.write(")");
33180            }
33181            Some(DialectType::BigQuery) => {
33182                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
33183                let fmt = to_strftime(&e.format);
33184                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
33185                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
33186                self.write_keyword("PARSE_TIMESTAMP");
33187                self.write("('");
33188                self.write(&fmt);
33189                self.write("', ");
33190                self.generate_expression(&e.this)?;
33191                self.write(")");
33192            }
33193            Some(DialectType::Hive) => {
33194                // Hive: CAST(x AS TIMESTAMP) for simple date formats
33195                // Check both the raw format and the converted format (in case it's already Java)
33196                let java_fmt = to_java(&e.format);
33197                if java_fmt == "yyyy-MM-dd HH:mm:ss"
33198                    || java_fmt == "yyyy-MM-dd"
33199                    || e.format == "yyyy-MM-dd HH:mm:ss"
33200                    || e.format == "yyyy-MM-dd"
33201                {
33202                    self.write_keyword("CAST");
33203                    self.write("(");
33204                    self.generate_expression(&e.this)?;
33205                    self.write(" ");
33206                    self.write_keyword("AS TIMESTAMP");
33207                    self.write(")");
33208                } else {
33209                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
33210                    self.write_keyword("CAST");
33211                    self.write("(");
33212                    self.write_keyword("FROM_UNIXTIME");
33213                    self.write("(");
33214                    self.write_keyword("UNIX_TIMESTAMP");
33215                    self.write("(");
33216                    self.generate_expression(&e.this)?;
33217                    self.write(", '");
33218                    self.write(&java_fmt);
33219                    self.write("')");
33220                    self.write(") ");
33221                    self.write_keyword("AS TIMESTAMP");
33222                    self.write(")");
33223                }
33224            }
33225            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
33226                // Spark: TO_TIMESTAMP(value, java_format)
33227                let java_fmt = to_java(&e.format);
33228                self.write_keyword("TO_TIMESTAMP");
33229                self.write("(");
33230                self.generate_expression(&e.this)?;
33231                self.write(", '");
33232                self.write(&java_fmt);
33233                self.write("')");
33234            }
33235            Some(DialectType::MySQL) => {
33236                // MySQL: STR_TO_DATE(value, format)
33237                let mut fmt = to_strftime(&e.format);
33238                // MySQL uses %e for non-padded day, %T for %H:%M:%S
33239                fmt = fmt.replace("%-d", "%e");
33240                fmt = fmt.replace("%-m", "%c");
33241                fmt = fmt.replace("%H:%M:%S", "%T");
33242                self.write_keyword("STR_TO_DATE");
33243                self.write("(");
33244                self.generate_expression(&e.this)?;
33245                self.write(", '");
33246                self.write(&fmt);
33247                self.write("')");
33248            }
33249            Some(DialectType::Drill) => {
33250                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
33251                let java_fmt = to_java(&e.format);
33252                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
33253                let java_fmt = java_fmt.replace('T', "''T''");
33254                self.write_keyword("TO_TIMESTAMP");
33255                self.write("(");
33256                self.generate_expression(&e.this)?;
33257                self.write(", '");
33258                self.write(&java_fmt);
33259                self.write("')");
33260            }
33261            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
33262                // Presto: DATE_PARSE(value, strftime_format)
33263                let mut fmt = to_strftime(&e.format);
33264                // Presto uses %e for non-padded day, %T for %H:%M:%S
33265                fmt = fmt.replace("%-d", "%e");
33266                fmt = fmt.replace("%-m", "%c");
33267                fmt = fmt.replace("%H:%M:%S", "%T");
33268                self.write_keyword("DATE_PARSE");
33269                self.write("(");
33270                self.generate_expression(&e.this)?;
33271                self.write(", '");
33272                self.write(&fmt);
33273                self.write("')");
33274            }
33275            Some(DialectType::DuckDB) => {
33276                // DuckDB: STRPTIME(value, strftime_format)
33277                let fmt = to_strftime(&e.format);
33278                self.write_keyword("STRPTIME");
33279                self.write("(");
33280                self.generate_expression(&e.this)?;
33281                self.write(", '");
33282                self.write(&fmt);
33283                self.write("')");
33284            }
33285            Some(DialectType::PostgreSQL)
33286            | Some(DialectType::Redshift)
33287            | Some(DialectType::Materialize) => {
33288                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
33289                let pg_fmt = to_pg(&e.format);
33290                self.write_keyword("TO_TIMESTAMP");
33291                self.write("(");
33292                self.generate_expression(&e.this)?;
33293                self.write(", '");
33294                self.write(&pg_fmt);
33295                self.write("')");
33296            }
33297            Some(DialectType::Oracle) => {
33298                // Oracle: TO_TIMESTAMP(value, pg_format)
33299                let pg_fmt = to_pg(&e.format);
33300                self.write_keyword("TO_TIMESTAMP");
33301                self.write("(");
33302                self.generate_expression(&e.this)?;
33303                self.write(", '");
33304                self.write(&pg_fmt);
33305                self.write("')");
33306            }
33307            Some(DialectType::Snowflake) => {
33308                // Snowflake: TO_TIMESTAMP(value, format) - native format
33309                self.write_keyword("TO_TIMESTAMP");
33310                self.write("(");
33311                self.generate_expression(&e.this)?;
33312                self.write(", '");
33313                self.write(&e.format);
33314                self.write("')");
33315            }
33316            _ => {
33317                // Default: STR_TO_TIME(this, format)
33318                self.write_keyword("STR_TO_TIME");
33319                self.write("(");
33320                self.generate_expression(&e.this)?;
33321                self.write(", '");
33322                self.write(&e.format);
33323                self.write("'");
33324                self.write(")");
33325            }
33326        }
33327        Ok(())
33328    }
33329
33330    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
33331    fn snowflake_format_to_strftime(format: &str) -> String {
33332        let mut result = String::new();
33333        let chars: Vec<char> = format.chars().collect();
33334        let mut i = 0;
33335        while i < chars.len() {
33336            let remaining = &format[i..];
33337            if remaining.starts_with("yyyy") {
33338                result.push_str("%Y");
33339                i += 4;
33340            } else if remaining.starts_with("yy") {
33341                result.push_str("%y");
33342                i += 2;
33343            } else if remaining.starts_with("mmmm") {
33344                result.push_str("%B"); // full month name
33345                i += 4;
33346            } else if remaining.starts_with("mon") {
33347                result.push_str("%b"); // abbreviated month
33348                i += 3;
33349            } else if remaining.starts_with("mm") {
33350                result.push_str("%m");
33351                i += 2;
33352            } else if remaining.starts_with("DD") {
33353                result.push_str("%d");
33354                i += 2;
33355            } else if remaining.starts_with("dy") {
33356                result.push_str("%a"); // abbreviated day name
33357                i += 2;
33358            } else if remaining.starts_with("hh24") {
33359                result.push_str("%H");
33360                i += 4;
33361            } else if remaining.starts_with("hh12") {
33362                result.push_str("%I");
33363                i += 4;
33364            } else if remaining.starts_with("hh") {
33365                result.push_str("%H");
33366                i += 2;
33367            } else if remaining.starts_with("mi") {
33368                result.push_str("%M");
33369                i += 2;
33370            } else if remaining.starts_with("ss") {
33371                result.push_str("%S");
33372                i += 2;
33373            } else if remaining.starts_with("ff") {
33374                // Fractional seconds
33375                result.push_str("%f");
33376                i += 2;
33377                // Skip digits after ff (ff3, ff6, ff9)
33378                while i < chars.len() && chars[i].is_ascii_digit() {
33379                    i += 1;
33380                }
33381            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
33382                result.push_str("%p");
33383                i += 2;
33384            } else if remaining.starts_with("tz") {
33385                result.push_str("%Z");
33386                i += 2;
33387            } else {
33388                result.push(chars[i]);
33389                i += 1;
33390            }
33391        }
33392        result
33393    }
33394
33395    /// Convert Snowflake normalized format to Spark format (Java-style)
33396    fn snowflake_format_to_spark(format: &str) -> String {
33397        let mut result = String::new();
33398        let chars: Vec<char> = format.chars().collect();
33399        let mut i = 0;
33400        while i < chars.len() {
33401            let remaining = &format[i..];
33402            if remaining.starts_with("yyyy") {
33403                result.push_str("yyyy");
33404                i += 4;
33405            } else if remaining.starts_with("yy") {
33406                result.push_str("yy");
33407                i += 2;
33408            } else if remaining.starts_with("mmmm") {
33409                result.push_str("MMMM"); // full month name
33410                i += 4;
33411            } else if remaining.starts_with("mon") {
33412                result.push_str("MMM"); // abbreviated month
33413                i += 3;
33414            } else if remaining.starts_with("mm") {
33415                result.push_str("MM");
33416                i += 2;
33417            } else if remaining.starts_with("DD") {
33418                result.push_str("dd");
33419                i += 2;
33420            } else if remaining.starts_with("dy") {
33421                result.push_str("EEE"); // abbreviated day name
33422                i += 2;
33423            } else if remaining.starts_with("hh24") {
33424                result.push_str("HH");
33425                i += 4;
33426            } else if remaining.starts_with("hh12") {
33427                result.push_str("hh");
33428                i += 4;
33429            } else if remaining.starts_with("hh") {
33430                result.push_str("HH");
33431                i += 2;
33432            } else if remaining.starts_with("mi") {
33433                result.push_str("mm");
33434                i += 2;
33435            } else if remaining.starts_with("ss") {
33436                result.push_str("ss");
33437                i += 2;
33438            } else if remaining.starts_with("ff") {
33439                result.push_str("SSS"); // milliseconds
33440                i += 2;
33441                // Skip digits after ff
33442                while i < chars.len() && chars[i].is_ascii_digit() {
33443                    i += 1;
33444                }
33445            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
33446                result.push_str("a");
33447                i += 2;
33448            } else if remaining.starts_with("tz") {
33449                result.push_str("z");
33450                i += 2;
33451            } else {
33452                result.push(chars[i]);
33453                i += 1;
33454            }
33455        }
33456        result
33457    }
33458
33459    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
33460        match self.config.dialect {
33461            Some(DialectType::DuckDB) => {
33462                // DuckDB: EPOCH(STRPTIME(value, format))
33463                self.write_keyword("EPOCH");
33464                self.write("(");
33465                self.write_keyword("STRPTIME");
33466                self.write("(");
33467                if let Some(this) = &e.this {
33468                    self.generate_expression(this)?;
33469                }
33470                if let Some(format) = &e.format {
33471                    self.write(", '");
33472                    self.write(format);
33473                    self.write("'");
33474                }
33475                self.write("))");
33476            }
33477            Some(DialectType::Hive) => {
33478                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
33479                self.write_keyword("UNIX_TIMESTAMP");
33480                self.write("(");
33481                if let Some(this) = &e.this {
33482                    self.generate_expression(this)?;
33483                }
33484                if let Some(format) = &e.format {
33485                    let java_fmt = Self::strftime_to_java_format(format);
33486                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
33487                        self.write(", '");
33488                        self.write(&java_fmt);
33489                        self.write("'");
33490                    }
33491                }
33492                self.write(")");
33493            }
33494            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
33495                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
33496                self.write_keyword("UNIX_TIMESTAMP");
33497                self.write("(");
33498                if let Some(this) = &e.this {
33499                    self.generate_expression(this)?;
33500                }
33501                if let Some(format) = &e.format {
33502                    self.write(", '");
33503                    self.write(format);
33504                    self.write("'");
33505                }
33506                self.write(")");
33507            }
33508            Some(DialectType::Presto) | Some(DialectType::Trino) => {
33509                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
33510                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
33511                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
33512                let java_fmt = Self::strftime_to_java_format(c_fmt);
33513                self.write_keyword("TO_UNIXTIME");
33514                self.write("(");
33515                self.write_keyword("COALESCE");
33516                self.write("(");
33517                self.write_keyword("TRY");
33518                self.write("(");
33519                self.write_keyword("DATE_PARSE");
33520                self.write("(");
33521                self.write_keyword("CAST");
33522                self.write("(");
33523                if let Some(this) = &e.this {
33524                    self.generate_expression(this)?;
33525                }
33526                self.write(" ");
33527                self.write_keyword("AS VARCHAR");
33528                self.write("), '");
33529                self.write(c_fmt);
33530                self.write("')), ");
33531                self.write_keyword("PARSE_DATETIME");
33532                self.write("(");
33533                self.write_keyword("DATE_FORMAT");
33534                self.write("(");
33535                self.write_keyword("CAST");
33536                self.write("(");
33537                if let Some(this) = &e.this {
33538                    self.generate_expression(this)?;
33539                }
33540                self.write(" ");
33541                self.write_keyword("AS TIMESTAMP");
33542                self.write("), '");
33543                self.write(c_fmt);
33544                self.write("'), '");
33545                self.write(&java_fmt);
33546                self.write("')))");
33547            }
33548            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
33549                // Spark: UNIX_TIMESTAMP(value, java_format)
33550                self.write_keyword("UNIX_TIMESTAMP");
33551                self.write("(");
33552                if let Some(this) = &e.this {
33553                    self.generate_expression(this)?;
33554                }
33555                if let Some(format) = &e.format {
33556                    let java_fmt = Self::strftime_to_java_format(format);
33557                    self.write(", '");
33558                    self.write(&java_fmt);
33559                    self.write("'");
33560                }
33561                self.write(")");
33562            }
33563            _ => {
33564                // Default: STR_TO_UNIX(this, format)
33565                self.write_keyword("STR_TO_UNIX");
33566                self.write("(");
33567                if let Some(this) = &e.this {
33568                    self.generate_expression(this)?;
33569                }
33570                if let Some(format) = &e.format {
33571                    self.write(", '");
33572                    self.write(format);
33573                    self.write("'");
33574                }
33575                self.write(")");
33576            }
33577        }
33578        Ok(())
33579    }
33580
33581    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
33582        // STRING_TO_ARRAY(this, delimiter, null_string)
33583        self.write_keyword("STRING_TO_ARRAY");
33584        self.write("(");
33585        self.generate_expression(&e.this)?;
33586        if let Some(expression) = &e.expression {
33587            self.write(", ");
33588            self.generate_expression(expression)?;
33589        }
33590        if let Some(null_val) = &e.null {
33591            self.write(", ");
33592            self.generate_expression(null_val)?;
33593        }
33594        self.write(")");
33595        Ok(())
33596    }
33597
33598    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
33599        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
33600            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
33601            self.write_keyword("OBJECT_CONSTRUCT");
33602            self.write("(");
33603            for (i, (name, expr)) in e.fields.iter().enumerate() {
33604                if i > 0 {
33605                    self.write(", ");
33606                }
33607                if let Some(name) = name {
33608                    self.write("'");
33609                    self.write(name);
33610                    self.write("'");
33611                    self.write(", ");
33612                } else {
33613                    self.write("'_");
33614                    self.write(&i.to_string());
33615                    self.write("'");
33616                    self.write(", ");
33617                }
33618                self.generate_expression(expr)?;
33619            }
33620            self.write(")");
33621        } else if self.config.struct_curly_brace_notation {
33622            // DuckDB-style: {'key': value, ...}
33623            self.write("{");
33624            for (i, (name, expr)) in e.fields.iter().enumerate() {
33625                if i > 0 {
33626                    self.write(", ");
33627                }
33628                if let Some(name) = name {
33629                    // Quote the key as a string literal
33630                    self.write("'");
33631                    self.write(name);
33632                    self.write("'");
33633                    self.write(": ");
33634                } else {
33635                    // Unnamed field: use positional key
33636                    self.write("'_");
33637                    self.write(&i.to_string());
33638                    self.write("'");
33639                    self.write(": ");
33640                }
33641                self.generate_expression(expr)?;
33642            }
33643            self.write("}");
33644        } else {
33645            // Standard SQL struct notation
33646            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
33647            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
33648            let value_as_name = matches!(
33649                self.config.dialect,
33650                Some(DialectType::BigQuery)
33651                    | Some(DialectType::Spark)
33652                    | Some(DialectType::Databricks)
33653                    | Some(DialectType::Hive)
33654            );
33655            self.write_keyword("STRUCT");
33656            self.write("(");
33657            for (i, (name, expr)) in e.fields.iter().enumerate() {
33658                if i > 0 {
33659                    self.write(", ");
33660                }
33661                if let Some(name) = name {
33662                    if value_as_name {
33663                        // STRUCT(value AS name)
33664                        self.generate_expression(expr)?;
33665                        self.write_space();
33666                        self.write_keyword("AS");
33667                        self.write_space();
33668                        // Quote name if it contains spaces or special chars
33669                        let needs_quoting = name.contains(' ') || name.contains('-');
33670                        if needs_quoting {
33671                            if matches!(
33672                                self.config.dialect,
33673                                Some(DialectType::Spark)
33674                                    | Some(DialectType::Databricks)
33675                                    | Some(DialectType::Hive)
33676                            ) {
33677                                self.write("`");
33678                                self.write(name);
33679                                self.write("`");
33680                            } else {
33681                                self.write(name);
33682                            }
33683                        } else {
33684                            self.write(name);
33685                        }
33686                    } else {
33687                        // STRUCT(name AS value)
33688                        self.write(name);
33689                        self.write_space();
33690                        self.write_keyword("AS");
33691                        self.write_space();
33692                        self.generate_expression(expr)?;
33693                    }
33694                } else {
33695                    self.generate_expression(expr)?;
33696                }
33697            }
33698            self.write(")");
33699        }
33700        Ok(())
33701    }
33702
33703    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
33704        // STUFF(this, start, length, expression)
33705        self.write_keyword("STUFF");
33706        self.write("(");
33707        self.generate_expression(&e.this)?;
33708        if let Some(start) = &e.start {
33709            self.write(", ");
33710            self.generate_expression(start)?;
33711        }
33712        if let Some(length) = e.length {
33713            self.write(", ");
33714            self.write(&length.to_string());
33715        }
33716        self.write(", ");
33717        self.generate_expression(&e.expression)?;
33718        self.write(")");
33719        Ok(())
33720    }
33721
33722    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
33723        // SUBSTRING_INDEX(this, delimiter, count)
33724        self.write_keyword("SUBSTRING_INDEX");
33725        self.write("(");
33726        self.generate_expression(&e.this)?;
33727        if let Some(delimiter) = &e.delimiter {
33728            self.write(", ");
33729            self.generate_expression(delimiter)?;
33730        }
33731        if let Some(count) = &e.count {
33732            self.write(", ");
33733            self.generate_expression(count)?;
33734        }
33735        self.write(")");
33736        Ok(())
33737    }
33738
33739    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
33740        // SUMMARIZE [TABLE] this
33741        self.write_keyword("SUMMARIZE");
33742        if e.table.is_some() {
33743            self.write_space();
33744            self.write_keyword("TABLE");
33745        }
33746        self.write_space();
33747        self.generate_expression(&e.this)?;
33748        Ok(())
33749    }
33750
33751    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
33752        // SYSTIMESTAMP
33753        self.write_keyword("SYSTIMESTAMP");
33754        Ok(())
33755    }
33756
33757    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
33758        // alias (columns...)
33759        if let Some(this) = &e.this {
33760            self.generate_expression(this)?;
33761        }
33762        if !e.columns.is_empty() {
33763            self.write("(");
33764            for (i, col) in e.columns.iter().enumerate() {
33765                if i > 0 {
33766                    self.write(", ");
33767                }
33768                self.generate_expression(col)?;
33769            }
33770            self.write(")");
33771        }
33772        Ok(())
33773    }
33774
33775    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
33776        // TABLE(this) [AS alias]
33777        self.write_keyword("TABLE");
33778        self.write("(");
33779        self.generate_expression(&e.this)?;
33780        self.write(")");
33781        if let Some(alias) = &e.alias {
33782            self.write_space();
33783            self.write_keyword("AS");
33784            self.write_space();
33785            self.write(alias);
33786        }
33787        Ok(())
33788    }
33789
33790    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
33791        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
33792        self.write_keyword("ROWS FROM");
33793        self.write(" (");
33794        for (i, expr) in e.expressions.iter().enumerate() {
33795            if i > 0 {
33796                self.write(", ");
33797            }
33798            // Each expression is either:
33799            // - A plain function (no alias)
33800            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
33801            match expr {
33802                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
33803                    // First element is the function, second is the TableAlias
33804                    self.generate_expression(&tuple.expressions[0])?;
33805                    self.write_space();
33806                    self.write_keyword("AS");
33807                    self.write_space();
33808                    self.generate_expression(&tuple.expressions[1])?;
33809                }
33810                _ => {
33811                    self.generate_expression(expr)?;
33812                }
33813            }
33814        }
33815        self.write(")");
33816        if e.ordinality {
33817            self.write_space();
33818            self.write_keyword("WITH ORDINALITY");
33819        }
33820        if let Some(alias) = &e.alias {
33821            self.write_space();
33822            self.write_keyword("AS");
33823            self.write_space();
33824            self.generate_expression(alias)?;
33825        }
33826        Ok(())
33827    }
33828
33829    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
33830        use crate::dialects::DialectType;
33831
33832        // New wrapper pattern: expression + Sample struct
33833        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
33834            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
33835            if self.config.alias_post_tablesample {
33836                // Handle Subquery with alias and Alias wrapper
33837                if let Expression::Subquery(ref s) = **this {
33838                    if let Some(ref alias) = s.alias {
33839                        // Create a clone without alias for output
33840                        let mut subquery_no_alias = (**s).clone();
33841                        subquery_no_alias.alias = None;
33842                        subquery_no_alias.column_aliases = Vec::new();
33843                        self.generate_expression(&Expression::Subquery(Box::new(
33844                            subquery_no_alias,
33845                        )))?;
33846                        self.write_space();
33847                        self.write_keyword("TABLESAMPLE");
33848                        self.generate_sample_body(sample)?;
33849                        if let Some(ref seed) = sample.seed {
33850                            self.write_space();
33851                            let use_seed = sample.use_seed_keyword
33852                                && !matches!(
33853                                    self.config.dialect,
33854                                    Some(crate::dialects::DialectType::Databricks)
33855                                        | Some(crate::dialects::DialectType::Spark)
33856                                );
33857                            if use_seed {
33858                                self.write_keyword("SEED");
33859                            } else {
33860                                self.write_keyword("REPEATABLE");
33861                            }
33862                            self.write(" (");
33863                            self.generate_expression(seed)?;
33864                            self.write(")");
33865                        }
33866                        self.write_space();
33867                        self.write_keyword("AS");
33868                        self.write_space();
33869                        self.generate_identifier(alias)?;
33870                        return Ok(());
33871                    }
33872                } else if let Expression::Alias(ref a) = **this {
33873                    // Output the base expression without alias
33874                    self.generate_expression(&a.this)?;
33875                    self.write_space();
33876                    self.write_keyword("TABLESAMPLE");
33877                    self.generate_sample_body(sample)?;
33878                    if let Some(ref seed) = sample.seed {
33879                        self.write_space();
33880                        let use_seed = sample.use_seed_keyword
33881                            && !matches!(
33882                                self.config.dialect,
33883                                Some(crate::dialects::DialectType::Databricks)
33884                                    | Some(crate::dialects::DialectType::Spark)
33885                            );
33886                        if use_seed {
33887                            self.write_keyword("SEED");
33888                        } else {
33889                            self.write_keyword("REPEATABLE");
33890                        }
33891                        self.write(" (");
33892                        self.generate_expression(seed)?;
33893                        self.write(")");
33894                    }
33895                    // Output alias after TABLESAMPLE
33896                    self.write_space();
33897                    self.write_keyword("AS");
33898                    self.write_space();
33899                    self.generate_identifier(&a.alias)?;
33900                    return Ok(());
33901                }
33902            }
33903            // Default: generate wrapped expression first, then TABLESAMPLE
33904            self.generate_expression(this)?;
33905            self.write_space();
33906            self.write_keyword("TABLESAMPLE");
33907            self.generate_sample_body(sample)?;
33908            // Seed for table-level sample
33909            if let Some(ref seed) = sample.seed {
33910                self.write_space();
33911                // Databricks uses REPEATABLE, not SEED
33912                let use_seed = sample.use_seed_keyword
33913                    && !matches!(
33914                        self.config.dialect,
33915                        Some(crate::dialects::DialectType::Databricks)
33916                            | Some(crate::dialects::DialectType::Spark)
33917                    );
33918                if use_seed {
33919                    self.write_keyword("SEED");
33920                } else {
33921                    self.write_keyword("REPEATABLE");
33922                }
33923                self.write(" (");
33924                self.generate_expression(seed)?;
33925                self.write(")");
33926            }
33927            return Ok(());
33928        }
33929
33930        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
33931        self.write_keyword("TABLESAMPLE");
33932        if let Some(method) = &e.method {
33933            self.write_space();
33934            self.write_keyword(method);
33935        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
33936            // Snowflake defaults to BERNOULLI when no method is specified
33937            self.write_space();
33938            self.write_keyword("BERNOULLI");
33939        }
33940        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
33941            self.write_space();
33942            self.write_keyword("BUCKET");
33943            self.write_space();
33944            self.generate_expression(numerator)?;
33945            self.write_space();
33946            self.write_keyword("OUT OF");
33947            self.write_space();
33948            self.generate_expression(denominator)?;
33949            if let Some(field) = &e.bucket_field {
33950                self.write_space();
33951                self.write_keyword("ON");
33952                self.write_space();
33953                self.generate_expression(field)?;
33954            }
33955        } else if !e.expressions.is_empty() {
33956            self.write(" (");
33957            for (i, expr) in e.expressions.iter().enumerate() {
33958                if i > 0 {
33959                    self.write(", ");
33960                }
33961                self.generate_expression(expr)?;
33962            }
33963            self.write(")");
33964        } else if let Some(percent) = &e.percent {
33965            self.write(" (");
33966            self.generate_expression(percent)?;
33967            self.write_space();
33968            self.write_keyword("PERCENT");
33969            self.write(")");
33970        }
33971        Ok(())
33972    }
33973
33974    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
33975        // [prefix]this[postfix]
33976        if let Some(prefix) = &e.prefix {
33977            self.generate_expression(prefix)?;
33978        }
33979        if let Some(this) = &e.this {
33980            self.generate_expression(this)?;
33981        }
33982        if let Some(postfix) = &e.postfix {
33983            self.generate_expression(postfix)?;
33984        }
33985        Ok(())
33986    }
33987
33988    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
33989        // TAG (expressions)
33990        self.write_keyword("TAG");
33991        self.write(" (");
33992        for (i, expr) in e.expressions.iter().enumerate() {
33993            if i > 0 {
33994                self.write(", ");
33995            }
33996            self.generate_expression(expr)?;
33997        }
33998        self.write(")");
33999        Ok(())
34000    }
34001
34002    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
34003        // TEMPORARY or TEMP or [this] TEMPORARY
34004        if let Some(this) = &e.this {
34005            self.generate_expression(this)?;
34006            self.write_space();
34007        }
34008        self.write_keyword("TEMPORARY");
34009        Ok(())
34010    }
34011
34012    /// Generate a Time function expression
34013    /// For most dialects: TIME('value')
34014    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
34015        // Standard: TIME(value)
34016        self.write_keyword("TIME");
34017        self.write("(");
34018        self.generate_expression(&e.this)?;
34019        self.write(")");
34020        Ok(())
34021    }
34022
34023    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
34024        // TIME_ADD(this, expression, unit)
34025        self.write_keyword("TIME_ADD");
34026        self.write("(");
34027        self.generate_expression(&e.this)?;
34028        self.write(", ");
34029        self.generate_expression(&e.expression)?;
34030        if let Some(unit) = &e.unit {
34031            self.write(", ");
34032            self.write_keyword(unit);
34033        }
34034        self.write(")");
34035        Ok(())
34036    }
34037
34038    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
34039        // TIME_DIFF(this, expression, unit)
34040        self.write_keyword("TIME_DIFF");
34041        self.write("(");
34042        self.generate_expression(&e.this)?;
34043        self.write(", ");
34044        self.generate_expression(&e.expression)?;
34045        if let Some(unit) = &e.unit {
34046            self.write(", ");
34047            self.write_keyword(unit);
34048        }
34049        self.write(")");
34050        Ok(())
34051    }
34052
34053    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
34054        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
34055        self.write_keyword("TIME_FROM_PARTS");
34056        self.write("(");
34057        let mut first = true;
34058        if let Some(hour) = &e.hour {
34059            self.generate_expression(hour)?;
34060            first = false;
34061        }
34062        if let Some(minute) = &e.min {
34063            if !first {
34064                self.write(", ");
34065            }
34066            self.generate_expression(minute)?;
34067            first = false;
34068        }
34069        if let Some(second) = &e.sec {
34070            if !first {
34071                self.write(", ");
34072            }
34073            self.generate_expression(second)?;
34074            first = false;
34075        }
34076        if let Some(ns) = &e.nano {
34077            if !first {
34078                self.write(", ");
34079            }
34080            self.generate_expression(ns)?;
34081        }
34082        self.write(")");
34083        Ok(())
34084    }
34085
34086    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
34087        // TIME_SLICE(this, expression, unit)
34088        self.write_keyword("TIME_SLICE");
34089        self.write("(");
34090        self.generate_expression(&e.this)?;
34091        self.write(", ");
34092        self.generate_expression(&e.expression)?;
34093        self.write(", ");
34094        self.write_keyword(&e.unit);
34095        self.write(")");
34096        Ok(())
34097    }
34098
34099    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
34100        // TIME_STR_TO_TIME(this)
34101        self.write_keyword("TIME_STR_TO_TIME");
34102        self.write("(");
34103        self.generate_expression(&e.this)?;
34104        self.write(")");
34105        Ok(())
34106    }
34107
34108    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
34109        // TIME_SUB(this, expression, unit)
34110        self.write_keyword("TIME_SUB");
34111        self.write("(");
34112        self.generate_expression(&e.this)?;
34113        self.write(", ");
34114        self.generate_expression(&e.expression)?;
34115        if let Some(unit) = &e.unit {
34116            self.write(", ");
34117            self.write_keyword(unit);
34118        }
34119        self.write(")");
34120        Ok(())
34121    }
34122
34123    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
34124        match self.config.dialect {
34125            Some(DialectType::Exasol) => {
34126                // Exasol uses TO_CHAR with Exasol-specific format
34127                self.write_keyword("TO_CHAR");
34128                self.write("(");
34129                self.generate_expression(&e.this)?;
34130                self.write(", '");
34131                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
34132                self.write("'");
34133                self.write(")");
34134            }
34135            Some(DialectType::PostgreSQL)
34136            | Some(DialectType::Redshift)
34137            | Some(DialectType::Materialize) => {
34138                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
34139                self.write_keyword("TO_CHAR");
34140                self.write("(");
34141                self.generate_expression(&e.this)?;
34142                self.write(", '");
34143                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
34144                self.write("'");
34145                self.write(")");
34146            }
34147            Some(DialectType::Oracle) => {
34148                // Oracle uses TO_CHAR with PG-like format
34149                self.write_keyword("TO_CHAR");
34150                self.write("(");
34151                self.generate_expression(&e.this)?;
34152                self.write(", '");
34153                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
34154                self.write("'");
34155                self.write(")");
34156            }
34157            Some(DialectType::Drill) => {
34158                // Drill: TO_CHAR with Java format
34159                self.write_keyword("TO_CHAR");
34160                self.write("(");
34161                self.generate_expression(&e.this)?;
34162                self.write(", '");
34163                self.write(&Self::strftime_to_java_format(&e.format));
34164                self.write("'");
34165                self.write(")");
34166            }
34167            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
34168                // TSQL: FORMAT(value, format) with .NET-style format
34169                self.write_keyword("FORMAT");
34170                self.write("(");
34171                self.generate_expression(&e.this)?;
34172                self.write(", '");
34173                self.write(&Self::strftime_to_tsql_format(&e.format));
34174                self.write("'");
34175                self.write(")");
34176            }
34177            Some(DialectType::DuckDB) => {
34178                // DuckDB: STRFTIME(value, format) - keeps C format
34179                self.write_keyword("STRFTIME");
34180                self.write("(");
34181                self.generate_expression(&e.this)?;
34182                self.write(", '");
34183                self.write(&e.format);
34184                self.write("'");
34185                self.write(")");
34186            }
34187            Some(DialectType::BigQuery) => {
34188                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
34189                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
34190                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
34191                self.write_keyword("FORMAT_DATE");
34192                self.write("('");
34193                self.write(&fmt);
34194                self.write("', ");
34195                self.generate_expression(&e.this)?;
34196                self.write(")");
34197            }
34198            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
34199                // Hive/Spark: DATE_FORMAT(value, java_format)
34200                self.write_keyword("DATE_FORMAT");
34201                self.write("(");
34202                self.generate_expression(&e.this)?;
34203                self.write(", '");
34204                self.write(&Self::strftime_to_java_format(&e.format));
34205                self.write("'");
34206                self.write(")");
34207            }
34208            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
34209                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
34210                self.write_keyword("DATE_FORMAT");
34211                self.write("(");
34212                self.generate_expression(&e.this)?;
34213                self.write(", '");
34214                self.write(&e.format);
34215                self.write("'");
34216                self.write(")");
34217            }
34218            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
34219                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
34220                self.write_keyword("DATE_FORMAT");
34221                self.write("(");
34222                self.generate_expression(&e.this)?;
34223                self.write(", '");
34224                self.write(&e.format);
34225                self.write("'");
34226                self.write(")");
34227            }
34228            _ => {
34229                // Default: TIME_TO_STR(this, format)
34230                self.write_keyword("TIME_TO_STR");
34231                self.write("(");
34232                self.generate_expression(&e.this)?;
34233                self.write(", '");
34234                self.write(&e.format);
34235                self.write("'");
34236                self.write(")");
34237            }
34238        }
34239        Ok(())
34240    }
34241
34242    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
34243        match self.config.dialect {
34244            Some(DialectType::DuckDB) => {
34245                // DuckDB: EPOCH(x)
34246                self.write_keyword("EPOCH");
34247                self.write("(");
34248                self.generate_expression(&e.this)?;
34249                self.write(")");
34250            }
34251            Some(DialectType::Hive)
34252            | Some(DialectType::Spark)
34253            | Some(DialectType::Databricks)
34254            | Some(DialectType::Doris)
34255            | Some(DialectType::StarRocks)
34256            | Some(DialectType::Drill) => {
34257                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
34258                self.write_keyword("UNIX_TIMESTAMP");
34259                self.write("(");
34260                self.generate_expression(&e.this)?;
34261                self.write(")");
34262            }
34263            Some(DialectType::Presto) | Some(DialectType::Trino) => {
34264                // Presto: TO_UNIXTIME(x)
34265                self.write_keyword("TO_UNIXTIME");
34266                self.write("(");
34267                self.generate_expression(&e.this)?;
34268                self.write(")");
34269            }
34270            _ => {
34271                // Default: TIME_TO_UNIX(x)
34272                self.write_keyword("TIME_TO_UNIX");
34273                self.write("(");
34274                self.generate_expression(&e.this)?;
34275                self.write(")");
34276            }
34277        }
34278        Ok(())
34279    }
34280
34281    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
34282        match self.config.dialect {
34283            Some(DialectType::Hive) => {
34284                // Hive: TO_DATE(x)
34285                self.write_keyword("TO_DATE");
34286                self.write("(");
34287                self.generate_expression(&e.this)?;
34288                self.write(")");
34289            }
34290            _ => {
34291                // Default: TIME_STR_TO_DATE(x)
34292                self.write_keyword("TIME_STR_TO_DATE");
34293                self.write("(");
34294                self.generate_expression(&e.this)?;
34295                self.write(")");
34296            }
34297        }
34298        Ok(())
34299    }
34300
34301    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
34302        // TIME_TRUNC(this, unit)
34303        self.write_keyword("TIME_TRUNC");
34304        self.write("(");
34305        self.generate_expression(&e.this)?;
34306        self.write(", ");
34307        self.write_keyword(&e.unit);
34308        self.write(")");
34309        Ok(())
34310    }
34311
34312    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
34313        // Just output the unit name
34314        if let Some(unit) = &e.unit {
34315            self.write_keyword(unit);
34316        }
34317        Ok(())
34318    }
34319
34320    /// Generate a Timestamp function expression
34321    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
34322    /// For other dialects: TIMESTAMP('value')
34323    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
34324        use crate::dialects::DialectType;
34325        use crate::expressions::Literal;
34326
34327        match self.config.dialect {
34328            // Exasol uses TO_TIMESTAMP for Timestamp expressions
34329            Some(DialectType::Exasol) => {
34330                self.write_keyword("TO_TIMESTAMP");
34331                self.write("(");
34332                // Extract the string value from the expression if it's a string literal
34333                if let Some(this) = &e.this {
34334                    match this.as_ref() {
34335                        Expression::Literal(Literal::String(s)) => {
34336                            self.write("'");
34337                            self.write(s);
34338                            self.write("'");
34339                        }
34340                        _ => {
34341                            self.generate_expression(this)?;
34342                        }
34343                    }
34344                }
34345                self.write(")");
34346            }
34347            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
34348            _ => {
34349                self.write_keyword("TIMESTAMP");
34350                self.write("(");
34351                if let Some(this) = &e.this {
34352                    self.generate_expression(this)?;
34353                }
34354                if let Some(zone) = &e.zone {
34355                    self.write(", ");
34356                    self.generate_expression(zone)?;
34357                }
34358                self.write(")");
34359            }
34360        }
34361        Ok(())
34362    }
34363
34364    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
34365        // TIMESTAMP_ADD(this, expression, unit)
34366        self.write_keyword("TIMESTAMP_ADD");
34367        self.write("(");
34368        self.generate_expression(&e.this)?;
34369        self.write(", ");
34370        self.generate_expression(&e.expression)?;
34371        if let Some(unit) = &e.unit {
34372            self.write(", ");
34373            self.write_keyword(unit);
34374        }
34375        self.write(")");
34376        Ok(())
34377    }
34378
34379    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
34380        // TIMESTAMP_DIFF(this, expression, unit)
34381        self.write_keyword("TIMESTAMP_DIFF");
34382        self.write("(");
34383        self.generate_expression(&e.this)?;
34384        self.write(", ");
34385        self.generate_expression(&e.expression)?;
34386        if let Some(unit) = &e.unit {
34387            self.write(", ");
34388            self.write_keyword(unit);
34389        }
34390        self.write(")");
34391        Ok(())
34392    }
34393
34394    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
34395        // TIMESTAMP_FROM_PARTS(this, expression)
34396        self.write_keyword("TIMESTAMP_FROM_PARTS");
34397        self.write("(");
34398        if let Some(this) = &e.this {
34399            self.generate_expression(this)?;
34400        }
34401        if let Some(expression) = &e.expression {
34402            self.write(", ");
34403            self.generate_expression(expression)?;
34404        }
34405        if let Some(zone) = &e.zone {
34406            self.write(", ");
34407            self.generate_expression(zone)?;
34408        }
34409        if let Some(milli) = &e.milli {
34410            self.write(", ");
34411            self.generate_expression(milli)?;
34412        }
34413        self.write(")");
34414        Ok(())
34415    }
34416
34417    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
34418        // TIMESTAMP_SUB(this, INTERVAL expression unit)
34419        self.write_keyword("TIMESTAMP_SUB");
34420        self.write("(");
34421        self.generate_expression(&e.this)?;
34422        self.write(", ");
34423        self.write_keyword("INTERVAL");
34424        self.write_space();
34425        self.generate_expression(&e.expression)?;
34426        if let Some(unit) = &e.unit {
34427            self.write_space();
34428            self.write_keyword(unit);
34429        }
34430        self.write(")");
34431        Ok(())
34432    }
34433
34434    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
34435        // TIMESTAMP_TZ_FROM_PARTS(...)
34436        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
34437        self.write("(");
34438        if let Some(zone) = &e.zone {
34439            self.generate_expression(zone)?;
34440        }
34441        self.write(")");
34442        Ok(())
34443    }
34444
34445    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
34446        // TO_BINARY(this, [format])
34447        self.write_keyword("TO_BINARY");
34448        self.write("(");
34449        self.generate_expression(&e.this)?;
34450        if let Some(format) = &e.format {
34451            self.write(", '");
34452            self.write(format);
34453            self.write("'");
34454        }
34455        self.write(")");
34456        Ok(())
34457    }
34458
34459    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
34460        // TO_BOOLEAN(this)
34461        self.write_keyword("TO_BOOLEAN");
34462        self.write("(");
34463        self.generate_expression(&e.this)?;
34464        self.write(")");
34465        Ok(())
34466    }
34467
34468    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
34469        // TO_CHAR(this, [format], [nlsparam])
34470        self.write_keyword("TO_CHAR");
34471        self.write("(");
34472        self.generate_expression(&e.this)?;
34473        if let Some(format) = &e.format {
34474            self.write(", '");
34475            self.write(format);
34476            self.write("'");
34477        }
34478        if let Some(nlsparam) = &e.nlsparam {
34479            self.write(", ");
34480            self.generate_expression(nlsparam)?;
34481        }
34482        self.write(")");
34483        Ok(())
34484    }
34485
34486    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
34487        // TO_DECFLOAT(this, [format])
34488        self.write_keyword("TO_DECFLOAT");
34489        self.write("(");
34490        self.generate_expression(&e.this)?;
34491        if let Some(format) = &e.format {
34492            self.write(", '");
34493            self.write(format);
34494            self.write("'");
34495        }
34496        self.write(")");
34497        Ok(())
34498    }
34499
34500    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
34501        // TO_DOUBLE(this, [format])
34502        self.write_keyword("TO_DOUBLE");
34503        self.write("(");
34504        self.generate_expression(&e.this)?;
34505        if let Some(format) = &e.format {
34506            self.write(", '");
34507            self.write(format);
34508            self.write("'");
34509        }
34510        self.write(")");
34511        Ok(())
34512    }
34513
34514    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
34515        // TO_FILE(this, path)
34516        self.write_keyword("TO_FILE");
34517        self.write("(");
34518        self.generate_expression(&e.this)?;
34519        if let Some(path) = &e.path {
34520            self.write(", ");
34521            self.generate_expression(path)?;
34522        }
34523        self.write(")");
34524        Ok(())
34525    }
34526
34527    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
34528        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
34529        // If safe flag is set, output TRY_TO_NUMBER
34530        let is_safe = e.safe.is_some();
34531        if is_safe {
34532            self.write_keyword("TRY_TO_NUMBER");
34533        } else {
34534            self.write_keyword("TO_NUMBER");
34535        }
34536        self.write("(");
34537        self.generate_expression(&e.this)?;
34538        if let Some(format) = &e.format {
34539            self.write(", ");
34540            self.generate_expression(format)?;
34541        }
34542        if let Some(nlsparam) = &e.nlsparam {
34543            self.write(", ");
34544            self.generate_expression(nlsparam)?;
34545        }
34546        if let Some(precision) = &e.precision {
34547            self.write(", ");
34548            self.generate_expression(precision)?;
34549        }
34550        if let Some(scale) = &e.scale {
34551            self.write(", ");
34552            self.generate_expression(scale)?;
34553        }
34554        self.write(")");
34555        Ok(())
34556    }
34557
34558    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
34559        // TO_TABLE this
34560        self.write_keyword("TO_TABLE");
34561        self.write_space();
34562        self.generate_expression(&e.this)?;
34563        Ok(())
34564    }
34565
34566    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
34567        // Check mark to determine the format
34568        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
34569            Expression::Identifier(id) => id.name.clone(),
34570            Expression::Literal(Literal::String(s)) => s.clone(),
34571            _ => String::new(),
34572        });
34573
34574        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
34575        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
34576        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
34577            matches!(m.as_ref(), Expression::Literal(Literal::String(_)))
34578        });
34579
34580        // For Presto/Trino: always use START TRANSACTION
34581        let use_start_transaction = matches!(
34582            self.config.dialect,
34583            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
34584        );
34585        // For most dialects: strip TRANSACTION keyword
34586        let strip_transaction = matches!(
34587            self.config.dialect,
34588            Some(DialectType::Snowflake)
34589                | Some(DialectType::PostgreSQL)
34590                | Some(DialectType::Redshift)
34591                | Some(DialectType::MySQL)
34592                | Some(DialectType::Hive)
34593                | Some(DialectType::Spark)
34594                | Some(DialectType::Databricks)
34595                | Some(DialectType::DuckDB)
34596                | Some(DialectType::Oracle)
34597                | Some(DialectType::Doris)
34598                | Some(DialectType::StarRocks)
34599                | Some(DialectType::Materialize)
34600                | Some(DialectType::ClickHouse)
34601        );
34602
34603        if is_start || use_start_transaction {
34604            // START TRANSACTION [modes]
34605            self.write_keyword("START TRANSACTION");
34606            if let Some(modes) = &e.modes {
34607                self.write_space();
34608                self.generate_expression(modes)?;
34609            }
34610        } else {
34611            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
34612            self.write_keyword("BEGIN");
34613
34614            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
34615            let is_kind = e.this.as_ref().map_or(false, |t| {
34616                if let Expression::Identifier(id) = t.as_ref() {
34617                    matches!(
34618                        id.name.to_uppercase().as_str(),
34619                        "DEFERRED" | "IMMEDIATE" | "EXCLUSIVE"
34620                    )
34621                } else {
34622                    false
34623                }
34624            });
34625
34626            // Output kind before TRANSACTION keyword
34627            if is_kind {
34628                if let Some(this) = &e.this {
34629                    self.write_space();
34630                    if let Expression::Identifier(id) = this.as_ref() {
34631                        self.write_keyword(&id.name);
34632                    }
34633                }
34634            }
34635
34636            // Output TRANSACTION keyword if it was present and target supports it
34637            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
34638                self.write_space();
34639                self.write_keyword("TRANSACTION");
34640            }
34641
34642            // Output transaction name (not kind)
34643            if !is_kind {
34644                if let Some(this) = &e.this {
34645                    self.write_space();
34646                    self.generate_expression(this)?;
34647                }
34648            }
34649
34650            // Output WITH MARK 'description' for TSQL
34651            if has_with_mark {
34652                self.write_space();
34653                self.write_keyword("WITH MARK");
34654                if let Some(Expression::Literal(Literal::String(desc))) = e.mark.as_deref() {
34655                    if !desc.is_empty() {
34656                        self.write_space();
34657                        self.write(&format!("'{}'", desc));
34658                    }
34659                }
34660            }
34661
34662            // Output modes (isolation levels, etc.)
34663            if let Some(modes) = &e.modes {
34664                self.write_space();
34665                self.generate_expression(modes)?;
34666            }
34667        }
34668        Ok(())
34669    }
34670
34671    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
34672        // TRANSFORM(this, expression)
34673        self.write_keyword("TRANSFORM");
34674        self.write("(");
34675        self.generate_expression(&e.this)?;
34676        self.write(", ");
34677        self.generate_expression(&e.expression)?;
34678        self.write(")");
34679        Ok(())
34680    }
34681
34682    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
34683        // TRANSFORM(expressions)
34684        self.write_keyword("TRANSFORM");
34685        self.write("(");
34686        if self.config.pretty && !e.expressions.is_empty() {
34687            self.indent_level += 1;
34688            for (i, expr) in e.expressions.iter().enumerate() {
34689                if i > 0 {
34690                    self.write(",");
34691                }
34692                self.write_newline();
34693                self.write_indent();
34694                self.generate_expression(expr)?;
34695            }
34696            self.indent_level -= 1;
34697            self.write_newline();
34698            self.write(")");
34699        } else {
34700            for (i, expr) in e.expressions.iter().enumerate() {
34701                if i > 0 {
34702                    self.write(", ");
34703                }
34704                self.generate_expression(expr)?;
34705            }
34706            self.write(")");
34707        }
34708        Ok(())
34709    }
34710
34711    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
34712        use crate::dialects::DialectType;
34713        // TRANSIENT is Snowflake-specific; skip for other dialects
34714        if let Some(this) = &e.this {
34715            self.generate_expression(this)?;
34716            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
34717                self.write_space();
34718            }
34719        }
34720        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
34721            self.write_keyword("TRANSIENT");
34722        }
34723        Ok(())
34724    }
34725
34726    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
34727        // TRANSLATE(this, from_, to)
34728        self.write_keyword("TRANSLATE");
34729        self.write("(");
34730        self.generate_expression(&e.this)?;
34731        if let Some(from) = &e.from_ {
34732            self.write(", ");
34733            self.generate_expression(from)?;
34734        }
34735        if let Some(to) = &e.to {
34736            self.write(", ");
34737            self.generate_expression(to)?;
34738        }
34739        self.write(")");
34740        Ok(())
34741    }
34742
34743    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
34744        // TRANSLATE(this USING expression)
34745        self.write_keyword("TRANSLATE");
34746        self.write("(");
34747        self.generate_expression(&e.this)?;
34748        self.write_space();
34749        self.write_keyword("USING");
34750        self.write_space();
34751        self.generate_expression(&e.expression)?;
34752        if e.with_error.is_some() {
34753            self.write_space();
34754            self.write_keyword("WITH ERROR");
34755        }
34756        self.write(")");
34757        Ok(())
34758    }
34759
34760    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
34761        // TRUNCATE TABLE table1, table2, ...
34762        self.write_keyword("TRUNCATE TABLE");
34763        self.write_space();
34764        for (i, expr) in e.expressions.iter().enumerate() {
34765            if i > 0 {
34766                self.write(", ");
34767            }
34768            self.generate_expression(expr)?;
34769        }
34770        Ok(())
34771    }
34772
34773    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
34774        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
34775        self.write_keyword("TRY_BASE64_DECODE_BINARY");
34776        self.write("(");
34777        self.generate_expression(&e.this)?;
34778        if let Some(alphabet) = &e.alphabet {
34779            self.write(", ");
34780            self.generate_expression(alphabet)?;
34781        }
34782        self.write(")");
34783        Ok(())
34784    }
34785
34786    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
34787        // TRY_BASE64_DECODE_STRING(this, [alphabet])
34788        self.write_keyword("TRY_BASE64_DECODE_STRING");
34789        self.write("(");
34790        self.generate_expression(&e.this)?;
34791        if let Some(alphabet) = &e.alphabet {
34792            self.write(", ");
34793            self.generate_expression(alphabet)?;
34794        }
34795        self.write(")");
34796        Ok(())
34797    }
34798
34799    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
34800        // TRY_TO_DECFLOAT(this, [format])
34801        self.write_keyword("TRY_TO_DECFLOAT");
34802        self.write("(");
34803        self.generate_expression(&e.this)?;
34804        if let Some(format) = &e.format {
34805            self.write(", '");
34806            self.write(format);
34807            self.write("'");
34808        }
34809        self.write(")");
34810        Ok(())
34811    }
34812
34813    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
34814        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
34815        self.write_keyword("TS_OR_DS_ADD");
34816        self.write("(");
34817        self.generate_expression(&e.this)?;
34818        self.write(", ");
34819        self.generate_expression(&e.expression)?;
34820        if let Some(unit) = &e.unit {
34821            self.write(", ");
34822            self.write_keyword(unit);
34823        }
34824        if let Some(return_type) = &e.return_type {
34825            self.write(", ");
34826            self.generate_expression(return_type)?;
34827        }
34828        self.write(")");
34829        Ok(())
34830    }
34831
34832    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
34833        // TS_OR_DS_DIFF(this, expression, [unit])
34834        self.write_keyword("TS_OR_DS_DIFF");
34835        self.write("(");
34836        self.generate_expression(&e.this)?;
34837        self.write(", ");
34838        self.generate_expression(&e.expression)?;
34839        if let Some(unit) = &e.unit {
34840            self.write(", ");
34841            self.write_keyword(unit);
34842        }
34843        self.write(")");
34844        Ok(())
34845    }
34846
34847    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
34848        let default_time_format = "%Y-%m-%d %H:%M:%S";
34849        let default_date_format = "%Y-%m-%d";
34850        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
34851            f != default_time_format && f != default_date_format
34852        });
34853
34854        if has_non_default_format {
34855            // With non-default format: dialect-specific handling
34856            let fmt = e.format.as_ref().unwrap();
34857            match self.config.dialect {
34858                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
34859                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
34860                    // STR_TO_DATE is the MySQL-native form of StrToTime
34861                    let str_to_time = crate::expressions::StrToTime {
34862                        this: Box::new((*e.this).clone()),
34863                        format: fmt.clone(),
34864                        zone: None,
34865                        safe: None,
34866                        target_type: None,
34867                    };
34868                    self.generate_str_to_time(&str_to_time)?;
34869                }
34870                Some(DialectType::Hive)
34871                | Some(DialectType::Spark)
34872                | Some(DialectType::Databricks) => {
34873                    // Hive/Spark: TO_DATE(x, java_fmt)
34874                    self.write_keyword("TO_DATE");
34875                    self.write("(");
34876                    self.generate_expression(&e.this)?;
34877                    self.write(", '");
34878                    self.write(&Self::strftime_to_java_format(fmt));
34879                    self.write("')");
34880                }
34881                Some(DialectType::Snowflake) => {
34882                    // Snowflake: TO_DATE(x, snowflake_fmt)
34883                    self.write_keyword("TO_DATE");
34884                    self.write("(");
34885                    self.generate_expression(&e.this)?;
34886                    self.write(", '");
34887                    self.write(&Self::strftime_to_snowflake_format(fmt));
34888                    self.write("')");
34889                }
34890                Some(DialectType::Doris) => {
34891                    // Doris: TO_DATE(x) - ignores format
34892                    self.write_keyword("TO_DATE");
34893                    self.write("(");
34894                    self.generate_expression(&e.this)?;
34895                    self.write(")");
34896                }
34897                _ => {
34898                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
34899                    self.write_keyword("CAST");
34900                    self.write("(");
34901                    let str_to_time = crate::expressions::StrToTime {
34902                        this: Box::new((*e.this).clone()),
34903                        format: fmt.clone(),
34904                        zone: None,
34905                        safe: None,
34906                        target_type: None,
34907                    };
34908                    self.generate_str_to_time(&str_to_time)?;
34909                    self.write_keyword(" AS ");
34910                    self.write_keyword("DATE");
34911                    self.write(")");
34912                }
34913            }
34914        } else {
34915            // Without format (or default format): simple date conversion
34916            match self.config.dialect {
34917                Some(DialectType::MySQL)
34918                | Some(DialectType::SQLite)
34919                | Some(DialectType::StarRocks) => {
34920                    // MySQL/SQLite/StarRocks: DATE(x)
34921                    self.write_keyword("DATE");
34922                    self.write("(");
34923                    self.generate_expression(&e.this)?;
34924                    self.write(")");
34925                }
34926                Some(DialectType::Hive)
34927                | Some(DialectType::Spark)
34928                | Some(DialectType::Databricks)
34929                | Some(DialectType::Snowflake)
34930                | Some(DialectType::Doris) => {
34931                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
34932                    self.write_keyword("TO_DATE");
34933                    self.write("(");
34934                    self.generate_expression(&e.this)?;
34935                    self.write(")");
34936                }
34937                Some(DialectType::Presto)
34938                | Some(DialectType::Trino)
34939                | Some(DialectType::Athena) => {
34940                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
34941                    self.write_keyword("CAST");
34942                    self.write("(");
34943                    self.write_keyword("CAST");
34944                    self.write("(");
34945                    self.generate_expression(&e.this)?;
34946                    self.write_keyword(" AS ");
34947                    self.write_keyword("TIMESTAMP");
34948                    self.write(")");
34949                    self.write_keyword(" AS ");
34950                    self.write_keyword("DATE");
34951                    self.write(")");
34952                }
34953                Some(DialectType::ClickHouse) => {
34954                    // ClickHouse: CAST(x AS Nullable(DATE))
34955                    self.write_keyword("CAST");
34956                    self.write("(");
34957                    self.generate_expression(&e.this)?;
34958                    self.write_keyword(" AS ");
34959                    self.write("Nullable(DATE)");
34960                    self.write(")");
34961                }
34962                _ => {
34963                    // Default: CAST(x AS DATE)
34964                    self.write_keyword("CAST");
34965                    self.write("(");
34966                    self.generate_expression(&e.this)?;
34967                    self.write_keyword(" AS ");
34968                    self.write_keyword("DATE");
34969                    self.write(")");
34970                }
34971            }
34972        }
34973        Ok(())
34974    }
34975
34976    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
34977        // TS_OR_DS_TO_TIME(this, [format])
34978        self.write_keyword("TS_OR_DS_TO_TIME");
34979        self.write("(");
34980        self.generate_expression(&e.this)?;
34981        if let Some(format) = &e.format {
34982            self.write(", '");
34983            self.write(format);
34984            self.write("'");
34985        }
34986        self.write(")");
34987        Ok(())
34988    }
34989
34990    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
34991        // UNHEX(this, [expression])
34992        self.write_keyword("UNHEX");
34993        self.write("(");
34994        self.generate_expression(&e.this)?;
34995        if let Some(expression) = &e.expression {
34996            self.write(", ");
34997            self.generate_expression(expression)?;
34998        }
34999        self.write(")");
35000        Ok(())
35001    }
35002
35003    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
35004        // U&this [UESCAPE escape]
35005        self.write("U&");
35006        self.generate_expression(&e.this)?;
35007        if let Some(escape) = &e.escape {
35008            self.write_space();
35009            self.write_keyword("UESCAPE");
35010            self.write_space();
35011            self.generate_expression(escape)?;
35012        }
35013        Ok(())
35014    }
35015
35016    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
35017        // UNIFORM(this, expression, [gen], [seed])
35018        self.write_keyword("UNIFORM");
35019        self.write("(");
35020        self.generate_expression(&e.this)?;
35021        self.write(", ");
35022        self.generate_expression(&e.expression)?;
35023        if let Some(gen) = &e.gen {
35024            self.write(", ");
35025            self.generate_expression(gen)?;
35026        }
35027        if let Some(seed) = &e.seed {
35028            self.write(", ");
35029            self.generate_expression(seed)?;
35030        }
35031        self.write(")");
35032        Ok(())
35033    }
35034
35035    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
35036        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
35037        self.write_keyword("UNIQUE");
35038        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
35039        if e.nulls.is_some() {
35040            self.write(" NULLS NOT DISTINCT");
35041        }
35042        if let Some(this) = &e.this {
35043            self.write_space();
35044            self.generate_expression(this)?;
35045        }
35046        if let Some(index_type) = &e.index_type {
35047            self.write(" USING ");
35048            self.generate_expression(index_type)?;
35049        }
35050        if let Some(on_conflict) = &e.on_conflict {
35051            self.write_space();
35052            self.generate_expression(on_conflict)?;
35053        }
35054        for opt in &e.options {
35055            self.write_space();
35056            self.generate_expression(opt)?;
35057        }
35058        Ok(())
35059    }
35060
35061    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
35062        // UNIQUE KEY (expressions)
35063        self.write_keyword("UNIQUE KEY");
35064        self.write(" (");
35065        for (i, expr) in e.expressions.iter().enumerate() {
35066            if i > 0 {
35067                self.write(", ");
35068            }
35069            self.generate_expression(expr)?;
35070        }
35071        self.write(")");
35072        Ok(())
35073    }
35074
35075    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
35076        // ROLLUP (r1(col1, col2), r2(col1))
35077        self.write_keyword("ROLLUP");
35078        self.write(" (");
35079        for (i, index) in e.expressions.iter().enumerate() {
35080            if i > 0 {
35081                self.write(", ");
35082            }
35083            self.generate_identifier(&index.name)?;
35084            self.write("(");
35085            for (j, col) in index.expressions.iter().enumerate() {
35086                if j > 0 {
35087                    self.write(", ");
35088                }
35089                self.generate_identifier(col)?;
35090            }
35091            self.write(")");
35092        }
35093        self.write(")");
35094        Ok(())
35095    }
35096
35097    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
35098        match self.config.dialect {
35099            Some(DialectType::DuckDB) => {
35100                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
35101                self.write_keyword("STRFTIME");
35102                self.write("(");
35103                self.write_keyword("TO_TIMESTAMP");
35104                self.write("(");
35105                self.generate_expression(&e.this)?;
35106                self.write("), '");
35107                if let Some(format) = &e.format {
35108                    self.write(format);
35109                }
35110                self.write("')");
35111            }
35112            Some(DialectType::Hive) => {
35113                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
35114                self.write_keyword("FROM_UNIXTIME");
35115                self.write("(");
35116                self.generate_expression(&e.this)?;
35117                if let Some(format) = &e.format {
35118                    if format != "yyyy-MM-dd HH:mm:ss" {
35119                        self.write(", '");
35120                        self.write(format);
35121                        self.write("'");
35122                    }
35123                }
35124                self.write(")");
35125            }
35126            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35127                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
35128                self.write_keyword("DATE_FORMAT");
35129                self.write("(");
35130                self.write_keyword("FROM_UNIXTIME");
35131                self.write("(");
35132                self.generate_expression(&e.this)?;
35133                self.write("), '");
35134                if let Some(format) = &e.format {
35135                    self.write(format);
35136                }
35137                self.write("')");
35138            }
35139            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35140                // Spark: FROM_UNIXTIME(value, format)
35141                self.write_keyword("FROM_UNIXTIME");
35142                self.write("(");
35143                self.generate_expression(&e.this)?;
35144                if let Some(format) = &e.format {
35145                    self.write(", '");
35146                    self.write(format);
35147                    self.write("'");
35148                }
35149                self.write(")");
35150            }
35151            _ => {
35152                // Default: UNIX_TO_STR(this, [format])
35153                self.write_keyword("UNIX_TO_STR");
35154                self.write("(");
35155                self.generate_expression(&e.this)?;
35156                if let Some(format) = &e.format {
35157                    self.write(", '");
35158                    self.write(format);
35159                    self.write("'");
35160                }
35161                self.write(")");
35162            }
35163        }
35164        Ok(())
35165    }
35166
35167    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
35168        use crate::dialects::DialectType;
35169        let scale = e.scale.unwrap_or(0); // 0 = seconds
35170
35171        match self.config.dialect {
35172            Some(DialectType::Snowflake) => {
35173                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
35174                self.write_keyword("TO_TIMESTAMP");
35175                self.write("(");
35176                self.generate_expression(&e.this)?;
35177                if let Some(s) = e.scale {
35178                    if s > 0 {
35179                        self.write(", ");
35180                        self.write(&s.to_string());
35181                    }
35182                }
35183                self.write(")");
35184            }
35185            Some(DialectType::BigQuery) => {
35186                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
35187                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
35188                match scale {
35189                    0 => {
35190                        self.write_keyword("TIMESTAMP_SECONDS");
35191                        self.write("(");
35192                        self.generate_expression(&e.this)?;
35193                        self.write(")");
35194                    }
35195                    3 => {
35196                        self.write_keyword("TIMESTAMP_MILLIS");
35197                        self.write("(");
35198                        self.generate_expression(&e.this)?;
35199                        self.write(")");
35200                    }
35201                    6 => {
35202                        self.write_keyword("TIMESTAMP_MICROS");
35203                        self.write("(");
35204                        self.generate_expression(&e.this)?;
35205                        self.write(")");
35206                    }
35207                    _ => {
35208                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
35209                        self.write_keyword("TIMESTAMP_SECONDS");
35210                        self.write("(CAST(");
35211                        self.generate_expression(&e.this)?;
35212                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
35213                    }
35214                }
35215            }
35216            Some(DialectType::Spark) => {
35217                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
35218                // TIMESTAMP_MILLIS(value) for scale=3
35219                // TIMESTAMP_MICROS(value) for scale=6
35220                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
35221                match scale {
35222                    0 => {
35223                        self.write_keyword("CAST");
35224                        self.write("(");
35225                        self.write_keyword("FROM_UNIXTIME");
35226                        self.write("(");
35227                        self.generate_expression(&e.this)?;
35228                        self.write(") ");
35229                        self.write_keyword("AS TIMESTAMP");
35230                        self.write(")");
35231                    }
35232                    3 => {
35233                        self.write_keyword("TIMESTAMP_MILLIS");
35234                        self.write("(");
35235                        self.generate_expression(&e.this)?;
35236                        self.write(")");
35237                    }
35238                    6 => {
35239                        self.write_keyword("TIMESTAMP_MICROS");
35240                        self.write("(");
35241                        self.generate_expression(&e.this)?;
35242                        self.write(")");
35243                    }
35244                    _ => {
35245                        self.write_keyword("TIMESTAMP_SECONDS");
35246                        self.write("(");
35247                        self.generate_expression(&e.this)?;
35248                        self.write(&format!(" / POWER(10, {}))", scale));
35249                    }
35250                }
35251            }
35252            Some(DialectType::Databricks) => {
35253                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
35254                // TIMESTAMP_MILLIS(value) for scale=3
35255                // TIMESTAMP_MICROS(value) for scale=6
35256                match scale {
35257                    0 => {
35258                        self.write_keyword("CAST");
35259                        self.write("(");
35260                        self.write_keyword("FROM_UNIXTIME");
35261                        self.write("(");
35262                        self.generate_expression(&e.this)?;
35263                        self.write(") ");
35264                        self.write_keyword("AS TIMESTAMP");
35265                        self.write(")");
35266                    }
35267                    3 => {
35268                        self.write_keyword("TIMESTAMP_MILLIS");
35269                        self.write("(");
35270                        self.generate_expression(&e.this)?;
35271                        self.write(")");
35272                    }
35273                    6 => {
35274                        self.write_keyword("TIMESTAMP_MICROS");
35275                        self.write("(");
35276                        self.generate_expression(&e.this)?;
35277                        self.write(")");
35278                    }
35279                    _ => {
35280                        self.write_keyword("TIMESTAMP_SECONDS");
35281                        self.write("(");
35282                        self.generate_expression(&e.this)?;
35283                        self.write(&format!(" / POWER(10, {}))", scale));
35284                    }
35285                }
35286            }
35287            Some(DialectType::Hive) => {
35288                // Hive: FROM_UNIXTIME(value)
35289                if scale == 0 {
35290                    self.write_keyword("FROM_UNIXTIME");
35291                    self.write("(");
35292                    self.generate_expression(&e.this)?;
35293                    self.write(")");
35294                } else {
35295                    self.write_keyword("FROM_UNIXTIME");
35296                    self.write("(");
35297                    self.generate_expression(&e.this)?;
35298                    self.write(&format!(" / POWER(10, {})", scale));
35299                    self.write(")");
35300                }
35301            }
35302            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35303                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
35304                // FROM_UNIXTIME(value) for scale=0
35305                if scale == 0 {
35306                    self.write_keyword("FROM_UNIXTIME");
35307                    self.write("(");
35308                    self.generate_expression(&e.this)?;
35309                    self.write(")");
35310                } else {
35311                    self.write_keyword("FROM_UNIXTIME");
35312                    self.write("(CAST(");
35313                    self.generate_expression(&e.this)?;
35314                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
35315                }
35316            }
35317            Some(DialectType::DuckDB) => {
35318                // DuckDB: TO_TIMESTAMP(value) for scale=0
35319                // EPOCH_MS(value) for scale=3
35320                // MAKE_TIMESTAMP(value) for scale=6
35321                match scale {
35322                    0 => {
35323                        self.write_keyword("TO_TIMESTAMP");
35324                        self.write("(");
35325                        self.generate_expression(&e.this)?;
35326                        self.write(")");
35327                    }
35328                    3 => {
35329                        self.write_keyword("EPOCH_MS");
35330                        self.write("(");
35331                        self.generate_expression(&e.this)?;
35332                        self.write(")");
35333                    }
35334                    6 => {
35335                        self.write_keyword("MAKE_TIMESTAMP");
35336                        self.write("(");
35337                        self.generate_expression(&e.this)?;
35338                        self.write(")");
35339                    }
35340                    _ => {
35341                        self.write_keyword("TO_TIMESTAMP");
35342                        self.write("(");
35343                        self.generate_expression(&e.this)?;
35344                        self.write(&format!(" / POWER(10, {}))", scale));
35345                        self.write_keyword(" AT TIME ZONE");
35346                        self.write(" 'UTC'");
35347                    }
35348                }
35349            }
35350            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
35351                // Doris/StarRocks: FROM_UNIXTIME(value)
35352                self.write_keyword("FROM_UNIXTIME");
35353                self.write("(");
35354                self.generate_expression(&e.this)?;
35355                self.write(")");
35356            }
35357            Some(DialectType::Oracle) => {
35358                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
35359                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
35360                self.generate_expression(&e.this)?;
35361                self.write(" / 86400)");
35362            }
35363            Some(DialectType::Redshift) => {
35364                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
35365                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
35366                self.write("(TIMESTAMP 'epoch' + ");
35367                if scale == 0 {
35368                    self.generate_expression(&e.this)?;
35369                } else {
35370                    self.write("(");
35371                    self.generate_expression(&e.this)?;
35372                    self.write(&format!(" / POWER(10, {}))", scale));
35373                }
35374                self.write(" * INTERVAL '1 SECOND')");
35375            }
35376            _ => {
35377                // Default: TO_TIMESTAMP(value[, scale])
35378                self.write_keyword("TO_TIMESTAMP");
35379                self.write("(");
35380                self.generate_expression(&e.this)?;
35381                if let Some(s) = e.scale {
35382                    self.write(", ");
35383                    self.write(&s.to_string());
35384                }
35385                self.write(")");
35386            }
35387        }
35388        Ok(())
35389    }
35390
35391    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
35392        // NAME col VALUE col1, col2, ...
35393        if !matches!(&*e.this, Expression::Null(_)) {
35394            self.write_keyword("NAME");
35395            self.write_space();
35396            self.generate_expression(&e.this)?;
35397        }
35398        if !e.expressions.is_empty() {
35399            self.write_space();
35400            self.write_keyword("VALUE");
35401            self.write_space();
35402            for (i, expr) in e.expressions.iter().enumerate() {
35403                if i > 0 {
35404                    self.write(", ");
35405                }
35406                self.generate_expression(expr)?;
35407            }
35408        }
35409        Ok(())
35410    }
35411
35412    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
35413        // this(expressions) or (this)(expressions)
35414        if e.wrapped.is_some() {
35415            self.write("(");
35416        }
35417        self.generate_expression(&e.this)?;
35418        if e.wrapped.is_some() {
35419            self.write(")");
35420        }
35421        self.write("(");
35422        for (i, expr) in e.expressions.iter().enumerate() {
35423            if i > 0 {
35424                self.write(", ");
35425            }
35426            self.generate_expression(expr)?;
35427        }
35428        self.write(")");
35429        Ok(())
35430    }
35431
35432    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
35433        // USING TEMPLATE this
35434        self.write_keyword("USING TEMPLATE");
35435        self.write_space();
35436        self.generate_expression(&e.this)?;
35437        Ok(())
35438    }
35439
35440    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
35441        // UTC_TIME
35442        self.write_keyword("UTC_TIME");
35443        Ok(())
35444    }
35445
35446    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
35447        // UTC_TIMESTAMP
35448        self.write_keyword("UTC_TIMESTAMP");
35449        Ok(())
35450    }
35451
35452    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
35453        use crate::dialects::DialectType;
35454        // Choose UUID function name based on target dialect
35455        let func_name = match self.config.dialect {
35456            Some(DialectType::Snowflake) => "UUID_STRING",
35457            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
35458            Some(DialectType::BigQuery) => "GENERATE_UUID",
35459            _ => {
35460                if let Some(name) = &e.name {
35461                    name.as_str()
35462                } else {
35463                    "UUID"
35464                }
35465            }
35466        };
35467        self.write_keyword(func_name);
35468        self.write("(");
35469        if let Some(this) = &e.this {
35470            self.generate_expression(this)?;
35471        }
35472        self.write(")");
35473        Ok(())
35474    }
35475
35476    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
35477        // MAP(key1, value1, key2, value2, ...)
35478        self.write_keyword("MAP");
35479        self.write("(");
35480        let mut first = true;
35481        for (k, v) in e.keys.iter().zip(e.values.iter()) {
35482            if !first {
35483                self.write(", ");
35484            }
35485            self.generate_expression(k)?;
35486            self.write(", ");
35487            self.generate_expression(v)?;
35488            first = false;
35489        }
35490        self.write(")");
35491        Ok(())
35492    }
35493
35494    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
35495        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
35496        self.write_keyword("VECTOR_SEARCH");
35497        self.write("(");
35498        self.generate_expression(&e.this)?;
35499        if let Some(col) = &e.column_to_search {
35500            self.write(", ");
35501            self.generate_expression(col)?;
35502        }
35503        if let Some(query_table) = &e.query_table {
35504            self.write(", ");
35505            self.generate_expression(query_table)?;
35506        }
35507        if let Some(query_col) = &e.query_column_to_search {
35508            self.write(", ");
35509            self.generate_expression(query_col)?;
35510        }
35511        if let Some(top_k) = &e.top_k {
35512            self.write(", ");
35513            self.generate_expression(top_k)?;
35514        }
35515        if let Some(dist_type) = &e.distance_type {
35516            self.write(", ");
35517            self.generate_expression(dist_type)?;
35518        }
35519        self.write(")");
35520        Ok(())
35521    }
35522
35523    fn generate_version(&mut self, e: &Version) -> Result<()> {
35524        // Python: f"FOR {expression.name} {kind} {expr}"
35525        // e.this = Identifier("TIMESTAMP" or "VERSION")
35526        // e.kind = "AS OF" (or "BETWEEN", etc.)
35527        // e.expression = the value expression
35528        // Hive does NOT use the FOR prefix for time travel
35529        use crate::dialects::DialectType;
35530        let skip_for = matches!(
35531            self.config.dialect,
35532            Some(DialectType::Hive) | Some(DialectType::Spark)
35533        );
35534        if !skip_for {
35535            self.write_keyword("FOR");
35536            self.write_space();
35537        }
35538        // Extract the name from this (which is an Identifier expression)
35539        match e.this.as_ref() {
35540            Expression::Identifier(ident) => {
35541                self.write_keyword(&ident.name);
35542            }
35543            _ => {
35544                self.generate_expression(&e.this)?;
35545            }
35546        }
35547        self.write_space();
35548        self.write_keyword(&e.kind);
35549        if let Some(expression) = &e.expression {
35550            self.write_space();
35551            self.generate_expression(expression)?;
35552        }
35553        Ok(())
35554    }
35555
35556    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
35557        // Python: return self.sql(expression, "this")
35558        self.generate_expression(&e.this)?;
35559        Ok(())
35560    }
35561
35562    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
35563        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
35564        if e.this.is_some() {
35565            self.write_keyword("NOT VOLATILE");
35566        } else {
35567            self.write_keyword("VOLATILE");
35568        }
35569        Ok(())
35570    }
35571
35572    fn generate_watermark_column_constraint(
35573        &mut self,
35574        e: &WatermarkColumnConstraint,
35575    ) -> Result<()> {
35576        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
35577        self.write_keyword("WATERMARK FOR");
35578        self.write_space();
35579        self.generate_expression(&e.this)?;
35580        self.write_space();
35581        self.write_keyword("AS");
35582        self.write_space();
35583        self.generate_expression(&e.expression)?;
35584        Ok(())
35585    }
35586
35587    fn generate_week(&mut self, e: &Week) -> Result<()> {
35588        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
35589        self.write_keyword("WEEK");
35590        self.write("(");
35591        self.generate_expression(&e.this)?;
35592        if let Some(mode) = &e.mode {
35593            self.write(", ");
35594            self.generate_expression(mode)?;
35595        }
35596        self.write(")");
35597        Ok(())
35598    }
35599
35600    fn generate_when(&mut self, e: &When) -> Result<()> {
35601        // Python: WHEN {matched}{source}{condition} THEN {then}
35602        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
35603        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
35604        self.write_keyword("WHEN");
35605        self.write_space();
35606
35607        // Check if matched
35608        if let Some(matched) = &e.matched {
35609            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
35610            match matched.as_ref() {
35611                Expression::Boolean(b) if b.value => {
35612                    self.write_keyword("MATCHED");
35613                }
35614                _ => {
35615                    self.write_keyword("NOT MATCHED");
35616                }
35617            }
35618        } else {
35619            self.write_keyword("NOT MATCHED");
35620        }
35621
35622        // BY SOURCE / BY TARGET
35623        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
35624        // BY TARGET is the default and typically omitted in output
35625        // Only emit if the dialect supports BY SOURCE syntax
35626        if self.config.matched_by_source {
35627            if let Some(source) = &e.source {
35628                if let Expression::Boolean(b) = source.as_ref() {
35629                    if b.value {
35630                        // BY SOURCE
35631                        self.write_space();
35632                        self.write_keyword("BY SOURCE");
35633                    }
35634                    // BY TARGET (b.value == false) is omitted as it's the default
35635                } else {
35636                    // For non-boolean source, output as BY SOURCE (legacy behavior)
35637                    self.write_space();
35638                    self.write_keyword("BY SOURCE");
35639                }
35640            }
35641        }
35642
35643        // Condition
35644        if let Some(condition) = &e.condition {
35645            self.write_space();
35646            self.write_keyword("AND");
35647            self.write_space();
35648            self.generate_expression(condition)?;
35649        }
35650
35651        self.write_space();
35652        self.write_keyword("THEN");
35653        self.write_space();
35654
35655        // Generate the then expression (could be INSERT, UPDATE, DELETE)
35656        // MERGE actions are stored as Tuples with the action keyword as first element
35657        self.generate_merge_action(&e.then)?;
35658
35659        Ok(())
35660    }
35661
35662    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
35663        match action {
35664            Expression::Tuple(tuple) => {
35665                let elements = &tuple.expressions;
35666                if elements.is_empty() {
35667                    return self.generate_expression(action);
35668                }
35669                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
35670                match &elements[0] {
35671                    Expression::Var(v) if v.this == "INSERT" => {
35672                        self.write_keyword("INSERT");
35673                        // Spark: INSERT * (insert all columns)
35674                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
35675                            self.write(" *");
35676                        } else {
35677                            let mut values_idx = 1;
35678                            // Check if second element is column list (Tuple)
35679                            if elements.len() > 1 {
35680                                if let Expression::Tuple(cols) = &elements[1] {
35681                                    // Could be columns or values - if there's a third element, second is columns
35682                                    if elements.len() > 2 {
35683                                        // Second is columns, third is values
35684                                        self.write(" (");
35685                                        for (i, col) in cols.expressions.iter().enumerate() {
35686                                            if i > 0 {
35687                                                self.write(", ");
35688                                            }
35689                                            // Strip MERGE target qualifiers from INSERT column list
35690                                            if !self.merge_strip_qualifiers.is_empty() {
35691                                                let stripped = self.strip_merge_qualifier(col);
35692                                                self.generate_expression(&stripped)?;
35693                                            } else {
35694                                                self.generate_expression(col)?;
35695                                            }
35696                                        }
35697                                        self.write(")");
35698                                        values_idx = 2;
35699                                    } else {
35700                                        // Only two elements: INSERT + values (no explicit columns)
35701                                        values_idx = 1;
35702                                    }
35703                                }
35704                            }
35705                            // Generate VALUES clause
35706                            if values_idx < elements.len() {
35707                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
35708                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
35709                                if !is_row {
35710                                    self.write_space();
35711                                    self.write_keyword("VALUES");
35712                                }
35713                                self.write(" ");
35714                                if let Expression::Tuple(vals) = &elements[values_idx] {
35715                                    self.write("(");
35716                                    for (i, val) in vals.expressions.iter().enumerate() {
35717                                        if i > 0 {
35718                                            self.write(", ");
35719                                        }
35720                                        self.generate_expression(val)?;
35721                                    }
35722                                    self.write(")");
35723                                } else {
35724                                    self.generate_expression(&elements[values_idx])?;
35725                                }
35726                            }
35727                        } // close else for INSERT * check
35728                    }
35729                    Expression::Var(v) if v.this == "UPDATE" => {
35730                        self.write_keyword("UPDATE");
35731                        // Spark: UPDATE * (update all columns)
35732                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
35733                            self.write(" *");
35734                        } else if elements.len() > 1 {
35735                            self.write_space();
35736                            self.write_keyword("SET");
35737                            // In pretty mode, put assignments on next line with extra indent
35738                            if self.config.pretty {
35739                                self.write_newline();
35740                                self.indent_level += 1;
35741                                self.write_indent();
35742                            } else {
35743                                self.write_space();
35744                            }
35745                            if let Expression::Tuple(assignments) = &elements[1] {
35746                                for (i, assignment) in assignments.expressions.iter().enumerate() {
35747                                    if i > 0 {
35748                                        if self.config.pretty {
35749                                            self.write(",");
35750                                            self.write_newline();
35751                                            self.write_indent();
35752                                        } else {
35753                                            self.write(", ");
35754                                        }
35755                                    }
35756                                    // Strip MERGE target qualifiers from left side of UPDATE SET
35757                                    if !self.merge_strip_qualifiers.is_empty() {
35758                                        self.generate_merge_set_assignment(assignment)?;
35759                                    } else {
35760                                        self.generate_expression(assignment)?;
35761                                    }
35762                                }
35763                            } else {
35764                                self.generate_expression(&elements[1])?;
35765                            }
35766                            if self.config.pretty {
35767                                self.indent_level -= 1;
35768                            }
35769                        }
35770                    }
35771                    _ => {
35772                        // Fallback: generic tuple generation
35773                        self.generate_expression(action)?;
35774                    }
35775                }
35776            }
35777            Expression::Var(v)
35778                if v.this == "INSERT"
35779                    || v.this == "UPDATE"
35780                    || v.this == "DELETE"
35781                    || v.this == "DO NOTHING" =>
35782            {
35783                self.write_keyword(&v.this);
35784            }
35785            _ => {
35786                self.generate_expression(action)?;
35787            }
35788        }
35789        Ok(())
35790    }
35791
35792    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
35793    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
35794        match assignment {
35795            Expression::Eq(eq) => {
35796                // Strip qualifier from the left side if it matches a MERGE target name
35797                let stripped_left = self.strip_merge_qualifier(&eq.left);
35798                self.generate_expression(&stripped_left)?;
35799                self.write(" = ");
35800                self.generate_expression(&eq.right)?;
35801                Ok(())
35802            }
35803            other => self.generate_expression(other),
35804        }
35805    }
35806
35807    /// Strip table qualifier from a column reference if it matches a MERGE target name
35808    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
35809        match expr {
35810            Expression::Column(col) => {
35811                if let Some(ref table_ident) = col.table {
35812                    if self
35813                        .merge_strip_qualifiers
35814                        .iter()
35815                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
35816                    {
35817                        // Strip the table qualifier
35818                        let mut col = col.clone();
35819                        col.table = None;
35820                        return Expression::Column(col);
35821                    }
35822                }
35823                expr.clone()
35824            }
35825            Expression::Dot(dot) => {
35826                // table.column -> column (strip qualifier)
35827                if let Expression::Identifier(id) = &dot.this {
35828                    if self
35829                        .merge_strip_qualifiers
35830                        .iter()
35831                        .any(|n| n.eq_ignore_ascii_case(&id.name))
35832                    {
35833                        return Expression::Identifier(dot.field.clone());
35834                    }
35835                }
35836                expr.clone()
35837            }
35838            _ => expr.clone(),
35839        }
35840    }
35841
35842    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
35843        // Python: return self.expressions(expression, sep=" ", indent=False)
35844        for (i, expr) in e.expressions.iter().enumerate() {
35845            if i > 0 {
35846                // In pretty mode, each WHEN clause on its own line
35847                if self.config.pretty {
35848                    self.write_newline();
35849                    self.write_indent();
35850                } else {
35851                    self.write_space();
35852                }
35853            }
35854            self.generate_expression(expr)?;
35855        }
35856        Ok(())
35857    }
35858
35859    fn generate_where(&mut self, e: &Where) -> Result<()> {
35860        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
35861        self.write_keyword("WHERE");
35862        self.write_space();
35863        self.generate_expression(&e.this)?;
35864        Ok(())
35865    }
35866
35867    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
35868        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
35869        self.write_keyword("WIDTH_BUCKET");
35870        self.write("(");
35871        self.generate_expression(&e.this)?;
35872        if let Some(min_value) = &e.min_value {
35873            self.write(", ");
35874            self.generate_expression(min_value)?;
35875        }
35876        if let Some(max_value) = &e.max_value {
35877            self.write(", ");
35878            self.generate_expression(max_value)?;
35879        }
35880        if let Some(num_buckets) = &e.num_buckets {
35881            self.write(", ");
35882            self.generate_expression(num_buckets)?;
35883        }
35884        self.write(")");
35885        Ok(())
35886    }
35887
35888    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
35889        // Window specification: PARTITION BY ... ORDER BY ... frame
35890        self.generate_window_spec(e)
35891    }
35892
35893    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
35894        // Window specification: PARTITION BY ... ORDER BY ... frame
35895        let mut has_content = false;
35896
35897        // PARTITION BY
35898        if !e.partition_by.is_empty() {
35899            self.write_keyword("PARTITION BY");
35900            self.write_space();
35901            for (i, expr) in e.partition_by.iter().enumerate() {
35902                if i > 0 {
35903                    self.write(", ");
35904                }
35905                self.generate_expression(expr)?;
35906            }
35907            has_content = true;
35908        }
35909
35910        // ORDER BY
35911        if !e.order_by.is_empty() {
35912            if has_content {
35913                self.write_space();
35914            }
35915            self.write_keyword("ORDER BY");
35916            self.write_space();
35917            for (i, ordered) in e.order_by.iter().enumerate() {
35918                if i > 0 {
35919                    self.write(", ");
35920                }
35921                self.generate_expression(&ordered.this)?;
35922                if ordered.desc {
35923                    self.write_space();
35924                    self.write_keyword("DESC");
35925                } else if ordered.explicit_asc {
35926                    self.write_space();
35927                    self.write_keyword("ASC");
35928                }
35929                if let Some(nulls_first) = ordered.nulls_first {
35930                    self.write_space();
35931                    self.write_keyword("NULLS");
35932                    self.write_space();
35933                    if nulls_first {
35934                        self.write_keyword("FIRST");
35935                    } else {
35936                        self.write_keyword("LAST");
35937                    }
35938                }
35939            }
35940            has_content = true;
35941        }
35942
35943        // Frame specification
35944        if let Some(frame) = &e.frame {
35945            if has_content {
35946                self.write_space();
35947            }
35948            self.generate_window_frame(frame)?;
35949        }
35950
35951        Ok(())
35952    }
35953
35954    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
35955        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
35956        self.write_keyword("WITH");
35957        self.write_space();
35958        if e.no.is_some() {
35959            self.write_keyword("NO");
35960            self.write_space();
35961        }
35962        self.write_keyword("DATA");
35963
35964        // statistics
35965        if let Some(statistics) = &e.statistics {
35966            self.write_space();
35967            self.write_keyword("AND");
35968            self.write_space();
35969            // Check if statistics is true or false
35970            match statistics.as_ref() {
35971                Expression::Boolean(b) if !b.value => {
35972                    self.write_keyword("NO");
35973                    self.write_space();
35974                }
35975                _ => {}
35976            }
35977            self.write_keyword("STATISTICS");
35978        }
35979        Ok(())
35980    }
35981
35982    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
35983        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
35984        self.write_keyword("WITH FILL");
35985
35986        if let Some(from_) = &e.from_ {
35987            self.write_space();
35988            self.write_keyword("FROM");
35989            self.write_space();
35990            self.generate_expression(from_)?;
35991        }
35992
35993        if let Some(to) = &e.to {
35994            self.write_space();
35995            self.write_keyword("TO");
35996            self.write_space();
35997            self.generate_expression(to)?;
35998        }
35999
36000        if let Some(step) = &e.step {
36001            self.write_space();
36002            self.write_keyword("STEP");
36003            self.write_space();
36004            self.generate_expression(step)?;
36005        }
36006
36007        if let Some(staleness) = &e.staleness {
36008            self.write_space();
36009            self.write_keyword("STALENESS");
36010            self.write_space();
36011            self.generate_expression(staleness)?;
36012        }
36013
36014        if let Some(interpolate) = &e.interpolate {
36015            self.write_space();
36016            self.write_keyword("INTERPOLATE");
36017            self.write(" (");
36018            // INTERPOLATE items use reversed alias format: name AS expression
36019            self.generate_interpolate_item(interpolate)?;
36020            self.write(")");
36021        }
36022
36023        Ok(())
36024    }
36025
36026    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
36027    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
36028        match expr {
36029            Expression::Alias(alias) => {
36030                // Output as: alias_name AS expression
36031                self.generate_identifier(&alias.alias)?;
36032                self.write_space();
36033                self.write_keyword("AS");
36034                self.write_space();
36035                self.generate_expression(&alias.this)?;
36036            }
36037            Expression::Tuple(tuple) => {
36038                for (i, item) in tuple.expressions.iter().enumerate() {
36039                    if i > 0 {
36040                        self.write(", ");
36041                    }
36042                    self.generate_interpolate_item(item)?;
36043                }
36044            }
36045            other => {
36046                self.generate_expression(other)?;
36047            }
36048        }
36049        Ok(())
36050    }
36051
36052    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
36053        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
36054        self.write_keyword("WITH JOURNAL TABLE");
36055        self.write("=");
36056        self.generate_expression(&e.this)?;
36057        Ok(())
36058    }
36059
36060    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
36061        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
36062        self.generate_expression(&e.this)?;
36063        self.write_space();
36064        self.write_keyword("WITH");
36065        self.write_space();
36066        self.write_keyword(&e.op);
36067        Ok(())
36068    }
36069
36070    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
36071        // Python: return f"WITH {self.expressions(expression, flat=True)}"
36072        self.write_keyword("WITH");
36073        self.write_space();
36074        for (i, expr) in e.expressions.iter().enumerate() {
36075            if i > 0 {
36076                self.write(", ");
36077            }
36078            self.generate_expression(expr)?;
36079        }
36080        Ok(())
36081    }
36082
36083    fn generate_with_schema_binding_property(
36084        &mut self,
36085        e: &WithSchemaBindingProperty,
36086    ) -> Result<()> {
36087        // Python: return f"WITH {self.sql(expression, 'this')}"
36088        self.write_keyword("WITH");
36089        self.write_space();
36090        self.generate_expression(&e.this)?;
36091        Ok(())
36092    }
36093
36094    fn generate_with_system_versioning_property(
36095        &mut self,
36096        e: &WithSystemVersioningProperty,
36097    ) -> Result<()> {
36098        // Python: complex logic for SYSTEM_VERSIONING with options
36099        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
36100        // or SYSTEM_VERSIONING=ON/OFF
36101        // with WITH(...) wrapper if with_ is set
36102
36103        let mut parts = Vec::new();
36104
36105        if let Some(this) = &e.this {
36106            // HISTORY_TABLE=...
36107            let mut s = String::from("HISTORY_TABLE=");
36108            let mut gen = Generator::new();
36109            gen.generate_expression(this)?;
36110            s.push_str(&gen.output);
36111            parts.push(s);
36112        }
36113
36114        if let Some(data_consistency) = &e.data_consistency {
36115            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
36116            let mut gen = Generator::new();
36117            gen.generate_expression(data_consistency)?;
36118            s.push_str(&gen.output);
36119            parts.push(s);
36120        }
36121
36122        if let Some(retention_period) = &e.retention_period {
36123            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
36124            let mut gen = Generator::new();
36125            gen.generate_expression(retention_period)?;
36126            s.push_str(&gen.output);
36127            parts.push(s);
36128        }
36129
36130        self.write_keyword("SYSTEM_VERSIONING");
36131        self.write("=");
36132
36133        if !parts.is_empty() {
36134            self.write_keyword("ON");
36135            self.write("(");
36136            self.write(&parts.join(", "));
36137            self.write(")");
36138        } else if e.on.is_some() {
36139            self.write_keyword("ON");
36140        } else {
36141            self.write_keyword("OFF");
36142        }
36143
36144        // Wrap in WITH(...) if with_ is set
36145        if e.with_.is_some() {
36146            let inner = self.output.clone();
36147            self.output.clear();
36148            self.write("WITH(");
36149            self.write(&inner);
36150            self.write(")");
36151        }
36152
36153        Ok(())
36154    }
36155
36156    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
36157        // Python: f"WITH ({self.expressions(expression, flat=True)})"
36158        self.write_keyword("WITH");
36159        self.write(" (");
36160        for (i, expr) in e.expressions.iter().enumerate() {
36161            if i > 0 {
36162                self.write(", ");
36163            }
36164            self.generate_expression(expr)?;
36165        }
36166        self.write(")");
36167        Ok(())
36168    }
36169
36170    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
36171        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
36172        // return self.func("XMLELEMENT", name, *expression.expressions)
36173        self.write_keyword("XMLELEMENT");
36174        self.write("(");
36175
36176        if e.evalname.is_some() {
36177            self.write_keyword("EVALNAME");
36178        } else {
36179            self.write_keyword("NAME");
36180        }
36181        self.write_space();
36182        self.generate_expression(&e.this)?;
36183
36184        for expr in &e.expressions {
36185            self.write(", ");
36186            self.generate_expression(expr)?;
36187        }
36188        self.write(")");
36189        Ok(())
36190    }
36191
36192    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
36193        // XMLGET(this, expression [, instance])
36194        self.write_keyword("XMLGET");
36195        self.write("(");
36196        self.generate_expression(&e.this)?;
36197        self.write(", ");
36198        self.generate_expression(&e.expression)?;
36199        if let Some(instance) = &e.instance {
36200            self.write(", ");
36201            self.generate_expression(instance)?;
36202        }
36203        self.write(")");
36204        Ok(())
36205    }
36206
36207    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
36208        // Python: this + optional (expr)
36209        self.generate_expression(&e.this)?;
36210        if let Some(expression) = &e.expression {
36211            self.write("(");
36212            self.generate_expression(expression)?;
36213            self.write(")");
36214        }
36215        Ok(())
36216    }
36217
36218    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
36219        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
36220        self.write_keyword("XMLTABLE");
36221        self.write("(");
36222
36223        if self.config.pretty {
36224            self.indent_level += 1;
36225            self.write_newline();
36226            self.write_indent();
36227            self.generate_expression(&e.this)?;
36228
36229            if let Some(passing) = &e.passing {
36230                self.write_newline();
36231                self.write_indent();
36232                self.write_keyword("PASSING");
36233                if let Expression::Tuple(tuple) = passing.as_ref() {
36234                    for expr in &tuple.expressions {
36235                        self.write_newline();
36236                        self.indent_level += 1;
36237                        self.write_indent();
36238                        self.generate_expression(expr)?;
36239                        self.indent_level -= 1;
36240                    }
36241                } else {
36242                    self.write_newline();
36243                    self.indent_level += 1;
36244                    self.write_indent();
36245                    self.generate_expression(passing)?;
36246                    self.indent_level -= 1;
36247                }
36248            }
36249
36250            if e.by_ref.is_some() {
36251                self.write_newline();
36252                self.write_indent();
36253                self.write_keyword("RETURNING SEQUENCE BY REF");
36254            }
36255
36256            if !e.columns.is_empty() {
36257                self.write_newline();
36258                self.write_indent();
36259                self.write_keyword("COLUMNS");
36260                for (i, col) in e.columns.iter().enumerate() {
36261                    self.write_newline();
36262                    self.indent_level += 1;
36263                    self.write_indent();
36264                    self.generate_expression(col)?;
36265                    self.indent_level -= 1;
36266                    if i < e.columns.len() - 1 {
36267                        self.write(",");
36268                    }
36269                }
36270            }
36271
36272            self.indent_level -= 1;
36273            self.write_newline();
36274            self.write_indent();
36275            self.write(")");
36276            return Ok(());
36277        }
36278
36279        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
36280        if let Some(namespaces) = &e.namespaces {
36281            self.write_keyword("XMLNAMESPACES");
36282            self.write("(");
36283            // Unwrap Tuple if present to avoid extra parentheses
36284            if let Expression::Tuple(tuple) = namespaces.as_ref() {
36285                for (i, expr) in tuple.expressions.iter().enumerate() {
36286                    if i > 0 {
36287                        self.write(", ");
36288                    }
36289                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
36290                    // See xmlnamespace_sql in generator.py
36291                    if !matches!(expr, Expression::Alias(_)) {
36292                        self.write_keyword("DEFAULT");
36293                        self.write_space();
36294                    }
36295                    self.generate_expression(expr)?;
36296                }
36297            } else {
36298                // Single namespace - check if DEFAULT
36299                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
36300                    self.write_keyword("DEFAULT");
36301                    self.write_space();
36302                }
36303                self.generate_expression(namespaces)?;
36304            }
36305            self.write("), ");
36306        }
36307
36308        // XPath expression
36309        self.generate_expression(&e.this)?;
36310
36311        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
36312        if let Some(passing) = &e.passing {
36313            self.write_space();
36314            self.write_keyword("PASSING");
36315            self.write_space();
36316            // Unwrap Tuple if present to avoid extra parentheses
36317            if let Expression::Tuple(tuple) = passing.as_ref() {
36318                for (i, expr) in tuple.expressions.iter().enumerate() {
36319                    if i > 0 {
36320                        self.write(", ");
36321                    }
36322                    self.generate_expression(expr)?;
36323                }
36324            } else {
36325                self.generate_expression(passing)?;
36326            }
36327        }
36328
36329        // RETURNING SEQUENCE BY REF
36330        if e.by_ref.is_some() {
36331            self.write_space();
36332            self.write_keyword("RETURNING SEQUENCE BY REF");
36333        }
36334
36335        // COLUMNS clause
36336        if !e.columns.is_empty() {
36337            self.write_space();
36338            self.write_keyword("COLUMNS");
36339            self.write_space();
36340            for (i, col) in e.columns.iter().enumerate() {
36341                if i > 0 {
36342                    self.write(", ");
36343                }
36344                self.generate_expression(col)?;
36345            }
36346        }
36347
36348        self.write(")");
36349        Ok(())
36350    }
36351
36352    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
36353        // Python: return self.connector_sql(expression, "XOR", stack)
36354        // Handles: this XOR expression or expressions joined by XOR
36355        if let Some(this) = &e.this {
36356            self.generate_expression(this)?;
36357            if let Some(expression) = &e.expression {
36358                self.write_space();
36359                self.write_keyword("XOR");
36360                self.write_space();
36361                self.generate_expression(expression)?;
36362            }
36363        }
36364
36365        // Handle multiple expressions
36366        for (i, expr) in e.expressions.iter().enumerate() {
36367            if i > 0 || e.this.is_some() {
36368                self.write_space();
36369                self.write_keyword("XOR");
36370                self.write_space();
36371            }
36372            self.generate_expression(expr)?;
36373        }
36374        Ok(())
36375    }
36376
36377    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
36378        // ZIPF(this, elementcount [, gen])
36379        self.write_keyword("ZIPF");
36380        self.write("(");
36381        self.generate_expression(&e.this)?;
36382        if let Some(elementcount) = &e.elementcount {
36383            self.write(", ");
36384            self.generate_expression(elementcount)?;
36385        }
36386        if let Some(gen) = &e.gen {
36387            self.write(", ");
36388            self.generate_expression(gen)?;
36389        }
36390        self.write(")");
36391        Ok(())
36392    }
36393}
36394
36395impl Default for Generator {
36396    fn default() -> Self {
36397        Self::new()
36398    }
36399}
36400
36401#[cfg(test)]
36402mod tests {
36403    use super::*;
36404    use crate::parser::Parser;
36405
36406    fn roundtrip(sql: &str) -> String {
36407        let ast = Parser::parse_sql(sql).unwrap();
36408        Generator::sql(&ast[0]).unwrap()
36409    }
36410
36411    #[test]
36412    fn test_simple_select() {
36413        let result = roundtrip("SELECT 1");
36414        assert_eq!(result, "SELECT 1");
36415    }
36416
36417    #[test]
36418    fn test_select_from() {
36419        let result = roundtrip("SELECT a, b FROM t");
36420        assert_eq!(result, "SELECT a, b FROM t");
36421    }
36422
36423    #[test]
36424    fn test_select_where() {
36425        let result = roundtrip("SELECT * FROM t WHERE x = 1");
36426        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
36427    }
36428
36429    #[test]
36430    fn test_select_join() {
36431        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
36432        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
36433    }
36434
36435    #[test]
36436    fn test_insert() {
36437        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
36438        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
36439    }
36440
36441    #[test]
36442    fn test_pretty_print() {
36443        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
36444        let result = Generator::pretty_sql(&ast[0]).unwrap();
36445        assert!(result.contains('\n'));
36446    }
36447
36448    #[test]
36449    fn test_window_function() {
36450        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
36451        assert_eq!(
36452            result,
36453            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
36454        );
36455    }
36456
36457    #[test]
36458    fn test_window_function_with_frame() {
36459        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
36460        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
36461    }
36462
36463    #[test]
36464    fn test_aggregate_with_filter() {
36465        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
36466        assert_eq!(
36467            result,
36468            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
36469        );
36470    }
36471
36472    #[test]
36473    fn test_subscript() {
36474        let result = roundtrip("SELECT arr[0]");
36475        assert_eq!(result, "SELECT arr[0]");
36476    }
36477
36478    // DDL tests
36479    #[test]
36480    fn test_create_table() {
36481        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
36482        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
36483    }
36484
36485    #[test]
36486    fn test_create_table_with_constraints() {
36487        let result = roundtrip(
36488            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
36489        );
36490        assert_eq!(
36491            result,
36492            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
36493        );
36494    }
36495
36496    #[test]
36497    fn test_create_table_if_not_exists() {
36498        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
36499        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
36500    }
36501
36502    #[test]
36503    fn test_drop_table() {
36504        let result = roundtrip("DROP TABLE users");
36505        assert_eq!(result, "DROP TABLE users");
36506    }
36507
36508    #[test]
36509    fn test_drop_table_if_exists_cascade() {
36510        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
36511        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
36512    }
36513
36514    #[test]
36515    fn test_alter_table_add_column() {
36516        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
36517        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
36518    }
36519
36520    #[test]
36521    fn test_alter_table_drop_column() {
36522        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
36523        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
36524    }
36525
36526    #[test]
36527    fn test_create_index() {
36528        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
36529        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
36530    }
36531
36532    #[test]
36533    fn test_create_unique_index() {
36534        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
36535        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
36536    }
36537
36538    #[test]
36539    fn test_drop_index() {
36540        let result = roundtrip("DROP INDEX idx_name");
36541        assert_eq!(result, "DROP INDEX idx_name");
36542    }
36543
36544    #[test]
36545    fn test_create_view() {
36546        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
36547        assert_eq!(
36548            result,
36549            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
36550        );
36551    }
36552
36553    #[test]
36554    fn test_drop_view() {
36555        let result = roundtrip("DROP VIEW active_users");
36556        assert_eq!(result, "DROP VIEW active_users");
36557    }
36558
36559    #[test]
36560    fn test_truncate() {
36561        let result = roundtrip("TRUNCATE TABLE users");
36562        assert_eq!(result, "TRUNCATE TABLE users");
36563    }
36564
36565    #[test]
36566    fn test_string_literal_escaping_default() {
36567        // Default: double single quotes
36568        let result = roundtrip("SELECT 'hello'");
36569        assert_eq!(result, "SELECT 'hello'");
36570
36571        // Single quotes are doubled
36572        let result = roundtrip("SELECT 'it''s a test'");
36573        assert_eq!(result, "SELECT 'it''s a test'");
36574    }
36575
36576    #[test]
36577    fn test_not_in_style_prefix_default_generic() {
36578        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
36579        assert_eq!(
36580            result,
36581            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
36582        );
36583    }
36584
36585    #[test]
36586    fn test_not_in_style_infix_generic_override() {
36587        let ast =
36588            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
36589                .unwrap();
36590        let config = GeneratorConfig {
36591            not_in_style: NotInStyle::Infix,
36592            ..Default::default()
36593        };
36594        let mut gen = Generator::with_config(config);
36595        let result = gen.generate(&ast[0]).unwrap();
36596        assert_eq!(
36597            result,
36598            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
36599        );
36600    }
36601
36602    #[test]
36603    fn test_string_literal_escaping_mysql() {
36604        use crate::dialects::DialectType;
36605
36606        let config = GeneratorConfig {
36607            dialect: Some(DialectType::MySQL),
36608            ..Default::default()
36609        };
36610
36611        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36612        let mut gen = Generator::with_config(config.clone());
36613        let result = gen.generate(&ast[0]).unwrap();
36614        assert_eq!(result, "SELECT 'hello'");
36615
36616        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
36617        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36618        let mut gen = Generator::with_config(config.clone());
36619        let result = gen.generate(&ast[0]).unwrap();
36620        assert_eq!(result, "SELECT 'it''s'");
36621    }
36622
36623    #[test]
36624    fn test_string_literal_escaping_postgres() {
36625        use crate::dialects::DialectType;
36626
36627        let config = GeneratorConfig {
36628            dialect: Some(DialectType::PostgreSQL),
36629            ..Default::default()
36630        };
36631
36632        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36633        let mut gen = Generator::with_config(config.clone());
36634        let result = gen.generate(&ast[0]).unwrap();
36635        assert_eq!(result, "SELECT 'hello'");
36636
36637        // PostgreSQL uses doubled quotes for regular strings
36638        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36639        let mut gen = Generator::with_config(config.clone());
36640        let result = gen.generate(&ast[0]).unwrap();
36641        assert_eq!(result, "SELECT 'it''s'");
36642    }
36643
36644    #[test]
36645    fn test_string_literal_escaping_bigquery() {
36646        use crate::dialects::DialectType;
36647
36648        let config = GeneratorConfig {
36649            dialect: Some(DialectType::BigQuery),
36650            ..Default::default()
36651        };
36652
36653        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
36654        let mut gen = Generator::with_config(config.clone());
36655        let result = gen.generate(&ast[0]).unwrap();
36656        assert_eq!(result, "SELECT 'hello'");
36657
36658        // BigQuery escapes single quotes with backslash
36659        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
36660        let mut gen = Generator::with_config(config.clone());
36661        let result = gen.generate(&ast[0]).unwrap();
36662        assert_eq!(result, "SELECT 'it\\'s'");
36663    }
36664
36665    #[test]
36666    fn test_generate_deep_and_chain_without_stack_growth() {
36667        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
36668            Expression::column("c0"),
36669            Expression::number(0),
36670        )));
36671
36672        for i in 1..2500 {
36673            let predicate = Expression::Eq(Box::new(BinaryOp::new(
36674                Expression::column(format!("c{i}")),
36675                Expression::number(i as i64),
36676            )));
36677            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
36678        }
36679
36680        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
36681        assert!(sql.contains("c2499 = 2499"), "{}", sql);
36682    }
36683
36684    #[test]
36685    fn test_generate_deep_or_chain_without_stack_growth() {
36686        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
36687            Expression::column("c0"),
36688            Expression::number(0),
36689        )));
36690
36691        for i in 1..2500 {
36692            let predicate = Expression::Eq(Box::new(BinaryOp::new(
36693                Expression::column(format!("c{i}")),
36694                Expression::number(i as i64),
36695            )));
36696            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
36697        }
36698
36699        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
36700        assert!(sql.contains("c2499 = 2499"), "{}", sql);
36701    }
36702}