Skip to main content

polyglot_sql/
generator.rs

1//! SQL Generator -- converts an AST back into SQL strings.
2//!
3//! The central type is [`Generator`], which walks an [`Expression`] tree and
4//! emits a SQL string. Generation is controlled by a [`GeneratorConfig`] that
5//! specifies the target dialect, formatting preferences, identifier quoting
6//! style, function name casing, and many other dialect-specific flags.
7//!
8//! For one-off generation the static helpers [`Generator::sql`] and
9//! [`Generator::pretty_sql`] are the simplest entry points. For repeated
10//! generation, construct a `Generator` once with [`Generator::with_config`]
11//! and call [`Generator::generate`] for each expression.
12
13use std::borrow::Cow;
14use std::sync::Arc;
15
16use crate::error::Result;
17use crate::expressions::*;
18use crate::DialectType;
19
20/// SQL code generator that converts an AST (`Expression`) back into a SQL string.
21///
22/// The generator walks the expression tree and emits dialect-specific SQL text.
23/// It supports pretty-printing with configurable indentation, identifier quoting,
24/// keyword casing, function name normalization, and 30+ SQL dialect variants.
25///
26/// # Usage
27///
28/// ```rust,ignore
29/// use polyglot_sql::generator::Generator;
30/// use polyglot_sql::parser::Parser;
31///
32/// let ast = Parser::parse_sql("SELECT 1")?;
33/// // Quick one-shot generation (default config):
34/// let sql = Generator::sql(&ast[0])?;
35///
36/// // Pretty-printed output:
37/// let pretty = Generator::pretty_sql(&ast[0])?;
38///
39/// // Custom config (e.g. for a specific dialect):
40/// let config = GeneratorConfig { pretty: true, ..GeneratorConfig::default() };
41/// let mut gen = Generator::with_config(config);
42/// let sql = gen.generate(&ast[0])?;
43/// ```
44pub struct Generator {
45    config: Arc<GeneratorConfig>,
46    output: String,
47    unsupported_messages: Vec<String>,
48    indent_level: usize,
49    /// Athena dialect: true when generating Hive-style DDL (uses backticks)
50    /// false when generating Trino-style DML/CREATE VIEW (uses double quotes)
51    athena_hive_context: bool,
52    /// SQLite: column names that should have PRIMARY KEY inlined (from single-column table constraints)
53    sqlite_inline_pk_columns: std::collections::HashSet<String>,
54    /// MERGE: table name/alias qualifiers to strip from UPDATE SET left side (for PostgreSQL)
55    merge_strip_qualifiers: Vec<String>,
56    /// ClickHouse: depth counter for Nullable wrapping context in CAST types.
57    /// 0 = not in cast context, 1 = top-level cast type, 2+ = inside container type.
58    /// Positive values indicate the type should be wrapped in Nullable (for non-container types).
59    /// Negative values indicate map key context (should NOT be wrapped).
60    clickhouse_nullable_depth: i32,
61}
62
63/// Controls how SQL function names are cased in generated output.
64///
65/// - `Upper` (default) -- `COUNT`, `SUM`, `COALESCE`
66/// - `Lower` -- `count`, `sum`, `coalesce`
67/// - `None` -- preserve the original casing from the parsed input
68#[derive(Debug, Clone, Copy, PartialEq, Default)]
69pub enum NormalizeFunctions {
70    /// Emit function names in UPPER CASE (default).
71    #[default]
72    Upper,
73    /// Emit function names in lower case.
74    Lower,
75    /// Preserve the original casing from the parsed input.
76    None,
77}
78
79/// Strategy for generating row-limiting clauses across SQL dialects.
80#[derive(Debug, Clone, Copy, PartialEq, Default)]
81pub enum LimitFetchStyle {
82    /// `LIMIT n` -- MySQL, PostgreSQL, DuckDB, and most modern dialects.
83    #[default]
84    Limit,
85    /// `TOP n` -- TSQL (SQL Server).
86    Top,
87    /// `FETCH FIRST n ROWS ONLY` -- ISO/ANSI SQL standard, Oracle, DB2.
88    FetchFirst,
89}
90
91/// Strategy for rendering negated IN predicates.
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
93pub enum NotInStyle {
94    /// Emit `NOT x IN (...)` in generic mode (current compatibility behavior).
95    #[default]
96    Prefix,
97    /// Emit `x NOT IN (...)` in generic mode (canonical SQL style).
98    Infix,
99}
100
101/// Controls how the generator reacts when it encounters unsupported output.
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
103pub enum UnsupportedLevel {
104    /// Ignore unsupported diagnostics and continue generation.
105    Ignore,
106    /// Collect unsupported diagnostics and continue generation.
107    #[default]
108    Warn,
109    /// Collect unsupported diagnostics and raise after generation completes.
110    Raise,
111    /// Raise immediately when the first unsupported feature is encountered.
112    Immediate,
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116enum ConnectorOperator {
117    And,
118    Or,
119}
120
121impl ConnectorOperator {
122    fn keyword(self) -> &'static str {
123        match self {
124            Self::And => "AND",
125            Self::Or => "OR",
126        }
127    }
128}
129
130/// Identifier quote style (start/end characters)
131#[derive(Debug, Clone, Copy, PartialEq)]
132pub struct IdentifierQuoteStyle {
133    /// Start character for quoting identifiers (e.g., '"', '`', '[')
134    pub start: char,
135    /// End character for quoting identifiers (e.g., '"', '`', ']')
136    pub end: char,
137}
138
139impl Default for IdentifierQuoteStyle {
140    fn default() -> Self {
141        Self {
142            start: '"',
143            end: '"',
144        }
145    }
146}
147
148impl IdentifierQuoteStyle {
149    /// Double-quote style (PostgreSQL, Oracle, standard SQL)
150    pub const DOUBLE_QUOTE: Self = Self {
151        start: '"',
152        end: '"',
153    };
154    /// Backtick style (MySQL, BigQuery, Spark, Hive)
155    pub const BACKTICK: Self = Self {
156        start: '`',
157        end: '`',
158    };
159    /// Square bracket style (TSQL, SQLite)
160    pub const BRACKET: Self = Self {
161        start: '[',
162        end: ']',
163    };
164}
165
166/// Configuration for the SQL [`Generator`].
167///
168/// This is a comprehensive port of the Python sqlglot `Generator` class attributes.
169/// It controls every aspect of SQL output: formatting, quoting, dialect-specific
170/// syntax, feature support flags, and more.
171///
172/// Most users should start from `GeneratorConfig::default()` and override only the
173/// fields they need. Dialect-specific presets are applied automatically when
174/// `dialect` is set via the higher-level transpilation API.
175///
176/// # Key fields
177///
178/// | Field | Default | Purpose |
179/// |-------|---------|---------|
180/// | `dialect` | `None` | Target SQL dialect (e.g. PostgreSQL, MySQL, BigQuery) |
181/// | `pretty` | `false` | Enable multi-line, indented output |
182/// | `indent` | `"  "` | Indentation string used when `pretty` is true |
183/// | `max_text_width` | `80` | Soft line-width limit for pretty-printing |
184/// | `normalize_functions` | `Upper` | Function name casing (`Upper`, `Lower`, `None`) |
185/// | `identifier_quote_style` | `"…"` | Quote characters for identifiers |
186/// | `uppercase_keywords` | `true` | Whether SQL keywords are upper-cased |
187#[derive(Debug, Clone)]
188pub struct GeneratorConfig {
189    // ===== Basic formatting =====
190    /// Pretty print with indentation
191    pub pretty: bool,
192    /// Indentation string (default 2 spaces)
193    pub indent: &'static str,
194    /// Maximum text width before wrapping (default 80)
195    pub max_text_width: usize,
196    /// Quote identifier style (deprecated, use identifier_quote_style instead)
197    pub identifier_quote: char,
198    /// Identifier quote style with separate start/end characters
199    pub identifier_quote_style: IdentifierQuoteStyle,
200    /// Uppercase keywords
201    pub uppercase_keywords: bool,
202    /// Normalize identifiers to lowercase when generating
203    pub normalize_identifiers: bool,
204    /// Dialect type for dialect-specific generation
205    pub dialect: Option<crate::dialects::DialectType>,
206    /// Source dialect type (used during transpilation to distinguish identity vs cross-dialect)
207    pub source_dialect: Option<crate::dialects::DialectType>,
208    /// How unsupported generation should be handled.
209    pub unsupported_level: UnsupportedLevel,
210    /// Maximum number of unsupported diagnostics to include in raised errors.
211    pub max_unsupported: usize,
212    /// How to output function names (UPPER, lower, or as-is)
213    pub normalize_functions: NormalizeFunctions,
214    /// String escape character
215    pub string_escape: char,
216    /// Whether identifiers are case-sensitive
217    pub case_sensitive_identifiers: bool,
218    /// Whether unquoted identifiers can start with a digit
219    pub identifiers_can_start_with_digit: bool,
220    /// Whether to always quote identifiers regardless of reserved keyword status
221    /// Used by dialects like Athena/Presto that prefer quoted identifiers
222    pub always_quote_identifiers: bool,
223    /// How to render negated IN predicates in generic output.
224    pub not_in_style: NotInStyle,
225
226    // ===== Null handling =====
227    /// Whether null ordering (NULLS FIRST/LAST) is supported in ORDER BY
228    /// True: Full Support, false: No support
229    pub null_ordering_supported: bool,
230    /// Whether ignore nulls is inside the agg or outside
231    /// FIRST(x IGNORE NULLS) OVER vs FIRST(x) IGNORE NULLS OVER
232    pub ignore_nulls_in_func: bool,
233    /// Whether the NVL2 function is supported
234    pub nvl2_supported: bool,
235
236    // ===== Limit/Fetch =====
237    /// How to output LIMIT clauses
238    pub limit_fetch_style: LimitFetchStyle,
239    /// Whether to generate the limit as TOP <value> instead of LIMIT <value>
240    pub limit_is_top: bool,
241    /// Whether limit and fetch allows expressions or just literals
242    pub limit_only_literals: bool,
243
244    // ===== Interval =====
245    /// Whether INTERVAL uses single quoted string ('1 day' vs 1 DAY)
246    pub single_string_interval: bool,
247    /// Whether the plural form of date parts (e.g., "days") is supported in INTERVALs
248    pub interval_allows_plural_form: bool,
249
250    // ===== CTE =====
251    /// Whether WITH RECURSIVE keyword is required (vs just WITH for recursive CTEs)
252    pub cte_recursive_keyword_required: bool,
253
254    // ===== VALUES =====
255    /// Whether VALUES can be used as a table source
256    pub values_as_table: bool,
257    /// Wrap derived values in parens (standard but Spark doesn't support)
258    pub wrap_derived_values: bool,
259
260    // ===== TABLESAMPLE =====
261    /// Keyword for TABLESAMPLE seed: "SEED" or "REPEATABLE"
262    pub tablesample_seed_keyword: &'static str,
263    /// Whether parentheses are required around the table sample's expression
264    pub tablesample_requires_parens: bool,
265    /// Whether a table sample clause's size needs to be followed by ROWS keyword
266    pub tablesample_size_is_rows: bool,
267    /// The keyword(s) to use when generating a sample clause
268    pub tablesample_keywords: &'static str,
269    /// Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
270    pub tablesample_with_method: bool,
271    /// Whether the table alias comes after tablesample (Oracle, Hive)
272    pub alias_post_tablesample: bool,
273
274    // ===== Aggregate =====
275    /// Whether aggregate FILTER (WHERE ...) is supported
276    pub aggregate_filter_supported: bool,
277    /// Whether DISTINCT can be followed by multiple args in an AggFunc
278    pub multi_arg_distinct: bool,
279    /// Whether ANY/ALL quantifiers have no space before `(`: `ANY(` vs `ANY (`
280    pub quantified_no_paren_space: bool,
281    /// Whether MEDIAN(expr) is supported; if not, generates PERCENTILE_CONT
282    pub supports_median: bool,
283
284    // ===== SELECT =====
285    /// Whether SELECT ... INTO is supported
286    pub supports_select_into: bool,
287    /// Whether locking reads (SELECT ... FOR UPDATE/SHARE) are supported
288    pub locking_reads_supported: bool,
289
290    // ===== Table/Join =====
291    /// Whether a table is allowed to be renamed with a db
292    pub rename_table_with_db: bool,
293    /// Whether JOIN sides (LEFT, RIGHT) are supported with SEMI/ANTI join kinds
294    pub semi_anti_join_with_side: bool,
295    /// Whether named columns are allowed in table aliases
296    pub supports_table_alias_columns: bool,
297    /// Whether join hints should be generated
298    pub join_hints: bool,
299    /// Whether table hints should be generated
300    pub table_hints: bool,
301    /// Whether query hints should be generated
302    pub query_hints: bool,
303    /// What kind of separator to use for query hints
304    pub query_hint_sep: &'static str,
305    /// Whether Oracle-style (+) join markers are supported (Oracle, Exasol)
306    pub supports_column_join_marks: bool,
307
308    // ===== DDL =====
309    /// Whether CREATE INDEX USING method should have no space before column parens
310    /// true: `USING btree(col)`, false: `USING btree (col)`
311    pub index_using_no_space: bool,
312    /// Whether UNLOGGED tables can be created
313    pub supports_unlogged_tables: bool,
314    /// Whether CREATE TABLE LIKE statement is supported
315    pub supports_create_table_like: bool,
316    /// Whether the LikeProperty needs to be inside the schema clause
317    pub like_property_inside_schema: bool,
318    /// Whether the word COLUMN is included when adding a column with ALTER TABLE
319    pub alter_table_include_column_keyword: bool,
320    /// Whether CREATE TABLE .. COPY .. is supported (false = CLONE instead)
321    pub supports_table_copy: bool,
322    /// The syntax to use when altering the type of a column
323    pub alter_set_type: &'static str,
324    /// Whether to wrap <props> in AlterSet, e.g., ALTER ... SET (<props>)
325    pub alter_set_wrapped: bool,
326
327    // ===== Timestamp/Timezone =====
328    /// Whether TIMESTAMP WITH TIME ZONE is used (vs TIMESTAMPTZ)
329    pub tz_to_with_time_zone: bool,
330    /// Whether CONVERT_TIMEZONE() is supported
331    pub supports_convert_timezone: bool,
332
333    // ===== JSON =====
334    /// Whether the JSON extraction operators expect a value of type JSON
335    pub json_type_required_for_extraction: bool,
336    /// Whether bracketed keys like ["foo"] are supported in JSON paths
337    pub json_path_bracketed_key_supported: bool,
338    /// Whether to escape keys using single quotes in JSON paths
339    pub json_path_single_quote_escape: bool,
340    /// Whether to quote the generated expression of JsonPath
341    pub quote_json_path: bool,
342    /// What delimiter to use for separating JSON key/value pairs
343    pub json_key_value_pair_sep: &'static str,
344
345    // ===== COPY =====
346    /// Whether parameters from COPY statement are wrapped in parentheses
347    pub copy_params_are_wrapped: bool,
348    /// Whether values of params are set with "=" token or empty space
349    pub copy_params_eq_required: bool,
350    /// Whether COPY statement has INTO keyword
351    pub copy_has_into_keyword: bool,
352
353    // ===== Window functions =====
354    /// Whether EXCLUDE in window specification is supported
355    pub supports_window_exclude: bool,
356    /// UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
357    pub unnest_with_ordinality: bool,
358    /// Whether window frame keywords (ROWS/RANGE/GROUPS, PRECEDING/FOLLOWING) should be lowercase
359    /// Exasol uses lowercase for these specific keywords
360    pub lowercase_window_frame_keywords: bool,
361    /// Whether to normalize single-bound window frames to BETWEEN form
362    /// e.g., ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
363    pub normalize_window_frame_between: bool,
364
365    // ===== Array =====
366    /// Whether ARRAY_CONCAT can be generated with varlen args
367    pub array_concat_is_var_len: bool,
368    /// Whether exp.ArraySize should generate the dimension arg too
369    /// None -> Doesn't support, false -> optional, true -> required
370    pub array_size_dim_required: Option<bool>,
371    /// Whether any(f(x) for x in array) can be implemented
372    pub can_implement_array_any: bool,
373    /// Function used for array size
374    pub array_size_name: &'static str,
375
376    // ===== BETWEEN =====
377    /// Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN
378    pub supports_between_flags: bool,
379
380    // ===== Boolean =====
381    /// Whether comparing against booleans (e.g. x IS TRUE) is supported
382    pub is_bool_allowed: bool,
383    /// Whether conditions require booleans WHERE x = 0 vs WHERE x
384    pub ensure_bools: bool,
385
386    // ===== EXTRACT =====
387    /// Whether to generate an unquoted value for EXTRACT's date part argument
388    pub extract_allows_quotes: bool,
389    /// Whether to normalize date parts in EXTRACT
390    pub normalize_extract_date_parts: bool,
391
392    // ===== Other features =====
393    /// Whether the conditional TRY(expression) function is supported
394    pub try_supported: bool,
395    /// Whether the UESCAPE syntax in unicode strings is supported
396    pub supports_uescape: bool,
397    /// Whether the function TO_NUMBER is supported
398    pub supports_to_number: bool,
399    /// Whether CONCAT requires >1 arguments
400    pub supports_single_arg_concat: bool,
401    /// Whether LAST_DAY function supports a date part argument
402    pub last_day_supports_date_part: bool,
403    /// Whether a projection can explode into multiple rows
404    pub supports_exploding_projections: bool,
405    /// Whether UNIX_SECONDS(timestamp) is supported
406    pub supports_unix_seconds: bool,
407    /// Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
408    pub supports_like_quantifiers: bool,
409    /// Whether multi-argument DECODE(...) function is supported
410    pub supports_decode_case: bool,
411    /// Whether set op modifiers apply to the outer set op or select
412    pub set_op_modifiers: bool,
413    /// Whether FROM is supported in UPDATE statements
414    pub update_statement_supports_from: bool,
415
416    // ===== COLLATE =====
417    /// Whether COLLATE is a function instead of a binary operator
418    pub collate_is_func: bool,
419
420    // ===== INSERT =====
421    /// Whether to include "SET" keyword in "INSERT ... ON DUPLICATE KEY UPDATE"
422    pub duplicate_key_update_with_set: bool,
423    /// INSERT OVERWRITE TABLE x override
424    pub insert_overwrite: &'static str,
425
426    // ===== RETURNING =====
427    /// Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
428    pub returning_end: bool,
429
430    // ===== MERGE =====
431    /// Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
432    pub matched_by_source: bool,
433
434    // ===== CREATE FUNCTION =====
435    /// Whether create function uses an AS before the RETURN
436    pub create_function_return_as: bool,
437    /// Whether to use = instead of DEFAULT for parameter defaults (TSQL style)
438    pub parameter_default_equals: bool,
439
440    // ===== COMPUTED COLUMN =====
441    /// Whether to include the type of a computed column in the CREATE DDL
442    pub computed_column_with_type: bool,
443
444    // ===== UNPIVOT =====
445    /// Whether UNPIVOT aliases are Identifiers (false means they're Literals)
446    pub unpivot_aliases_are_identifiers: bool,
447
448    // ===== STAR =====
449    /// The keyword to use when generating a star projection with excluded columns
450    pub star_except: &'static str,
451
452    // ===== HEX =====
453    /// The HEX function name
454    pub hex_func: &'static str,
455
456    // ===== WITH =====
457    /// The keywords to use when prefixing WITH based properties
458    pub with_properties_prefix: &'static str,
459
460    // ===== PAD =====
461    /// Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional
462    pub pad_fill_pattern_is_required: bool,
463
464    // ===== INDEX =====
465    /// The string used for creating an index on a table
466    pub index_on: &'static str,
467
468    // ===== GROUPING =====
469    /// The separator for grouping sets and rollups
470    pub groupings_sep: &'static str,
471
472    // ===== STRUCT =====
473    /// Delimiters for STRUCT type
474    pub struct_delimiter: (&'static str, &'static str),
475    /// Whether Struct expressions use curly brace notation: {'key': value} (DuckDB)
476    pub struct_curly_brace_notation: bool,
477    /// Whether Array expressions omit the ARRAY keyword: [1, 2] instead of ARRAY[1, 2]
478    pub array_bracket_only: bool,
479    /// Separator between struct field name and type (": " for Hive, " " for others)
480    pub struct_field_sep: &'static str,
481
482    // ===== EXCEPT/INTERSECT =====
483    /// Whether EXCEPT and INTERSECT operations can return duplicates
484    pub except_intersect_support_all_clause: bool,
485
486    // ===== PARAMETERS/PLACEHOLDERS =====
487    /// Parameter token character (@ for TSQL, $ for PostgreSQL)
488    pub parameter_token: &'static str,
489    /// Named placeholder token (: for most, % for PostgreSQL)
490    pub named_placeholder_token: &'static str,
491
492    // ===== DATA TYPES =====
493    /// Whether data types support additional specifiers like CHAR or BYTE (oracle)
494    pub data_type_specifiers_allowed: bool,
495
496    // ===== COMMENT =====
497    /// Whether schema comments use `=` sign (COMMENT='value' vs COMMENT 'value')
498    /// StarRocks and Doris use naked COMMENT syntax without `=`
499    pub schema_comment_with_eq: bool,
500}
501
502impl Default for GeneratorConfig {
503    fn default() -> Self {
504        Self {
505            // ===== Basic formatting =====
506            pretty: false,
507            indent: "  ",
508            max_text_width: 80,
509            identifier_quote: '"',
510            identifier_quote_style: IdentifierQuoteStyle::DOUBLE_QUOTE,
511            uppercase_keywords: true,
512            normalize_identifiers: false,
513            dialect: None,
514            source_dialect: None,
515            unsupported_level: UnsupportedLevel::Warn,
516            max_unsupported: 3,
517            normalize_functions: NormalizeFunctions::Upper,
518            string_escape: '\'',
519            case_sensitive_identifiers: false,
520            identifiers_can_start_with_digit: false,
521            always_quote_identifiers: false,
522            not_in_style: NotInStyle::Prefix,
523
524            // ===== Null handling =====
525            null_ordering_supported: true,
526            ignore_nulls_in_func: false,
527            nvl2_supported: true,
528
529            // ===== Limit/Fetch =====
530            limit_fetch_style: LimitFetchStyle::Limit,
531            limit_is_top: false,
532            limit_only_literals: false,
533
534            // ===== Interval =====
535            single_string_interval: false,
536            interval_allows_plural_form: true,
537
538            // ===== CTE =====
539            cte_recursive_keyword_required: true,
540
541            // ===== VALUES =====
542            values_as_table: true,
543            wrap_derived_values: true,
544
545            // ===== TABLESAMPLE =====
546            tablesample_seed_keyword: "SEED",
547            tablesample_requires_parens: true,
548            tablesample_size_is_rows: true,
549            tablesample_keywords: "TABLESAMPLE",
550            tablesample_with_method: true,
551            alias_post_tablesample: false,
552
553            // ===== Aggregate =====
554            aggregate_filter_supported: true,
555            multi_arg_distinct: true,
556            quantified_no_paren_space: false,
557            supports_median: true,
558
559            // ===== SELECT =====
560            supports_select_into: false,
561            locking_reads_supported: true,
562
563            // ===== Table/Join =====
564            rename_table_with_db: true,
565            semi_anti_join_with_side: true,
566            supports_table_alias_columns: true,
567            join_hints: true,
568            table_hints: true,
569            query_hints: true,
570            query_hint_sep: ", ",
571            supports_column_join_marks: false,
572
573            // ===== DDL =====
574            index_using_no_space: false,
575            supports_unlogged_tables: false,
576            supports_create_table_like: true,
577            like_property_inside_schema: false,
578            alter_table_include_column_keyword: true,
579            supports_table_copy: true,
580            alter_set_type: "SET DATA TYPE",
581            alter_set_wrapped: false,
582
583            // ===== Timestamp/Timezone =====
584            tz_to_with_time_zone: false,
585            supports_convert_timezone: false,
586
587            // ===== JSON =====
588            json_type_required_for_extraction: false,
589            json_path_bracketed_key_supported: true,
590            json_path_single_quote_escape: false,
591            quote_json_path: true,
592            json_key_value_pair_sep: ":",
593
594            // ===== COPY =====
595            copy_params_are_wrapped: true,
596            copy_params_eq_required: false,
597            copy_has_into_keyword: true,
598
599            // ===== Window functions =====
600            supports_window_exclude: false,
601            unnest_with_ordinality: true,
602            lowercase_window_frame_keywords: false,
603            normalize_window_frame_between: false,
604
605            // ===== Array =====
606            array_concat_is_var_len: true,
607            array_size_dim_required: None,
608            can_implement_array_any: false,
609            array_size_name: "ARRAY_LENGTH",
610
611            // ===== BETWEEN =====
612            supports_between_flags: false,
613
614            // ===== Boolean =====
615            is_bool_allowed: true,
616            ensure_bools: false,
617
618            // ===== EXTRACT =====
619            extract_allows_quotes: true,
620            normalize_extract_date_parts: false,
621
622            // ===== Other features =====
623            try_supported: true,
624            supports_uescape: true,
625            supports_to_number: true,
626            supports_single_arg_concat: true,
627            last_day_supports_date_part: true,
628            supports_exploding_projections: true,
629            supports_unix_seconds: false,
630            supports_like_quantifiers: true,
631            supports_decode_case: true,
632            set_op_modifiers: true,
633            update_statement_supports_from: true,
634
635            // ===== COLLATE =====
636            collate_is_func: false,
637
638            // ===== INSERT =====
639            duplicate_key_update_with_set: true,
640            insert_overwrite: " OVERWRITE TABLE",
641
642            // ===== RETURNING =====
643            returning_end: true,
644
645            // ===== MERGE =====
646            matched_by_source: true,
647
648            // ===== CREATE FUNCTION =====
649            create_function_return_as: true,
650            parameter_default_equals: false,
651
652            // ===== COMPUTED COLUMN =====
653            computed_column_with_type: true,
654
655            // ===== UNPIVOT =====
656            unpivot_aliases_are_identifiers: true,
657
658            // ===== STAR =====
659            star_except: "EXCEPT",
660
661            // ===== HEX =====
662            hex_func: "HEX",
663
664            // ===== WITH =====
665            with_properties_prefix: "WITH",
666
667            // ===== PAD =====
668            pad_fill_pattern_is_required: false,
669
670            // ===== INDEX =====
671            index_on: "ON",
672
673            // ===== GROUPING =====
674            groupings_sep: ",",
675
676            // ===== STRUCT =====
677            struct_delimiter: ("<", ">"),
678            struct_curly_brace_notation: false,
679            array_bracket_only: false,
680            struct_field_sep: " ",
681
682            // ===== EXCEPT/INTERSECT =====
683            except_intersect_support_all_clause: true,
684
685            // ===== PARAMETERS/PLACEHOLDERS =====
686            parameter_token: "@",
687            named_placeholder_token: ":",
688
689            // ===== DATA TYPES =====
690            data_type_specifiers_allowed: false,
691
692            // ===== COMMENT =====
693            schema_comment_with_eq: true,
694        }
695    }
696}
697
698/// SQL reserved keywords that require quoting when used as identifiers
699/// Based on ANSI SQL standards and common dialect-specific reserved words
700mod reserved_keywords {
701    use std::collections::HashSet;
702    use std::sync::LazyLock;
703
704    /// Standard SQL reserved keywords (ANSI SQL:2016)
705    pub static SQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
706        [
707            "all",
708            "alter",
709            "and",
710            "any",
711            "array",
712            "as",
713            "asc",
714            "at",
715            "authorization",
716            "begin",
717            "between",
718            "both",
719            "by",
720            "case",
721            "cast",
722            "check",
723            "collate",
724            "column",
725            "commit",
726            "constraint",
727            "create",
728            "cross",
729            "cube",
730            "current",
731            "current_date",
732            "current_time",
733            "current_timestamp",
734            "current_user",
735            "default",
736            "delete",
737            "desc",
738            "distinct",
739            "drop",
740            "else",
741            "end",
742            "escape",
743            "except",
744            "execute",
745            "exists",
746            "external",
747            "false",
748            "fetch",
749            "filter",
750            "for",
751            "foreign",
752            "from",
753            "full",
754            "function",
755            "grant",
756            "group",
757            "grouping",
758            "having",
759            "if",
760            "in",
761            "index",
762            "inner",
763            "insert",
764            "intersect",
765            "interval",
766            "into",
767            "is",
768            "join",
769            "key",
770            "leading",
771            "left",
772            "like",
773            "limit",
774            "local",
775            "localtime",
776            "localtimestamp",
777            "match",
778            "merge",
779            "natural",
780            "no",
781            "not",
782            "null",
783            "of",
784            "offset",
785            "on",
786            "only",
787            "or",
788            "order",
789            "outer",
790            "over",
791            "partition",
792            "primary",
793            "procedure",
794            "range",
795            "references",
796            "right",
797            "rollback",
798            "rollup",
799            "row",
800            "rows",
801            "select",
802            "session_user",
803            "set",
804            "some",
805            "table",
806            "tablesample",
807            "then",
808            "to",
809            "trailing",
810            "true",
811            "truncate",
812            "union",
813            "unique",
814            "unknown",
815            "update",
816            "user",
817            "using",
818            "values",
819            "view",
820            "when",
821            "where",
822            "window",
823            "with",
824        ]
825        .into_iter()
826        .collect()
827    });
828
829    /// BigQuery-specific reserved keywords
830    /// Based on: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords
831    pub static BIGQUERY_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
832        let mut set = SQL_RESERVED.clone();
833        set.extend([
834            "assert_rows_modified",
835            "at",
836            "contains",
837            "cube",
838            "current",
839            "define",
840            "enum",
841            "escape",
842            "exclude",
843            "following",
844            "for",
845            "groups",
846            "hash",
847            "ignore",
848            "lateral",
849            "lookup",
850            "new",
851            "no",
852            "nulls",
853            "of",
854            "over",
855            "preceding",
856            "proto",
857            "qualify",
858            "recursive",
859            "respect",
860            "struct",
861            "tablesample",
862            "treat",
863            "unbounded",
864            "unnest",
865            "window",
866            "within",
867        ]);
868        // BigQuery does NOT reserve these keywords - they can be used as identifiers unquoted
869        set.remove("grant");
870        set.remove("key");
871        set.remove("index");
872        set.remove("values");
873        set.remove("table");
874        set
875    });
876
877    /// MySQL-specific reserved keywords
878    pub static MYSQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
879        let mut set = SQL_RESERVED.clone();
880        set.extend([
881            "accessible",
882            "add",
883            "analyze",
884            "asensitive",
885            "before",
886            "bigint",
887            "binary",
888            "blob",
889            "call",
890            "cascade",
891            "change",
892            "char",
893            "character",
894            "condition",
895            "continue",
896            "convert",
897            "current_date",
898            "current_time",
899            "current_timestamp",
900            "current_user",
901            "cursor",
902            "database",
903            "databases",
904            "day_hour",
905            "day_microsecond",
906            "day_minute",
907            "day_second",
908            "dec",
909            "decimal",
910            "declare",
911            "delayed",
912            "describe",
913            "deterministic",
914            "distinctrow",
915            "div",
916            "double",
917            "dual",
918            "each",
919            "elseif",
920            "enclosed",
921            "escaped",
922            "exit",
923            "explain",
924            "float",
925            "float4",
926            "float8",
927            "force",
928            "get",
929            "high_priority",
930            "hour_microsecond",
931            "hour_minute",
932            "hour_second",
933            "ignore",
934            "infile",
935            "inout",
936            "insensitive",
937            "int",
938            "int1",
939            "int2",
940            "int3",
941            "int4",
942            "int8",
943            "integer",
944            "iterate",
945            "keys",
946            "kill",
947            "leave",
948            "linear",
949            "lines",
950            "load",
951            "lock",
952            "long",
953            "longblob",
954            "longtext",
955            "loop",
956            "low_priority",
957            "master_ssl_verify_server_cert",
958            "maxvalue",
959            "mediumblob",
960            "mediumint",
961            "mediumtext",
962            "middleint",
963            "minute_microsecond",
964            "minute_second",
965            "mod",
966            "modifies",
967            "no_write_to_binlog",
968            "numeric",
969            "optimize",
970            "option",
971            "optionally",
972            "out",
973            "outfile",
974            "precision",
975            "purge",
976            "read",
977            "reads",
978            "real",
979            "regexp",
980            "release",
981            "rename",
982            "repeat",
983            "replace",
984            "require",
985            "resignal",
986            "restrict",
987            "return",
988            "revoke",
989            "rlike",
990            "schema",
991            "schemas",
992            "second_microsecond",
993            "sensitive",
994            "separator",
995            "show",
996            "signal",
997            "smallint",
998            "spatial",
999            "specific",
1000            "sql",
1001            "sql_big_result",
1002            "sql_calc_found_rows",
1003            "sql_small_result",
1004            "sqlexception",
1005            "sqlstate",
1006            "sqlwarning",
1007            "ssl",
1008            "starting",
1009            "straight_join",
1010            "terminated",
1011            "text",
1012            "tinyblob",
1013            "tinyint",
1014            "tinytext",
1015            "trigger",
1016            "undo",
1017            "unlock",
1018            "unsigned",
1019            "usage",
1020            "utc_date",
1021            "utc_time",
1022            "utc_timestamp",
1023            "varbinary",
1024            "varchar",
1025            "varcharacter",
1026            "varying",
1027            "while",
1028            "write",
1029            "xor",
1030            "year_month",
1031            "zerofill",
1032        ]);
1033        set.remove("table");
1034        set
1035    });
1036
1037    /// Doris-specific reserved keywords
1038    /// Extends MySQL reserved with additional Doris-specific words
1039    pub static DORIS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1040        let mut set = MYSQL_RESERVED.clone();
1041        set.extend([
1042            "aggregate",
1043            "anti",
1044            "array",
1045            "backend",
1046            "backup",
1047            "begin",
1048            "bitmap",
1049            "boolean",
1050            "broker",
1051            "buckets",
1052            "cached",
1053            "cancel",
1054            "cast",
1055            "catalog",
1056            "charset",
1057            "cluster",
1058            "collation",
1059            "columns",
1060            "comment",
1061            "commit",
1062            "config",
1063            "connection",
1064            "count",
1065            "current",
1066            "data",
1067            "date",
1068            "datetime",
1069            "day",
1070            "deferred",
1071            "distributed",
1072            "dynamic",
1073            "enable",
1074            "end",
1075            "events",
1076            "export",
1077            "external",
1078            "fields",
1079            "first",
1080            "follower",
1081            "format",
1082            "free",
1083            "frontend",
1084            "full",
1085            "functions",
1086            "global",
1087            "grants",
1088            "hash",
1089            "help",
1090            "hour",
1091            "install",
1092            "intermediate",
1093            "json",
1094            "label",
1095            "last",
1096            "less",
1097            "level",
1098            "link",
1099            "local",
1100            "location",
1101            "max",
1102            "merge",
1103            "min",
1104            "minute",
1105            "modify",
1106            "month",
1107            "name",
1108            "names",
1109            "negative",
1110            "nulls",
1111            "observer",
1112            "offset",
1113            "only",
1114            "open",
1115            "overwrite",
1116            "password",
1117            "path",
1118            "plan",
1119            "plugin",
1120            "plugins",
1121            "policy",
1122            "process",
1123            "properties",
1124            "property",
1125            "query",
1126            "quota",
1127            "recover",
1128            "refresh",
1129            "repair",
1130            "replica",
1131            "repository",
1132            "resource",
1133            "restore",
1134            "resume",
1135            "role",
1136            "roles",
1137            "rollback",
1138            "rollup",
1139            "routine",
1140            "sample",
1141            "second",
1142            "semi",
1143            "session",
1144            "signed",
1145            "snapshot",
1146            "start",
1147            "stats",
1148            "status",
1149            "stop",
1150            "stream",
1151            "string",
1152            "sum",
1153            "tables",
1154            "tablet",
1155            "temporary",
1156            "text",
1157            "timestamp",
1158            "transaction",
1159            "trash",
1160            "trim",
1161            "truncate",
1162            "type",
1163            "user",
1164            "value",
1165            "variables",
1166            "verbose",
1167            "version",
1168            "view",
1169            "warnings",
1170            "week",
1171            "work",
1172            "year",
1173        ]);
1174        set
1175    });
1176
1177    /// PostgreSQL-specific reserved keywords
1178    pub static POSTGRES_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1179        let mut set = SQL_RESERVED.clone();
1180        set.extend([
1181            "analyse",
1182            "analyze",
1183            "asymmetric",
1184            "binary",
1185            "collation",
1186            "concurrently",
1187            "current_catalog",
1188            "current_role",
1189            "current_schema",
1190            "deferrable",
1191            "do",
1192            "freeze",
1193            "ilike",
1194            "initially",
1195            "isnull",
1196            "lateral",
1197            "notnull",
1198            "placing",
1199            "returning",
1200            "similar",
1201            "symmetric",
1202            "variadic",
1203            "verbose",
1204        ]);
1205        // PostgreSQL doesn't require quoting for these keywords
1206        set.remove("default");
1207        set.remove("interval");
1208        set.remove("match");
1209        set.remove("offset");
1210        set.remove("table");
1211        set
1212    });
1213
1214    /// Redshift-specific reserved keywords
1215    /// Based on: https://docs.aws.amazon.com/redshift/latest/dg/r_pg_keywords.html
1216    /// Note: `index` is NOT reserved in Redshift (unlike PostgreSQL)
1217    pub static REDSHIFT_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1218        [
1219            "aes128",
1220            "aes256",
1221            "all",
1222            "allowoverwrite",
1223            "analyse",
1224            "analyze",
1225            "and",
1226            "any",
1227            "array",
1228            "as",
1229            "asc",
1230            "authorization",
1231            "az64",
1232            "backup",
1233            "between",
1234            "binary",
1235            "blanksasnull",
1236            "both",
1237            "bytedict",
1238            "bzip2",
1239            "case",
1240            "cast",
1241            "check",
1242            "collate",
1243            "column",
1244            "constraint",
1245            "create",
1246            "credentials",
1247            "cross",
1248            "current_date",
1249            "current_time",
1250            "current_timestamp",
1251            "current_user",
1252            "current_user_id",
1253            "default",
1254            "deferrable",
1255            "deflate",
1256            "defrag",
1257            "delta",
1258            "delta32k",
1259            "desc",
1260            "disable",
1261            "distinct",
1262            "do",
1263            "else",
1264            "emptyasnull",
1265            "enable",
1266            "encode",
1267            "encrypt",
1268            "encryption",
1269            "end",
1270            "except",
1271            "explicit",
1272            "false",
1273            "for",
1274            "foreign",
1275            "freeze",
1276            "from",
1277            "full",
1278            "globaldict256",
1279            "globaldict64k",
1280            "grant",
1281            "group",
1282            "gzip",
1283            "having",
1284            "identity",
1285            "ignore",
1286            "ilike",
1287            "in",
1288            "initially",
1289            "inner",
1290            "intersect",
1291            "interval",
1292            "into",
1293            "is",
1294            "isnull",
1295            "join",
1296            "leading",
1297            "left",
1298            "like",
1299            "limit",
1300            "localtime",
1301            "localtimestamp",
1302            "lun",
1303            "luns",
1304            "lzo",
1305            "lzop",
1306            "minus",
1307            "mostly16",
1308            "mostly32",
1309            "mostly8",
1310            "natural",
1311            "new",
1312            "not",
1313            "notnull",
1314            "null",
1315            "nulls",
1316            "off",
1317            "offline",
1318            "offset",
1319            "oid",
1320            "old",
1321            "on",
1322            "only",
1323            "open",
1324            "or",
1325            "order",
1326            "outer",
1327            "overlaps",
1328            "parallel",
1329            "partition",
1330            "percent",
1331            "permissions",
1332            "pivot",
1333            "placing",
1334            "primary",
1335            "raw",
1336            "readratio",
1337            "recover",
1338            "references",
1339            "rejectlog",
1340            "resort",
1341            "respect",
1342            "restore",
1343            "right",
1344            "select",
1345            "session_user",
1346            "similar",
1347            "snapshot",
1348            "some",
1349            "sysdate",
1350            "system",
1351            "table",
1352            "tag",
1353            "tdes",
1354            "text255",
1355            "text32k",
1356            "then",
1357            "timestamp",
1358            "to",
1359            "top",
1360            "trailing",
1361            "true",
1362            "truncatecolumns",
1363            "type",
1364            "union",
1365            "unique",
1366            "unnest",
1367            "unpivot",
1368            "user",
1369            "using",
1370            "verbose",
1371            "wallet",
1372            "when",
1373            "where",
1374            "with",
1375            "without",
1376        ]
1377        .into_iter()
1378        .collect()
1379    });
1380
1381    /// DuckDB-specific reserved keywords
1382    pub static DUCKDB_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1383        let mut set = POSTGRES_RESERVED.clone();
1384        set.extend([
1385            "anti",
1386            "asof",
1387            "columns",
1388            "describe",
1389            "groups",
1390            "macro",
1391            "pivot",
1392            "pivot_longer",
1393            "pivot_wider",
1394            "qualify",
1395            "replace",
1396            "respect",
1397            "semi",
1398            "show",
1399            "table",
1400            "unpivot",
1401        ]);
1402        set.remove("at");
1403        set.remove("key");
1404        set.remove("range");
1405        set.remove("row");
1406        set.remove("values");
1407        set
1408    });
1409
1410    /// Presto/Trino/Athena-specific reserved keywords
1411    pub static PRESTO_TRINO_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1412        let mut set = SQL_RESERVED.clone();
1413        set.extend([
1414            "alter",
1415            "and",
1416            "as",
1417            "between",
1418            "by",
1419            "case",
1420            "cast",
1421            "constraint",
1422            "create",
1423            "cross",
1424            "cube",
1425            "current_catalog",
1426            "current_date",
1427            "current_path",
1428            "current_role",
1429            "current_schema",
1430            "current_time",
1431            "current_timestamp",
1432            "current_user",
1433            "deallocate",
1434            "delete",
1435            "describe",
1436            "distinct",
1437            "drop",
1438            "else",
1439            "end",
1440            "escape",
1441            "except",
1442            "execute",
1443            "exists",
1444            "extract",
1445            "false",
1446            "for",
1447            "from",
1448            "full",
1449            "group",
1450            "grouping",
1451            "having",
1452            "in",
1453            "inner",
1454            "insert",
1455            "intersect",
1456            "into",
1457            "is",
1458            "join",
1459            "json_array",
1460            "json_exists",
1461            "json_object",
1462            "json_query",
1463            "json_table",
1464            "json_value",
1465            "left",
1466            "like",
1467            "listagg",
1468            "localtime",
1469            "localtimestamp",
1470            "natural",
1471            "normalize",
1472            "not",
1473            "null",
1474            "on",
1475            "or",
1476            "order",
1477            "outer",
1478            "prepare",
1479            "recursive",
1480            "right",
1481            "rollup",
1482            "select",
1483            "skip",
1484            "table",
1485            "then",
1486            "trim",
1487            "true",
1488            "uescape",
1489            "union",
1490            "unnest",
1491            "using",
1492            "values",
1493            "when",
1494            "where",
1495            "with",
1496        ]);
1497        // Match sqlglot behavior for Presto/Trino: KEY does not require identifier quoting.
1498        set.remove("key");
1499        set
1500    });
1501
1502    /// StarRocks-specific reserved keywords
1503    /// Based on: https://docs.starrocks.io/docs/sql-reference/sql-statements/keywords/#reserved-keywords
1504    pub static STARROCKS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1505        [
1506            "add",
1507            "all",
1508            "alter",
1509            "analyze",
1510            "and",
1511            "array",
1512            "as",
1513            "asc",
1514            "between",
1515            "bigint",
1516            "bitmap",
1517            "both",
1518            "by",
1519            "case",
1520            "char",
1521            "character",
1522            "check",
1523            "collate",
1524            "column",
1525            "compaction",
1526            "convert",
1527            "create",
1528            "cross",
1529            "cube",
1530            "current_date",
1531            "current_role",
1532            "current_time",
1533            "current_timestamp",
1534            "current_user",
1535            "database",
1536            "databases",
1537            "decimal",
1538            "decimalv2",
1539            "decimal32",
1540            "decimal64",
1541            "decimal128",
1542            "default",
1543            "deferred",
1544            "delete",
1545            "dense_rank",
1546            "desc",
1547            "describe",
1548            "distinct",
1549            "double",
1550            "drop",
1551            "dual",
1552            "else",
1553            "except",
1554            "exists",
1555            "explain",
1556            "false",
1557            "first_value",
1558            "float",
1559            "for",
1560            "force",
1561            "from",
1562            "full",
1563            "function",
1564            "grant",
1565            "group",
1566            "grouping",
1567            "grouping_id",
1568            "groups",
1569            "having",
1570            "hll",
1571            "host",
1572            "if",
1573            "ignore",
1574            "immediate",
1575            "in",
1576            "index",
1577            "infile",
1578            "inner",
1579            "insert",
1580            "int",
1581            "integer",
1582            "intersect",
1583            "into",
1584            "is",
1585            "join",
1586            "json",
1587            "key",
1588            "keys",
1589            "kill",
1590            "lag",
1591            "largeint",
1592            "last_value",
1593            "lateral",
1594            "lead",
1595            "left",
1596            "like",
1597            "limit",
1598            "load",
1599            "localtime",
1600            "localtimestamp",
1601            "maxvalue",
1602            "minus",
1603            "mod",
1604            "not",
1605            "ntile",
1606            "null",
1607            "on",
1608            "or",
1609            "order",
1610            "outer",
1611            "outfile",
1612            "over",
1613            "partition",
1614            "percentile",
1615            "primary",
1616            "procedure",
1617            "qualify",
1618            "range",
1619            "rank",
1620            "read",
1621            "regexp",
1622            "release",
1623            "rename",
1624            "replace",
1625            "revoke",
1626            "right",
1627            "rlike",
1628            "row",
1629            "row_number",
1630            "rows",
1631            "schema",
1632            "schemas",
1633            "select",
1634            "set",
1635            "set_var",
1636            "show",
1637            "smallint",
1638            "system",
1639            "table",
1640            "terminated",
1641            "text",
1642            "then",
1643            "tinyint",
1644            "to",
1645            "true",
1646            "union",
1647            "unique",
1648            "unsigned",
1649            "update",
1650            "use",
1651            "using",
1652            "values",
1653            "varchar",
1654            "when",
1655            "where",
1656            "with",
1657        ]
1658        .into_iter()
1659        .collect()
1660    });
1661
1662    /// SingleStore-specific reserved keywords
1663    /// Based on: https://docs.singlestore.com/cloud/reference/sql-reference/restricted-keywords/list-of-restricted-keywords/
1664    pub static SINGLESTORE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1665        let mut set = MYSQL_RESERVED.clone();
1666        set.extend([
1667            // Additional SingleStore reserved keywords from Python sqlglot
1668            // NOTE: "all" is excluded because ORDER BY ALL needs ALL unquoted
1669            "abs",
1670            "account",
1671            "acos",
1672            "adddate",
1673            "addtime",
1674            "admin",
1675            "aes_decrypt",
1676            "aes_encrypt",
1677            "aggregate",
1678            "aggregates",
1679            "aggregator",
1680            "anti_join",
1681            "any_value",
1682            "approx_count_distinct",
1683            "approx_percentile",
1684            "arrange",
1685            "arrangement",
1686            "asin",
1687            "atan",
1688            "atan2",
1689            "attach",
1690            "autostats",
1691            "avro",
1692            "background",
1693            "backup",
1694            "batch",
1695            "batches",
1696            "boot_strapping",
1697            "ceil",
1698            "ceiling",
1699            "coercibility",
1700            "columnar",
1701            "columnstore",
1702            "compile",
1703            "concurrent",
1704            "connection_id",
1705            "cos",
1706            "cot",
1707            "current_security_groups",
1708            "current_security_roles",
1709            "dayname",
1710            "dayofmonth",
1711            "dayofweek",
1712            "dayofyear",
1713            "degrees",
1714            "dot_product",
1715            "dump",
1716            "durability",
1717            "earliest",
1718            "echo",
1719            "election",
1720            "euclidean_distance",
1721            "exp",
1722            "extractor",
1723            "extractors",
1724            "floor",
1725            "foreground",
1726            "found_rows",
1727            "from_base64",
1728            "from_days",
1729            "from_unixtime",
1730            "fs",
1731            "fulltext",
1732            "gc",
1733            "gcs",
1734            "geography",
1735            "geography_area",
1736            "geography_contains",
1737            "geography_distance",
1738            "geography_intersects",
1739            "geography_latitude",
1740            "geography_length",
1741            "geography_longitude",
1742            "geographypoint",
1743            "geography_point",
1744            "geography_within_distance",
1745            "geometry",
1746            "geometry_area",
1747            "geometry_contains",
1748            "geometry_distance",
1749            "geometry_filter",
1750            "geometry_intersects",
1751            "geometry_length",
1752            "geometrypoint",
1753            "geometry_point",
1754            "geometry_within_distance",
1755            "geometry_x",
1756            "geometry_y",
1757            "greatest",
1758            "groups",
1759            "group_concat",
1760            "gzip",
1761            "hdfs",
1762            "hex",
1763            "highlight",
1764            "ifnull",
1765            "ilike",
1766            "inet_aton",
1767            "inet_ntoa",
1768            "inet6_aton",
1769            "inet6_ntoa",
1770            "initcap",
1771            "instr",
1772            "interpreter_mode",
1773            "isnull",
1774            "json",
1775            "json_agg",
1776            "json_array_contains_double",
1777            "json_array_contains_json",
1778            "json_array_contains_string",
1779            "json_delete_key",
1780            "json_extract_double",
1781            "json_extract_json",
1782            "json_extract_string",
1783            "json_extract_bigint",
1784            "json_get_type",
1785            "json_length",
1786            "json_set_double",
1787            "json_set_json",
1788            "json_set_string",
1789            "kafka",
1790            "lag",
1791            "last_day",
1792            "last_insert_id",
1793            "latest",
1794            "lcase",
1795            "lead",
1796            "leaf",
1797            "least",
1798            "leaves",
1799            "length",
1800            "license",
1801            "links",
1802            "llvm",
1803            "ln",
1804            "load",
1805            "locate",
1806            "log",
1807            "log10",
1808            "log2",
1809            "lpad",
1810            "lz4",
1811            "management",
1812            "match",
1813            "mbc",
1814            "md5",
1815            "median",
1816            "memsql",
1817            "memsql_deserialize",
1818            "memsql_serialize",
1819            "metadata",
1820            "microsecond",
1821            "minute",
1822            "model",
1823            "monthname",
1824            "months_between",
1825            "mpl",
1826            "namespace",
1827            "node",
1828            "noparam",
1829            "now",
1830            "nth_value",
1831            "ntile",
1832            "nullcols",
1833            "nullif",
1834            "object",
1835            "octet_length",
1836            "offsets",
1837            "online",
1838            "optimizer",
1839            "orphan",
1840            "parquet",
1841            "partitions",
1842            "pause",
1843            "percentile_cont",
1844            "percentile_disc",
1845            "periodic",
1846            "persisted",
1847            "pi",
1848            "pipeline",
1849            "pipelines",
1850            "plancache",
1851            "plugins",
1852            "pool",
1853            "pools",
1854            "pow",
1855            "power",
1856            "process",
1857            "processlist",
1858            "profile",
1859            "profiles",
1860            "quarter",
1861            "queries",
1862            "query",
1863            "radians",
1864            "rand",
1865            "record",
1866            "reduce",
1867            "redundancy",
1868            "regexp_match",
1869            "regexp_substr",
1870            "remote",
1871            "replication",
1872            "resource",
1873            "resource_pool",
1874            "restore",
1875            "retry",
1876            "role",
1877            "roles",
1878            "round",
1879            "rpad",
1880            "rtrim",
1881            "running",
1882            "s3",
1883            "scalar",
1884            "sec_to_time",
1885            "second",
1886            "security_lists_intersect",
1887            "semi_join",
1888            "sha",
1889            "sha1",
1890            "sha2",
1891            "shard",
1892            "sharded",
1893            "sharded_id",
1894            "sigmoid",
1895            "sign",
1896            "sin",
1897            "skip",
1898            "sleep",
1899            "snapshot",
1900            "soname",
1901            "sparse",
1902            "spatial_check_index",
1903            "split",
1904            "sqrt",
1905            "standalone",
1906            "std",
1907            "stddev",
1908            "stddev_pop",
1909            "stddev_samp",
1910            "stop",
1911            "str_to_date",
1912            "subdate",
1913            "substr",
1914            "substring_index",
1915            "success",
1916            "synchronize",
1917            "table_checksum",
1918            "tan",
1919            "task",
1920            "timediff",
1921            "time_bucket",
1922            "time_format",
1923            "time_to_sec",
1924            "timestampadd",
1925            "timestampdiff",
1926            "to_base64",
1927            "to_char",
1928            "to_date",
1929            "to_days",
1930            "to_json",
1931            "to_number",
1932            "to_seconds",
1933            "to_timestamp",
1934            "tracelogs",
1935            "transform",
1936            "trim",
1937            "trunc",
1938            "truncate",
1939            "ucase",
1940            "unhex",
1941            "unix_timestamp",
1942            "utc_date",
1943            "utc_time",
1944            "utc_timestamp",
1945            "vacuum",
1946            "variance",
1947            "var_pop",
1948            "var_samp",
1949            "vector_sub",
1950            "voting",
1951            "week",
1952            "weekday",
1953            "weekofyear",
1954            "workload",
1955            "year",
1956        ]);
1957        // Remove "all" because ORDER BY ALL needs ALL unquoted
1958        set.remove("all");
1959        set
1960    });
1961
1962    /// SQLite-specific reserved keywords
1963    /// SQLite has a very minimal set of reserved keywords - most things can be used as identifiers unquoted
1964    /// Reference: https://www.sqlite.org/lang_keywords.html
1965    pub static SQLITE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1966        // SQLite only truly reserves these - everything else can be used as identifier unquoted
1967        [
1968            "abort",
1969            "action",
1970            "add",
1971            "after",
1972            "all",
1973            "alter",
1974            "always",
1975            "analyze",
1976            "and",
1977            "as",
1978            "asc",
1979            "attach",
1980            "autoincrement",
1981            "before",
1982            "begin",
1983            "between",
1984            "by",
1985            "cascade",
1986            "case",
1987            "cast",
1988            "check",
1989            "collate",
1990            "column",
1991            "commit",
1992            "conflict",
1993            "constraint",
1994            "create",
1995            "cross",
1996            "current",
1997            "current_date",
1998            "current_time",
1999            "current_timestamp",
2000            "database",
2001            "default",
2002            "deferrable",
2003            "deferred",
2004            "delete",
2005            "desc",
2006            "detach",
2007            "distinct",
2008            "do",
2009            "drop",
2010            "each",
2011            "else",
2012            "end",
2013            "escape",
2014            "except",
2015            "exclude",
2016            "exclusive",
2017            "exists",
2018            "explain",
2019            "fail",
2020            "filter",
2021            "first",
2022            "following",
2023            "for",
2024            "foreign",
2025            "from",
2026            "full",
2027            "generated",
2028            "glob",
2029            "group",
2030            "groups",
2031            "having",
2032            "if",
2033            "ignore",
2034            "immediate",
2035            "in",
2036            "index",
2037            "indexed",
2038            "initially",
2039            "inner",
2040            "insert",
2041            "instead",
2042            "intersect",
2043            "into",
2044            "is",
2045            "isnull",
2046            "join",
2047            "key",
2048            "last",
2049            "left",
2050            "like",
2051            "limit",
2052            "natural",
2053            "no",
2054            "not",
2055            "nothing",
2056            "notnull",
2057            "null",
2058            "nulls",
2059            "of",
2060            "offset",
2061            "on",
2062            "or",
2063            "order",
2064            "others",
2065            "outer",
2066            "partition",
2067            "plan",
2068            "pragma",
2069            "preceding",
2070            "primary",
2071            "query",
2072            "raise",
2073            "range",
2074            "recursive",
2075            "references",
2076            "regexp",
2077            "reindex",
2078            "release",
2079            "rename",
2080            "replace",
2081            "restrict",
2082            "returning",
2083            "right",
2084            "rollback",
2085            "row",
2086            "rows",
2087            "savepoint",
2088            "select",
2089            "set",
2090            "table",
2091            "temp",
2092            "temporary",
2093            "then",
2094            "ties",
2095            "to",
2096            "transaction",
2097            "trigger",
2098            "unbounded",
2099            "union",
2100            "unique",
2101            "update",
2102            "using",
2103            "vacuum",
2104            "values",
2105            "view",
2106            "virtual",
2107            "when",
2108            "where",
2109            "window",
2110            "with",
2111            "without",
2112        ]
2113        .into_iter()
2114        .collect()
2115    });
2116}
2117
2118impl Generator {
2119    /// Create a new generator with the default configuration.
2120    ///
2121    /// Equivalent to `Generator::with_config(GeneratorConfig::default())`.
2122    /// Uses uppercase keywords, double-quote identifier quoting, no pretty-printing,
2123    /// and no dialect-specific transformations.
2124    pub fn new() -> Self {
2125        Self::with_config(GeneratorConfig::default())
2126    }
2127
2128    /// Create a generator with a custom [`GeneratorConfig`].
2129    ///
2130    /// Use this when you need dialect-specific output, pretty-printing, or other
2131    /// non-default settings.
2132    pub fn with_config(config: GeneratorConfig) -> Self {
2133        Self::with_arc_config(Arc::new(config))
2134    }
2135
2136    /// Create a generator from a shared [`Arc<GeneratorConfig>`].
2137    ///
2138    /// This avoids cloning the configuration when multiple generators share the
2139    /// same settings (e.g. during transpilation). The [`Arc`] is cheap to clone.
2140    pub(crate) fn with_arc_config(config: Arc<GeneratorConfig>) -> Self {
2141        Self {
2142            config,
2143            output: String::new(),
2144            unsupported_messages: Vec::new(),
2145            indent_level: 0,
2146            athena_hive_context: false,
2147            sqlite_inline_pk_columns: std::collections::HashSet::new(),
2148            merge_strip_qualifiers: Vec::new(),
2149            clickhouse_nullable_depth: 0,
2150        }
2151    }
2152
2153    /// Add column aliases to a query expression for TSQL SELECT INTO.
2154    /// This ensures that unaliased columns get explicit aliases (e.g., `a` -> `a AS a`).
2155    /// Recursively processes all SELECT expressions in the query tree.
2156    fn add_column_aliases_to_query(expr: Expression) -> Expression {
2157        match expr {
2158            Expression::Select(mut select) => {
2159                // Add aliases to all select expressions that don't already have them
2160                select.expressions = select
2161                    .expressions
2162                    .into_iter()
2163                    .map(|e| Self::add_alias_to_expression(e))
2164                    .collect();
2165
2166                // Recursively process subqueries in FROM clause
2167                if let Some(ref mut from) = select.from {
2168                    from.expressions = from
2169                        .expressions
2170                        .iter()
2171                        .cloned()
2172                        .map(|e| Self::add_column_aliases_to_query(e))
2173                        .collect();
2174                }
2175
2176                Expression::Select(select)
2177            }
2178            Expression::Subquery(mut sq) => {
2179                sq.this = Self::add_column_aliases_to_query(sq.this);
2180                Expression::Subquery(sq)
2181            }
2182            Expression::Paren(mut p) => {
2183                p.this = Self::add_column_aliases_to_query(p.this);
2184                Expression::Paren(p)
2185            }
2186            // For other expressions (Union, Intersect, etc.), pass through
2187            other => other,
2188        }
2189    }
2190
2191    /// Add an alias to a single select expression if it doesn't already have one.
2192    /// Returns the expression with alias (e.g., `a` -> `a AS a`).
2193    fn add_alias_to_expression(expr: Expression) -> Expression {
2194        use crate::expressions::Alias;
2195
2196        match &expr {
2197            // Already aliased - just return it
2198            Expression::Alias(_) => expr,
2199
2200            // Column reference: add alias from column name
2201            Expression::Column(col) => Expression::Alias(Box::new(Alias {
2202                this: expr.clone(),
2203                alias: col.name.clone(),
2204                column_aliases: Vec::new(),
2205                pre_alias_comments: Vec::new(),
2206                trailing_comments: Vec::new(),
2207                inferred_type: None,
2208            })),
2209
2210            // Identifier: add alias from identifier name
2211            Expression::Identifier(ident) => Expression::Alias(Box::new(Alias {
2212                this: expr.clone(),
2213                alias: ident.clone(),
2214                column_aliases: Vec::new(),
2215                pre_alias_comments: Vec::new(),
2216                trailing_comments: Vec::new(),
2217                inferred_type: None,
2218            })),
2219
2220            // Subquery: recursively process and add alias if inner returns a named column
2221            Expression::Subquery(sq) => {
2222                let processed = Self::add_column_aliases_to_query(Expression::Subquery(sq.clone()));
2223                // Subqueries that are already aliased keep their alias
2224                if sq.alias.is_some() {
2225                    processed
2226                } else {
2227                    // If there's no alias, keep it as-is (let TSQL handle it)
2228                    processed
2229                }
2230            }
2231
2232            // Star expressions (*) - don't alias
2233            Expression::Star(_) => expr,
2234
2235            // For other expressions, don't add an alias
2236            // (function calls, literals, etc. would need explicit aliases anyway)
2237            _ => expr,
2238        }
2239    }
2240
2241    /// Try to evaluate a constant arithmetic expression to a number literal.
2242    /// Returns the evaluated result if the expression is a constant arithmetic expression,
2243    /// otherwise returns the original expression.
2244    fn try_evaluate_constant(expr: &Expression) -> Option<i64> {
2245        match expr {
2246            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
2247                let Literal::Number(n) = lit.as_ref() else {
2248                    unreachable!()
2249                };
2250                n.parse::<i64>().ok()
2251            }
2252            Expression::Add(op) => {
2253                let left = Self::try_evaluate_constant(&op.left)?;
2254                let right = Self::try_evaluate_constant(&op.right)?;
2255                Some(left + right)
2256            }
2257            Expression::Sub(op) => {
2258                let left = Self::try_evaluate_constant(&op.left)?;
2259                let right = Self::try_evaluate_constant(&op.right)?;
2260                Some(left - right)
2261            }
2262            Expression::Mul(op) => {
2263                let left = Self::try_evaluate_constant(&op.left)?;
2264                let right = Self::try_evaluate_constant(&op.right)?;
2265                Some(left * right)
2266            }
2267            Expression::Div(op) => {
2268                let left = Self::try_evaluate_constant(&op.left)?;
2269                let right = Self::try_evaluate_constant(&op.right)?;
2270                if right != 0 {
2271                    Some(left / right)
2272                } else {
2273                    None
2274                }
2275            }
2276            Expression::Paren(p) => Self::try_evaluate_constant(&p.this),
2277            _ => None,
2278        }
2279    }
2280
2281    /// Check if an identifier is a reserved keyword for the current dialect
2282    fn is_reserved_keyword(&self, name: &str) -> bool {
2283        use crate::dialects::DialectType;
2284        let mut buf = [0u8; 128];
2285        let lower_ref: &str = if name.len() <= 128 {
2286            for (i, b) in name.bytes().enumerate() {
2287                buf[i] = b.to_ascii_lowercase();
2288            }
2289            // SAFETY: input is valid UTF-8 and ASCII lowercase preserves that
2290            std::str::from_utf8(&buf[..name.len()]).unwrap_or(name)
2291        } else {
2292            return false;
2293        };
2294
2295        match self.config.dialect {
2296            Some(DialectType::BigQuery) => reserved_keywords::BIGQUERY_RESERVED.contains(lower_ref),
2297            Some(DialectType::MySQL) | Some(DialectType::TiDB) => {
2298                reserved_keywords::MYSQL_RESERVED.contains(lower_ref)
2299            }
2300            Some(DialectType::Doris) => reserved_keywords::DORIS_RESERVED.contains(lower_ref),
2301            Some(DialectType::SingleStore) => {
2302                reserved_keywords::SINGLESTORE_RESERVED.contains(lower_ref)
2303            }
2304            Some(DialectType::StarRocks) => {
2305                reserved_keywords::STARROCKS_RESERVED.contains(lower_ref)
2306            }
2307            Some(DialectType::PostgreSQL)
2308            | Some(DialectType::CockroachDB)
2309            | Some(DialectType::Materialize)
2310            | Some(DialectType::RisingWave) => {
2311                reserved_keywords::POSTGRES_RESERVED.contains(lower_ref)
2312            }
2313            Some(DialectType::Redshift) => reserved_keywords::REDSHIFT_RESERVED.contains(lower_ref),
2314            // Snowflake: Python sqlglot has RESERVED_KEYWORDS = set() for Snowflake,
2315            // meaning it never quotes identifiers based on reserved word status.
2316            Some(DialectType::Snowflake) => false,
2317            // ClickHouse: don't quote reserved keywords to preserve identity output
2318            Some(DialectType::ClickHouse) => false,
2319            Some(DialectType::DuckDB) => reserved_keywords::DUCKDB_RESERVED.contains(lower_ref),
2320            // Teradata: Python sqlglot has RESERVED_KEYWORDS = set() for Teradata
2321            Some(DialectType::Teradata) => false,
2322            // TSQL, Fabric, Oracle, Spark, Hive, Solr: Python sqlglot has no RESERVED_KEYWORDS for these dialects, so don't quote identifiers
2323            Some(DialectType::TSQL)
2324            | Some(DialectType::Fabric)
2325            | Some(DialectType::Oracle)
2326            | Some(DialectType::Spark)
2327            | Some(DialectType::Databricks)
2328            | Some(DialectType::Hive)
2329            | Some(DialectType::Solr) => false,
2330            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
2331                reserved_keywords::PRESTO_TRINO_RESERVED.contains(lower_ref)
2332            }
2333            Some(DialectType::SQLite) => reserved_keywords::SQLITE_RESERVED.contains(lower_ref),
2334            // For Generic dialect or None, don't add extra quoting to preserve identity
2335            Some(DialectType::Generic) | None => false,
2336            // For other dialects, use standard SQL reserved keywords
2337            _ => reserved_keywords::SQL_RESERVED.contains(lower_ref),
2338        }
2339    }
2340
2341    /// Normalize function name based on dialect settings
2342    fn normalize_func_name<'a>(&self, name: &'a str) -> Cow<'a, str> {
2343        match self.config.normalize_functions {
2344            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
2345            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
2346            NormalizeFunctions::None => Cow::Borrowed(name),
2347        }
2348    }
2349
2350    /// Generate a SQL string from an AST expression.
2351    ///
2352    /// This is the primary generation method. It clears any previous internal state,
2353    /// walks the expression tree, and returns the resulting SQL text. The output
2354    /// respects the [`GeneratorConfig`] that was supplied at construction time.
2355    ///
2356    /// The generator can be reused across multiple calls; each call to `generate`
2357    /// resets the internal buffer.
2358    pub fn generate(&mut self, expr: &Expression) -> Result<String> {
2359        self.output.clear();
2360        self.unsupported_messages.clear();
2361        self.generate_expression(expr)?;
2362        if self.config.unsupported_level == UnsupportedLevel::Raise
2363            && !self.unsupported_messages.is_empty()
2364        {
2365            return Err(crate::error::Error::generate(
2366                self.format_unsupported_messages(),
2367            ));
2368        }
2369        Ok(std::mem::take(&mut self.output))
2370    }
2371
2372    /// Returns the unsupported diagnostics collected during the most recent generate call.
2373    pub fn unsupported_messages(&self) -> &[String] {
2374        &self.unsupported_messages
2375    }
2376
2377    fn unsupported(&mut self, message: impl Into<String>) -> Result<()> {
2378        let message = message.into();
2379        if self.config.unsupported_level == UnsupportedLevel::Immediate {
2380            return Err(crate::error::Error::generate(message));
2381        }
2382        self.unsupported_messages.push(message);
2383        Ok(())
2384    }
2385
2386    fn write_unsupported_comment(&mut self, message: &str) -> Result<()> {
2387        self.unsupported(message.to_string())?;
2388        self.write("/* ");
2389        self.write(message);
2390        self.write(" */");
2391        Ok(())
2392    }
2393
2394    fn format_unsupported_messages(&self) -> String {
2395        let limit = self.config.max_unsupported.max(1);
2396        if self.unsupported_messages.len() <= limit {
2397            return self.unsupported_messages.join("; ");
2398        }
2399
2400        let mut messages = self
2401            .unsupported_messages
2402            .iter()
2403            .take(limit)
2404            .cloned()
2405            .collect::<Vec<_>>();
2406        messages.push(format!(
2407            "... and {} more",
2408            self.unsupported_messages.len() - limit
2409        ));
2410        messages.join("; ")
2411    }
2412
2413    /// Convenience: generate SQL with the default configuration (no dialect, compact output).
2414    ///
2415    /// This is a static helper that creates a throwaway `Generator` internally.
2416    /// For repeated generation, prefer constructing a `Generator` once and calling
2417    /// [`generate`](Self::generate) on it.
2418    pub fn sql(expr: &Expression) -> Result<String> {
2419        let mut gen = Generator::new();
2420        gen.generate(expr)
2421    }
2422
2423    /// Convenience: generate SQL with pretty-printing enabled (indented, multi-line).
2424    ///
2425    /// Produces human-readable output with newlines and indentation. A trailing
2426    /// semicolon is appended automatically if not already present.
2427    pub fn pretty_sql(expr: &Expression) -> Result<String> {
2428        let config = GeneratorConfig {
2429            pretty: true,
2430            ..Default::default()
2431        };
2432        let mut gen = Generator::with_config(config);
2433        let mut sql = gen.generate(expr)?;
2434        // Add semicolon for pretty output
2435        if !sql.ends_with(';') {
2436            sql.push(';');
2437        }
2438        Ok(sql)
2439    }
2440
2441    fn generate_expression(&mut self, expr: &Expression) -> Result<()> {
2442        match expr {
2443            Expression::Select(select) => self.generate_select(select),
2444            Expression::Union(union) => self.generate_union(union),
2445            Expression::Intersect(intersect) => self.generate_intersect(intersect),
2446            Expression::Except(except) => self.generate_except(except),
2447            Expression::Insert(insert) => self.generate_insert(insert),
2448            Expression::Update(update) => self.generate_update(update),
2449            Expression::Delete(delete) => self.generate_delete(delete),
2450            Expression::Literal(lit) => self.generate_literal(lit),
2451            Expression::Boolean(b) => self.generate_boolean(b),
2452            Expression::Null(_) => {
2453                self.write_keyword("NULL");
2454                Ok(())
2455            }
2456            Expression::Identifier(id) => self.generate_identifier(id),
2457            Expression::Column(col) => self.generate_column(col),
2458            Expression::Pseudocolumn(pc) => self.generate_pseudocolumn(pc),
2459            Expression::Connect(c) => self.generate_connect_expr(c),
2460            Expression::Prior(p) => self.generate_prior(p),
2461            Expression::ConnectByRoot(cbr) => self.generate_connect_by_root(cbr),
2462            Expression::MatchRecognize(mr) => self.generate_match_recognize(mr),
2463            Expression::Table(table) => self.generate_table(table),
2464            Expression::StageReference(sr) => self.generate_stage_reference(sr),
2465            Expression::HistoricalData(hd) => self.generate_historical_data(hd),
2466            Expression::JoinedTable(jt) => self.generate_joined_table(jt),
2467            Expression::Star(star) => self.generate_star(star),
2468            Expression::BracedWildcard(expr) => self.generate_braced_wildcard(expr),
2469            Expression::Alias(alias) => self.generate_alias(alias),
2470            Expression::Cast(cast) => self.generate_cast(cast),
2471            Expression::Collation(coll) => self.generate_collation(coll),
2472            Expression::Case(case) => self.generate_case(case),
2473            Expression::Function(func) => self.generate_function(func),
2474            Expression::FunctionEmits(fe) => self.generate_function_emits(fe),
2475            Expression::AggregateFunction(func) => self.generate_aggregate_function(func),
2476            Expression::WindowFunction(wf) => self.generate_window_function(wf),
2477            Expression::WithinGroup(wg) => self.generate_within_group(wg),
2478            Expression::Interval(interval) => self.generate_interval(interval),
2479
2480            // String functions
2481            Expression::ConcatWs(f) => self.generate_concat_ws(f),
2482            Expression::Substring(f) => self.generate_substring(f),
2483            Expression::Upper(f) => self.generate_unary_func("UPPER", f),
2484            Expression::Lower(f) => self.generate_unary_func("LOWER", f),
2485            Expression::Length(f) => self.generate_unary_func("LENGTH", f),
2486            Expression::Trim(f) => self.generate_trim(f),
2487            Expression::LTrim(f) => self.generate_simple_func("LTRIM", &f.this),
2488            Expression::RTrim(f) => self.generate_simple_func("RTRIM", &f.this),
2489            Expression::Replace(f) => self.generate_replace(f),
2490            Expression::Reverse(f) => self.generate_simple_func("REVERSE", &f.this),
2491            Expression::Left(f) => self.generate_left_right("LEFT", f),
2492            Expression::Right(f) => self.generate_left_right("RIGHT", f),
2493            Expression::Repeat(f) => self.generate_repeat(f),
2494            Expression::Lpad(f) => self.generate_pad("LPAD", f),
2495            Expression::Rpad(f) => self.generate_pad("RPAD", f),
2496            Expression::Split(f) => self.generate_split(f),
2497            Expression::RegexpLike(f) => self.generate_regexp_like(f),
2498            Expression::RegexpReplace(f) => self.generate_regexp_replace(f),
2499            Expression::RegexpExtract(f) => self.generate_regexp_extract(f),
2500            Expression::Overlay(f) => self.generate_overlay(f),
2501
2502            // Math functions
2503            Expression::Abs(f) => self.generate_simple_func("ABS", &f.this),
2504            Expression::Round(f) => self.generate_round(f),
2505            Expression::Floor(f) => self.generate_floor(f),
2506            Expression::Ceil(f) => self.generate_ceil(f),
2507            Expression::Power(f) => self.generate_power(f),
2508            Expression::Sqrt(f) => self.generate_sqrt_cbrt(f, "SQRT", "|/"),
2509            Expression::Cbrt(f) => self.generate_sqrt_cbrt(f, "CBRT", "||/"),
2510            Expression::Ln(f) => self.generate_simple_func("LN", &f.this),
2511            Expression::Log(f) => self.generate_log(f),
2512            Expression::Exp(f) => self.generate_simple_func("EXP", &f.this),
2513            Expression::Sign(f) => self.generate_simple_func("SIGN", &f.this),
2514            Expression::Greatest(f) => self.generate_vararg_func("GREATEST", &f.expressions),
2515            Expression::Least(f) => self.generate_vararg_func("LEAST", &f.expressions),
2516
2517            // Date/time functions
2518            Expression::CurrentDate(_) => {
2519                self.write_keyword("CURRENT_DATE");
2520                Ok(())
2521            }
2522            Expression::CurrentTime(f) => self.generate_current_time(f),
2523            Expression::CurrentTimestamp(f) => self.generate_current_timestamp(f),
2524            Expression::AtTimeZone(f) => self.generate_at_time_zone(f),
2525            Expression::DateAdd(f) => self.generate_date_add(f, "DATE_ADD"),
2526            Expression::DateSub(f) => self.generate_date_add(f, "DATE_SUB"),
2527            Expression::DateDiff(f) => self.generate_datediff(f),
2528            Expression::DateTrunc(f) => self.generate_date_trunc(f),
2529            Expression::Extract(f) => self.generate_extract(f),
2530            Expression::ToDate(f) => self.generate_to_date(f),
2531            Expression::ToTimestamp(f) => self.generate_to_timestamp(f),
2532
2533            // Control flow functions
2534            Expression::Coalesce(f) => {
2535                // Use original function name if preserved (COALESCE, IFNULL)
2536                let func_name = f.original_name.as_deref().unwrap_or("COALESCE");
2537                self.generate_vararg_func(func_name, &f.expressions)
2538            }
2539            Expression::NullIf(f) => self.generate_binary_func("NULLIF", &f.this, &f.expression),
2540            Expression::IfFunc(f) => self.generate_if_func(f),
2541            Expression::IfNull(f) => self.generate_ifnull(f),
2542            Expression::Nvl(f) => self.generate_nvl(f),
2543            Expression::Nvl2(f) => self.generate_nvl2(f),
2544
2545            // Type conversion
2546            Expression::TryCast(cast) => self.generate_try_cast(cast),
2547            Expression::SafeCast(cast) => self.generate_safe_cast(cast),
2548
2549            // Typed aggregate functions
2550            Expression::Count(f) => self.generate_count(f),
2551            Expression::Sum(f) => self.generate_agg_func("SUM", f),
2552            Expression::Avg(f) => self.generate_agg_func("AVG", f),
2553            Expression::Min(f) => self.generate_agg_func("MIN", f),
2554            Expression::Max(f) => self.generate_agg_func("MAX", f),
2555            Expression::GroupConcat(f) => self.generate_group_concat(f),
2556            Expression::StringAgg(f) => self.generate_string_agg(f),
2557            Expression::ListAgg(f) => self.generate_listagg(f),
2558            Expression::ArrayAgg(f) => {
2559                // Allow cross-dialect transforms to override the function name
2560                // (e.g., COLLECT_LIST for Spark)
2561                let override_name = f
2562                    .name
2563                    .as_ref()
2564                    .filter(|n| !n.eq_ignore_ascii_case("ARRAY_AGG"))
2565                    .map(|n| n.to_ascii_uppercase());
2566                match override_name {
2567                    Some(name) => self.generate_agg_func(&name, f),
2568                    None => self.generate_agg_func("ARRAY_AGG", f),
2569                }
2570            }
2571            Expression::ArrayConcatAgg(f) => self.generate_agg_func("ARRAY_CONCAT_AGG", f),
2572            Expression::CountIf(f) => self.generate_agg_func("COUNT_IF", f),
2573            Expression::SumIf(f) => self.generate_sum_if(f),
2574            Expression::Stddev(f) => self.generate_agg_func("STDDEV", f),
2575            Expression::StddevPop(f) => self.generate_agg_func("STDDEV_POP", f),
2576            Expression::StddevSamp(f) => self.generate_stddev_samp(f),
2577            Expression::Variance(f) => self.generate_agg_func("VARIANCE", f),
2578            Expression::VarPop(f) => {
2579                let name = if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
2580                    "VARIANCE_POP"
2581                } else {
2582                    "VAR_POP"
2583                };
2584                self.generate_agg_func(name, f)
2585            }
2586            Expression::VarSamp(f) => self.generate_agg_func("VAR_SAMP", f),
2587            Expression::Skewness(f) => {
2588                let name = match self.config.dialect {
2589                    Some(DialectType::Snowflake) => "SKEW",
2590                    _ => "SKEWNESS",
2591                };
2592                self.generate_agg_func(name, f)
2593            }
2594            Expression::Median(f) => self.generate_agg_func("MEDIAN", f),
2595            Expression::Mode(f) => self.generate_agg_func("MODE", f),
2596            Expression::First(f) => self.generate_agg_func_with_ignore_nulls_bool("FIRST", f),
2597            Expression::Last(f) => self.generate_agg_func_with_ignore_nulls_bool("LAST", f),
2598            Expression::AnyValue(f) => self.generate_agg_func("ANY_VALUE", f),
2599            Expression::ApproxDistinct(f) => {
2600                match self.config.dialect {
2601                    Some(DialectType::Hive)
2602                    | Some(DialectType::Spark)
2603                    | Some(DialectType::Databricks)
2604                    | Some(DialectType::BigQuery) => {
2605                        // These dialects use APPROX_COUNT_DISTINCT (single arg only)
2606                        self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2607                    }
2608                    Some(DialectType::Redshift) => {
2609                        // Redshift uses APPROXIMATE COUNT(DISTINCT expr)
2610                        self.write_keyword("APPROXIMATE COUNT");
2611                        self.write("(");
2612                        self.write_keyword("DISTINCT");
2613                        self.write(" ");
2614                        self.generate_expression(&f.this)?;
2615                        self.write(")");
2616                        Ok(())
2617                    }
2618                    _ => self.generate_agg_func("APPROX_DISTINCT", f),
2619                }
2620            }
2621            Expression::ApproxCountDistinct(f) => {
2622                self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2623            }
2624            Expression::ApproxPercentile(f) => self.generate_approx_percentile(f),
2625            Expression::Percentile(f) => self.generate_percentile("PERCENTILE", f),
2626            Expression::LogicalAnd(f) => {
2627                let name = match self.config.dialect {
2628                    Some(DialectType::Snowflake) => "BOOLAND_AGG",
2629                    Some(DialectType::Spark)
2630                    | Some(DialectType::Databricks)
2631                    | Some(DialectType::PostgreSQL)
2632                    | Some(DialectType::DuckDB)
2633                    | Some(DialectType::Redshift) => "BOOL_AND",
2634                    Some(DialectType::Oracle)
2635                    | Some(DialectType::SQLite)
2636                    | Some(DialectType::MySQL) => "MIN",
2637                    _ => "BOOL_AND",
2638                };
2639                self.generate_agg_func(name, f)
2640            }
2641            Expression::LogicalOr(f) => {
2642                let name = match self.config.dialect {
2643                    Some(DialectType::Snowflake) => "BOOLOR_AGG",
2644                    Some(DialectType::Spark)
2645                    | Some(DialectType::Databricks)
2646                    | Some(DialectType::PostgreSQL)
2647                    | Some(DialectType::DuckDB)
2648                    | Some(DialectType::Redshift) => "BOOL_OR",
2649                    Some(DialectType::Oracle)
2650                    | Some(DialectType::SQLite)
2651                    | Some(DialectType::MySQL) => "MAX",
2652                    _ => "BOOL_OR",
2653                };
2654                self.generate_agg_func(name, f)
2655            }
2656
2657            // Typed window functions
2658            Expression::RowNumber(_) => {
2659                if self.config.dialect == Some(DialectType::ClickHouse) {
2660                    self.write("row_number");
2661                } else {
2662                    self.write_keyword("ROW_NUMBER");
2663                }
2664                self.write("()");
2665                Ok(())
2666            }
2667            Expression::Rank(r) => {
2668                self.write_keyword("RANK");
2669                self.write("(");
2670                // Oracle hypothetical rank args: RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2671                if !r.args.is_empty() {
2672                    for (i, arg) in r.args.iter().enumerate() {
2673                        if i > 0 {
2674                            self.write(", ");
2675                        }
2676                        self.generate_expression(arg)?;
2677                    }
2678                } else if let Some(order_by) = &r.order_by {
2679                    // DuckDB: RANK(ORDER BY col)
2680                    self.write_keyword(" ORDER BY ");
2681                    for (i, ob) in order_by.iter().enumerate() {
2682                        if i > 0 {
2683                            self.write(", ");
2684                        }
2685                        self.generate_ordered(ob)?;
2686                    }
2687                }
2688                self.write(")");
2689                Ok(())
2690            }
2691            Expression::DenseRank(dr) => {
2692                self.write_keyword("DENSE_RANK");
2693                self.write("(");
2694                // Oracle hypothetical rank args: DENSE_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2695                for (i, arg) in dr.args.iter().enumerate() {
2696                    if i > 0 {
2697                        self.write(", ");
2698                    }
2699                    self.generate_expression(arg)?;
2700                }
2701                self.write(")");
2702                Ok(())
2703            }
2704            Expression::NTile(f) => self.generate_ntile(f),
2705            Expression::Lead(f) => self.generate_lead_lag("LEAD", f),
2706            Expression::Lag(f) => self.generate_lead_lag("LAG", f),
2707            Expression::FirstValue(f) => {
2708                self.generate_value_func_with_ignore_nulls_bool("FIRST_VALUE", f)
2709            }
2710            Expression::LastValue(f) => {
2711                self.generate_value_func_with_ignore_nulls_bool("LAST_VALUE", f)
2712            }
2713            Expression::NthValue(f) => self.generate_nth_value(f),
2714            Expression::PercentRank(pr) => {
2715                self.write_keyword("PERCENT_RANK");
2716                self.write("(");
2717                // Oracle hypothetical rank args: PERCENT_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2718                if !pr.args.is_empty() {
2719                    for (i, arg) in pr.args.iter().enumerate() {
2720                        if i > 0 {
2721                            self.write(", ");
2722                        }
2723                        self.generate_expression(arg)?;
2724                    }
2725                } else if let Some(order_by) = &pr.order_by {
2726                    // DuckDB: PERCENT_RANK(ORDER BY col)
2727                    self.write_keyword(" ORDER BY ");
2728                    for (i, ob) in order_by.iter().enumerate() {
2729                        if i > 0 {
2730                            self.write(", ");
2731                        }
2732                        self.generate_ordered(ob)?;
2733                    }
2734                }
2735                self.write(")");
2736                Ok(())
2737            }
2738            Expression::CumeDist(cd) => {
2739                self.write_keyword("CUME_DIST");
2740                self.write("(");
2741                // Oracle hypothetical rank args: CUME_DIST(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2742                if !cd.args.is_empty() {
2743                    for (i, arg) in cd.args.iter().enumerate() {
2744                        if i > 0 {
2745                            self.write(", ");
2746                        }
2747                        self.generate_expression(arg)?;
2748                    }
2749                } else if let Some(order_by) = &cd.order_by {
2750                    // DuckDB: CUME_DIST(ORDER BY col)
2751                    self.write_keyword(" ORDER BY ");
2752                    for (i, ob) in order_by.iter().enumerate() {
2753                        if i > 0 {
2754                            self.write(", ");
2755                        }
2756                        self.generate_ordered(ob)?;
2757                    }
2758                }
2759                self.write(")");
2760                Ok(())
2761            }
2762            Expression::PercentileCont(f) => self.generate_percentile("PERCENTILE_CONT", f),
2763            Expression::PercentileDisc(f) => self.generate_percentile("PERCENTILE_DISC", f),
2764
2765            // Additional string functions
2766            Expression::Contains(f) => {
2767                self.generate_binary_func("CONTAINS", &f.this, &f.expression)
2768            }
2769            Expression::StartsWith(f) => {
2770                let name = match self.config.dialect {
2771                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "STARTSWITH",
2772                    _ => "STARTS_WITH",
2773                };
2774                self.generate_binary_func(name, &f.this, &f.expression)
2775            }
2776            Expression::EndsWith(f) => {
2777                let name = match self.config.dialect {
2778                    Some(DialectType::Snowflake) => "ENDSWITH",
2779                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "ENDSWITH",
2780                    Some(DialectType::ClickHouse) => "endsWith",
2781                    _ => "ENDS_WITH",
2782                };
2783                self.generate_binary_func(name, &f.this, &f.expression)
2784            }
2785            Expression::Position(f) => self.generate_position(f),
2786            Expression::Initcap(f) => match self.config.dialect {
2787                Some(DialectType::Presto)
2788                | Some(DialectType::Trino)
2789                | Some(DialectType::Athena) => {
2790                    self.write_keyword("REGEXP_REPLACE");
2791                    self.write("(");
2792                    self.generate_expression(&f.this)?;
2793                    self.write(", '(\\w)(\\w*)', x -> UPPER(x[1]) || LOWER(x[2]))");
2794                    Ok(())
2795                }
2796                _ => self.generate_simple_func("INITCAP", &f.this),
2797            },
2798            Expression::Ascii(f) => self.generate_simple_func("ASCII", &f.this),
2799            Expression::Chr(f) => self.generate_simple_func("CHR", &f.this),
2800            Expression::CharFunc(f) => self.generate_char_func(f),
2801            Expression::Soundex(f) => self.generate_simple_func("SOUNDEX", &f.this),
2802            Expression::Levenshtein(f) => {
2803                self.generate_binary_func("LEVENSHTEIN", &f.this, &f.expression)
2804            }
2805
2806            // Additional math functions
2807            Expression::ModFunc(f) => self.generate_mod_func(f),
2808            Expression::Random(_) => {
2809                self.write_keyword("RANDOM");
2810                self.write("()");
2811                Ok(())
2812            }
2813            Expression::Rand(f) => self.generate_rand(f),
2814            Expression::TruncFunc(f) => self.generate_truncate_func(f),
2815            Expression::Pi(_) => {
2816                self.write_keyword("PI");
2817                self.write("()");
2818                Ok(())
2819            }
2820            Expression::Radians(f) => self.generate_simple_func("RADIANS", &f.this),
2821            Expression::Degrees(f) => self.generate_simple_func("DEGREES", &f.this),
2822            Expression::Sin(f) => self.generate_simple_func("SIN", &f.this),
2823            Expression::Cos(f) => self.generate_simple_func("COS", &f.this),
2824            Expression::Tan(f) => self.generate_simple_func("TAN", &f.this),
2825            Expression::Asin(f) => self.generate_simple_func("ASIN", &f.this),
2826            Expression::Acos(f) => self.generate_simple_func("ACOS", &f.this),
2827            Expression::Atan(f) => self.generate_simple_func("ATAN", &f.this),
2828            Expression::Atan2(f) => {
2829                let name = f.original_name.as_deref().unwrap_or("ATAN2");
2830                self.generate_binary_func(name, &f.this, &f.expression)
2831            }
2832
2833            // Control flow
2834            Expression::Decode(f) => self.generate_decode(f),
2835
2836            // Additional date/time functions
2837            Expression::DateFormat(f) => self.generate_date_format("DATE_FORMAT", f),
2838            Expression::FormatDate(f) => self.generate_date_format("FORMAT_DATE", f),
2839            Expression::Year(f) => self.generate_simple_func("YEAR", &f.this),
2840            Expression::Month(f) => self.generate_simple_func("MONTH", &f.this),
2841            Expression::Day(f) => self.generate_simple_func("DAY", &f.this),
2842            Expression::Hour(f) => self.generate_simple_func("HOUR", &f.this),
2843            Expression::Minute(f) => self.generate_simple_func("MINUTE", &f.this),
2844            Expression::Second(f) => self.generate_simple_func("SECOND", &f.this),
2845            Expression::DayOfWeek(f) => {
2846                let name = match self.config.dialect {
2847                    Some(DialectType::Presto)
2848                    | Some(DialectType::Trino)
2849                    | Some(DialectType::Athena) => "DAY_OF_WEEK",
2850                    Some(DialectType::DuckDB) => "ISODOW",
2851                    _ => "DAYOFWEEK",
2852                };
2853                self.generate_simple_func(name, &f.this)
2854            }
2855            Expression::DayOfMonth(f) => {
2856                let name = match self.config.dialect {
2857                    Some(DialectType::Presto)
2858                    | Some(DialectType::Trino)
2859                    | Some(DialectType::Athena) => "DAY_OF_MONTH",
2860                    _ => "DAYOFMONTH",
2861                };
2862                self.generate_simple_func(name, &f.this)
2863            }
2864            Expression::DayOfYear(f) => {
2865                let name = match self.config.dialect {
2866                    Some(DialectType::Presto)
2867                    | Some(DialectType::Trino)
2868                    | Some(DialectType::Athena) => "DAY_OF_YEAR",
2869                    _ => "DAYOFYEAR",
2870                };
2871                self.generate_simple_func(name, &f.this)
2872            }
2873            Expression::WeekOfYear(f) => {
2874                // Python sqlglot default is WEEK_OF_YEAR; Hive/DuckDB/Spark/MySQL override to WEEKOFYEAR
2875                let name = match self.config.dialect {
2876                    Some(DialectType::Hive)
2877                    | Some(DialectType::DuckDB)
2878                    | Some(DialectType::Spark)
2879                    | Some(DialectType::Databricks)
2880                    | Some(DialectType::MySQL) => "WEEKOFYEAR",
2881                    _ => "WEEK_OF_YEAR",
2882                };
2883                self.generate_simple_func(name, &f.this)
2884            }
2885            Expression::Quarter(f) => self.generate_simple_func("QUARTER", &f.this),
2886            Expression::AddMonths(f) => {
2887                self.generate_binary_func("ADD_MONTHS", &f.this, &f.expression)
2888            }
2889            Expression::MonthsBetween(f) => {
2890                self.generate_binary_func("MONTHS_BETWEEN", &f.this, &f.expression)
2891            }
2892            Expression::LastDay(f) => self.generate_last_day(f),
2893            Expression::NextDay(f) => self.generate_binary_func("NEXT_DAY", &f.this, &f.expression),
2894            Expression::Epoch(f) => self.generate_simple_func("EPOCH", &f.this),
2895            Expression::EpochMs(f) => self.generate_simple_func("EPOCH_MS", &f.this),
2896            Expression::FromUnixtime(f) => self.generate_from_unixtime(f),
2897            Expression::UnixTimestamp(f) => self.generate_unix_timestamp(f),
2898            Expression::MakeDate(f) => self.generate_make_date(f),
2899            Expression::MakeTimestamp(f) => self.generate_make_timestamp(f),
2900            Expression::TimestampTrunc(f) => self.generate_date_trunc(f),
2901
2902            // Array functions
2903            Expression::ArrayFunc(f) => self.generate_array_constructor(f),
2904            Expression::ArrayLength(f) => self.generate_simple_func("ARRAY_LENGTH", &f.this),
2905            Expression::ArraySize(f) => self.generate_simple_func("ARRAY_SIZE", &f.this),
2906            Expression::Cardinality(f) => self.generate_simple_func("CARDINALITY", &f.this),
2907            Expression::ArrayContains(f) => {
2908                self.generate_binary_func("ARRAY_CONTAINS", &f.this, &f.expression)
2909            }
2910            Expression::ArrayPosition(f) => {
2911                self.generate_binary_func("ARRAY_POSITION", &f.this, &f.expression)
2912            }
2913            Expression::ArrayAppend(f) => {
2914                self.generate_binary_func("ARRAY_APPEND", &f.this, &f.expression)
2915            }
2916            Expression::ArrayPrepend(f) => {
2917                self.generate_binary_func("ARRAY_PREPEND", &f.this, &f.expression)
2918            }
2919            Expression::ArrayConcat(f) => self.generate_vararg_func("ARRAY_CONCAT", &f.expressions),
2920            Expression::ArraySort(f) => self.generate_array_sort(f),
2921            Expression::ArrayReverse(f) => self.generate_simple_func("ARRAY_REVERSE", &f.this),
2922            Expression::ArrayDistinct(f) => self.generate_simple_func("ARRAY_DISTINCT", &f.this),
2923            Expression::ArrayJoin(f) => self.generate_array_join("ARRAY_JOIN", f),
2924            Expression::ArrayToString(f) => self.generate_array_join("ARRAY_TO_STRING", f),
2925            Expression::Unnest(f) => self.generate_unnest(f),
2926            Expression::Explode(f) => self.generate_simple_func("EXPLODE", &f.this),
2927            Expression::ExplodeOuter(f) => self.generate_simple_func("EXPLODE_OUTER", &f.this),
2928            Expression::ArrayFilter(f) => self.generate_array_filter(f),
2929            Expression::ArrayTransform(f) => self.generate_array_transform(f),
2930            Expression::ArrayFlatten(f) => self.generate_simple_func("FLATTEN", &f.this),
2931            Expression::ArrayCompact(f) => {
2932                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
2933                    // DuckDB: ARRAY_COMPACT(arr) -> LIST_FILTER(arr, _u -> NOT _u IS NULL)
2934                    self.write("LIST_FILTER(");
2935                    self.generate_expression(&f.this)?;
2936                    self.write(", _u -> NOT _u IS NULL)");
2937                    Ok(())
2938                } else {
2939                    self.generate_simple_func("ARRAY_COMPACT", &f.this)
2940                }
2941            }
2942            Expression::ArrayIntersect(f) => {
2943                let func_name = f.original_name.as_deref().unwrap_or("ARRAY_INTERSECT");
2944                self.generate_vararg_func(func_name, &f.expressions)
2945            }
2946            Expression::ArrayUnion(f) => {
2947                self.generate_binary_func("ARRAY_UNION", &f.this, &f.expression)
2948            }
2949            Expression::ArrayExcept(f) => {
2950                self.generate_binary_func("ARRAY_EXCEPT", &f.this, &f.expression)
2951            }
2952            Expression::ArrayRemove(f) => {
2953                self.generate_binary_func("ARRAY_REMOVE", &f.this, &f.expression)
2954            }
2955            Expression::ArrayZip(f) => self.generate_vararg_func("ARRAYS_ZIP", &f.expressions),
2956            Expression::Sequence(f) => self.generate_sequence("SEQUENCE", f),
2957            Expression::Generate(f) => self.generate_sequence("GENERATE_SERIES", f),
2958
2959            // Struct functions
2960            Expression::StructFunc(f) => self.generate_struct_constructor(f),
2961            Expression::StructExtract(f) => self.generate_struct_extract(f),
2962            Expression::NamedStruct(f) => self.generate_named_struct(f),
2963
2964            // Map functions
2965            Expression::MapFunc(f) => self.generate_map_constructor(f),
2966            Expression::MapFromEntries(f) => self.generate_simple_func("MAP_FROM_ENTRIES", &f.this),
2967            Expression::MapFromArrays(f) => {
2968                self.generate_binary_func("MAP_FROM_ARRAYS", &f.this, &f.expression)
2969            }
2970            Expression::MapKeys(f) => self.generate_simple_func("MAP_KEYS", &f.this),
2971            Expression::MapValues(f) => self.generate_simple_func("MAP_VALUES", &f.this),
2972            Expression::MapContainsKey(f) => {
2973                self.generate_binary_func("MAP_CONTAINS_KEY", &f.this, &f.expression)
2974            }
2975            Expression::MapConcat(f) => self.generate_vararg_func("MAP_CONCAT", &f.expressions),
2976            Expression::ElementAt(f) => {
2977                self.generate_binary_func("ELEMENT_AT", &f.this, &f.expression)
2978            }
2979            Expression::TransformKeys(f) => self.generate_transform_func("TRANSFORM_KEYS", f),
2980            Expression::TransformValues(f) => self.generate_transform_func("TRANSFORM_VALUES", f),
2981
2982            // JSON functions
2983            Expression::JsonExtract(f) => self.generate_json_extract("JSON_EXTRACT", f),
2984            Expression::JsonExtractScalar(f) => {
2985                self.generate_json_extract("JSON_EXTRACT_SCALAR", f)
2986            }
2987            Expression::JsonExtractPath(f) => self.generate_json_path("JSON_EXTRACT_PATH", f),
2988            Expression::JsonArray(f) => self.generate_vararg_func("JSON_ARRAY", &f.expressions),
2989            Expression::JsonObject(f) => self.generate_json_object(f),
2990            Expression::JsonQuery(f) => self.generate_json_extract("JSON_QUERY", f),
2991            Expression::JsonValue(f) => self.generate_json_extract("JSON_VALUE", f),
2992            Expression::JsonArrayLength(f) => {
2993                self.generate_simple_func("JSON_ARRAY_LENGTH", &f.this)
2994            }
2995            Expression::JsonKeys(f) => self.generate_simple_func("JSON_KEYS", &f.this),
2996            Expression::JsonType(f) => self.generate_simple_func("JSON_TYPE", &f.this),
2997            Expression::ParseJson(f) => {
2998                let name = match self.config.dialect {
2999                    Some(DialectType::Presto)
3000                    | Some(DialectType::Trino)
3001                    | Some(DialectType::Athena) => "JSON_PARSE",
3002                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
3003                        // PostgreSQL: CAST(x AS JSON)
3004                        self.write_keyword("CAST");
3005                        self.write("(");
3006                        self.generate_expression(&f.this)?;
3007                        self.write_keyword(" AS ");
3008                        self.write_keyword("JSON");
3009                        self.write(")");
3010                        return Ok(());
3011                    }
3012                    Some(DialectType::Hive)
3013                    | Some(DialectType::Spark)
3014                    | Some(DialectType::MySQL)
3015                    | Some(DialectType::SingleStore)
3016                    | Some(DialectType::TiDB)
3017                    | Some(DialectType::TSQL) => {
3018                        // Hive/Spark/MySQL/TSQL: just emit the string literal
3019                        self.generate_expression(&f.this)?;
3020                        return Ok(());
3021                    }
3022                    Some(DialectType::DuckDB) => "JSON",
3023                    _ => "PARSE_JSON",
3024                };
3025                self.generate_simple_func(name, &f.this)
3026            }
3027            Expression::ToJson(f) => self.generate_simple_func("TO_JSON", &f.this),
3028            Expression::JsonSet(f) => self.generate_json_modify("JSON_SET", f),
3029            Expression::JsonInsert(f) => self.generate_json_modify("JSON_INSERT", f),
3030            Expression::JsonRemove(f) => self.generate_json_path("JSON_REMOVE", f),
3031            Expression::JsonMergePatch(f) => {
3032                self.generate_binary_func("JSON_MERGE_PATCH", &f.this, &f.expression)
3033            }
3034            Expression::JsonArrayAgg(f) => self.generate_json_array_agg(f),
3035            Expression::JsonObjectAgg(f) => self.generate_json_object_agg(f),
3036
3037            // Type casting/conversion
3038            Expression::Convert(f) => self.generate_convert(f),
3039            Expression::Typeof(f) => self.generate_simple_func("TYPEOF", &f.this),
3040
3041            // Additional expressions
3042            Expression::Lambda(f) => self.generate_lambda(f),
3043            Expression::Parameter(f) => self.generate_parameter(f),
3044            Expression::Placeholder(f) => self.generate_placeholder(f),
3045            Expression::NamedArgument(f) => self.generate_named_argument(f),
3046            Expression::TableArgument(f) => self.generate_table_argument(f),
3047            Expression::SqlComment(f) => self.generate_sql_comment(f),
3048
3049            // Additional predicates
3050            Expression::NullSafeEq(op) => self.generate_null_safe_eq(op),
3051            Expression::NullSafeNeq(op) => self.generate_null_safe_neq(op),
3052            Expression::Glob(op) => self.generate_binary_op(op, "GLOB"),
3053            Expression::SimilarTo(f) => self.generate_similar_to(f),
3054            Expression::Any(f) => self.generate_quantified("ANY", f),
3055            Expression::All(f) => self.generate_quantified("ALL", f),
3056            Expression::Overlaps(f) => self.generate_overlaps(f),
3057
3058            // Bitwise operations
3059            Expression::BitwiseLeftShift(op) => {
3060                if matches!(
3061                    self.config.dialect,
3062                    Some(DialectType::Presto) | Some(DialectType::Trino)
3063                ) {
3064                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_LEFT");
3065                    self.write("(");
3066                    self.generate_expression(&op.left)?;
3067                    self.write(", ");
3068                    self.generate_expression(&op.right)?;
3069                    self.write(")");
3070                    Ok(())
3071                } else if matches!(
3072                    self.config.dialect,
3073                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3074                ) {
3075                    self.write_keyword("SHIFTLEFT");
3076                    self.write("(");
3077                    self.generate_expression(&op.left)?;
3078                    self.write(", ");
3079                    self.generate_expression(&op.right)?;
3080                    self.write(")");
3081                    Ok(())
3082                } else {
3083                    self.generate_binary_op(op, "<<")
3084                }
3085            }
3086            Expression::BitwiseRightShift(op) => {
3087                if matches!(
3088                    self.config.dialect,
3089                    Some(DialectType::Presto) | Some(DialectType::Trino)
3090                ) {
3091                    self.write_keyword("BITWISE_ARITHMETIC_SHIFT_RIGHT");
3092                    self.write("(");
3093                    self.generate_expression(&op.left)?;
3094                    self.write(", ");
3095                    self.generate_expression(&op.right)?;
3096                    self.write(")");
3097                    Ok(())
3098                } else if matches!(
3099                    self.config.dialect,
3100                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3101                ) {
3102                    self.write_keyword("SHIFTRIGHT");
3103                    self.write("(");
3104                    self.generate_expression(&op.left)?;
3105                    self.write(", ");
3106                    self.generate_expression(&op.right)?;
3107                    self.write(")");
3108                    Ok(())
3109                } else {
3110                    self.generate_binary_op(op, ">>")
3111                }
3112            }
3113            Expression::BitwiseAndAgg(f) => self.generate_agg_func("BIT_AND", f),
3114            Expression::BitwiseOrAgg(f) => self.generate_agg_func("BIT_OR", f),
3115            Expression::BitwiseXorAgg(f) => self.generate_agg_func("BIT_XOR", f),
3116
3117            // Array/struct/map access
3118            Expression::Subscript(s) => self.generate_subscript(s),
3119            Expression::Dot(d) => self.generate_dot_access(d),
3120            Expression::MethodCall(m) => self.generate_method_call(m),
3121            Expression::ArraySlice(s) => self.generate_array_slice(s),
3122
3123            Expression::And(op) => self.generate_connector_op(op, ConnectorOperator::And),
3124            Expression::Or(op) => self.generate_connector_op(op, ConnectorOperator::Or),
3125            Expression::Add(op) => self.generate_binary_op(op, "+"),
3126            Expression::Sub(op) => self.generate_binary_op(op, "-"),
3127            Expression::Mul(op) => self.generate_binary_op(op, "*"),
3128            Expression::Div(op) => self.generate_binary_op(op, "/"),
3129            Expression::IntDiv(f) => {
3130                use crate::dialects::DialectType;
3131                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
3132                    // DuckDB uses // operator for integer division
3133                    self.generate_expression(&f.this)?;
3134                    self.write(" // ");
3135                    self.generate_expression(&f.expression)?;
3136                    Ok(())
3137                } else if matches!(
3138                    self.config.dialect,
3139                    Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
3140                ) {
3141                    // Hive/Spark use DIV as an infix operator
3142                    self.generate_expression(&f.this)?;
3143                    self.write(" ");
3144                    self.write_keyword("DIV");
3145                    self.write(" ");
3146                    self.generate_expression(&f.expression)?;
3147                    Ok(())
3148                } else {
3149                    // Other dialects use DIV function
3150                    self.write_keyword("DIV");
3151                    self.write("(");
3152                    self.generate_expression(&f.this)?;
3153                    self.write(", ");
3154                    self.generate_expression(&f.expression)?;
3155                    self.write(")");
3156                    Ok(())
3157                }
3158            }
3159            Expression::Mod(op) => {
3160                if matches!(self.config.dialect, Some(DialectType::Teradata)) {
3161                    self.generate_binary_op(op, "MOD")
3162                } else {
3163                    self.generate_binary_op(op, "%")
3164                }
3165            }
3166            Expression::Eq(op) => self.generate_binary_op(op, "="),
3167            Expression::Neq(op) => self.generate_binary_op(op, "<>"),
3168            Expression::Lt(op) => self.generate_binary_op(op, "<"),
3169            Expression::Lte(op) => self.generate_binary_op(op, "<="),
3170            Expression::Gt(op) => self.generate_binary_op(op, ">"),
3171            Expression::Gte(op) => self.generate_binary_op(op, ">="),
3172            Expression::Like(op) => self.generate_like_op(op, "LIKE"),
3173            Expression::ILike(op) => self.generate_like_op(op, "ILIKE"),
3174            Expression::Match(op) => self.generate_binary_op(op, "MATCH"),
3175            Expression::Concat(op) => {
3176                // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
3177                if self.config.dialect == Some(DialectType::Solr) {
3178                    self.generate_binary_op(op, "OR")
3179                } else if self.config.dialect == Some(DialectType::MySQL) {
3180                    self.generate_mysql_concat_from_concat(op)
3181                } else {
3182                    self.generate_binary_op(op, "||")
3183                }
3184            }
3185            Expression::BitwiseAnd(op) => {
3186                // Presto/Trino use BITWISE_AND function
3187                if matches!(
3188                    self.config.dialect,
3189                    Some(DialectType::Presto) | Some(DialectType::Trino)
3190                ) {
3191                    self.write_keyword("BITWISE_AND");
3192                    self.write("(");
3193                    self.generate_expression(&op.left)?;
3194                    self.write(", ");
3195                    self.generate_expression(&op.right)?;
3196                    self.write(")");
3197                    Ok(())
3198                } else {
3199                    self.generate_binary_op(op, "&")
3200                }
3201            }
3202            Expression::BitwiseOr(op) => {
3203                // Presto/Trino use BITWISE_OR function
3204                if matches!(
3205                    self.config.dialect,
3206                    Some(DialectType::Presto) | Some(DialectType::Trino)
3207                ) {
3208                    self.write_keyword("BITWISE_OR");
3209                    self.write("(");
3210                    self.generate_expression(&op.left)?;
3211                    self.write(", ");
3212                    self.generate_expression(&op.right)?;
3213                    self.write(")");
3214                    Ok(())
3215                } else {
3216                    self.generate_binary_op(op, "|")
3217                }
3218            }
3219            Expression::BitwiseXor(op) => {
3220                // Presto/Trino use BITWISE_XOR function, PostgreSQL uses #, others use ^
3221                if matches!(
3222                    self.config.dialect,
3223                    Some(DialectType::Presto) | Some(DialectType::Trino)
3224                ) {
3225                    self.write_keyword("BITWISE_XOR");
3226                    self.write("(");
3227                    self.generate_expression(&op.left)?;
3228                    self.write(", ");
3229                    self.generate_expression(&op.right)?;
3230                    self.write(")");
3231                    Ok(())
3232                } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
3233                    self.generate_binary_op(op, "#")
3234                } else {
3235                    self.generate_binary_op(op, "^")
3236                }
3237            }
3238            Expression::Adjacent(op) => self.generate_binary_op(op, "-|-"),
3239            Expression::TsMatch(op) => self.generate_binary_op(op, "@@"),
3240            Expression::PropertyEQ(op) => self.generate_binary_op(op, ":="),
3241            Expression::ArrayContainsAll(op) => self.generate_binary_op(op, "@>"),
3242            Expression::ArrayContainedBy(op) => self.generate_binary_op(op, "<@"),
3243            Expression::ArrayOverlaps(op) => self.generate_binary_op(op, "&&"),
3244            Expression::JSONBContainsAllTopKeys(op) => self.generate_binary_op(op, "?&"),
3245            Expression::JSONBContainsAnyTopKeys(op) => self.generate_binary_op(op, "?|"),
3246            Expression::JSONBContains(f) => {
3247                // PostgreSQL JSONB contains key operator: a ? b
3248                self.generate_expression(&f.this)?;
3249                self.write_space();
3250                self.write("?");
3251                self.write_space();
3252                self.generate_expression(&f.expression)
3253            }
3254            Expression::JSONBDeleteAtPath(op) => self.generate_binary_op(op, "#-"),
3255            Expression::ExtendsLeft(op) => self.generate_binary_op(op, "&<"),
3256            Expression::ExtendsRight(op) => self.generate_binary_op(op, "&>"),
3257            Expression::Not(op) => self.generate_unary_op(op, "NOT"),
3258            Expression::Neg(op) => self.generate_unary_op(op, "-"),
3259            Expression::BitwiseNot(op) => {
3260                // Presto/Trino use BITWISE_NOT function
3261                if matches!(
3262                    self.config.dialect,
3263                    Some(DialectType::Presto) | Some(DialectType::Trino)
3264                ) {
3265                    self.write_keyword("BITWISE_NOT");
3266                    self.write("(");
3267                    self.generate_expression(&op.this)?;
3268                    self.write(")");
3269                    Ok(())
3270                } else {
3271                    self.generate_unary_op(op, "~")
3272                }
3273            }
3274            Expression::In(in_expr) => self.generate_in(in_expr),
3275            Expression::Between(between) => self.generate_between(between),
3276            Expression::IsNull(is_null) => self.generate_is_null(is_null),
3277            Expression::IsTrue(is_true) => self.generate_is_true(is_true),
3278            Expression::IsFalse(is_false) => self.generate_is_false(is_false),
3279            Expression::IsJson(is_json) => self.generate_is_json(is_json),
3280            Expression::Is(is_expr) => self.generate_is(is_expr),
3281            Expression::Exists(exists) => self.generate_exists(exists),
3282            Expression::MemberOf(member_of) => self.generate_member_of(member_of),
3283            Expression::Subquery(subquery) => self.generate_subquery(subquery),
3284            Expression::Paren(paren) => {
3285                // JoinedTable already outputs its own parentheses, so don't double-wrap
3286                let skip_parens = matches!(&paren.this, Expression::JoinedTable(_));
3287
3288                if !skip_parens {
3289                    self.write("(");
3290                    if self.config.pretty {
3291                        self.write_newline();
3292                        self.indent_level += 1;
3293                        self.write_indent();
3294                    }
3295                }
3296                self.generate_expression(&paren.this)?;
3297                if !skip_parens {
3298                    if self.config.pretty {
3299                        self.write_newline();
3300                        self.indent_level -= 1;
3301                        self.write_indent();
3302                    }
3303                    self.write(")");
3304                }
3305                // Output trailing comments after closing paren
3306                for comment in &paren.trailing_comments {
3307                    self.write(" ");
3308                    self.write_formatted_comment(comment);
3309                }
3310                Ok(())
3311            }
3312            Expression::Array(arr) => self.generate_array(arr),
3313            Expression::Tuple(tuple) => self.generate_tuple(tuple),
3314            Expression::PipeOperator(pipe) => self.generate_pipe_operator(pipe),
3315            Expression::Ordered(ordered) => self.generate_ordered(ordered),
3316            Expression::DataType(dt) => self.generate_data_type(dt),
3317            Expression::Raw(raw) => {
3318                self.write(&raw.sql);
3319                Ok(())
3320            }
3321            Expression::CreateTask(task) => self.generate_create_task(task),
3322            Expression::Command(cmd) => {
3323                self.write(&cmd.this);
3324                Ok(())
3325            }
3326            Expression::Kill(kill) => {
3327                self.write_keyword("KILL");
3328                if let Some(kind) = &kill.kind {
3329                    self.write_space();
3330                    self.write_keyword(kind);
3331                }
3332                self.write_space();
3333                self.generate_expression(&kill.this)?;
3334                Ok(())
3335            }
3336            Expression::Execute(exec) => {
3337                self.write_keyword("EXECUTE");
3338                self.write_space();
3339                self.generate_expression(&exec.this)?;
3340                for (i, param) in exec.parameters.iter().enumerate() {
3341                    if i == 0 {
3342                        self.write_space();
3343                    } else {
3344                        self.write(", ");
3345                    }
3346                    self.write(&param.name);
3347                    // Only write = value for named parameters (not positional)
3348                    if !param.positional {
3349                        self.write(" = ");
3350                        self.generate_expression(&param.value)?;
3351                    }
3352                    if param.output {
3353                        self.write_space();
3354                        self.write_keyword("OUTPUT");
3355                    }
3356                }
3357                if let Some(ref suffix) = exec.suffix {
3358                    self.write_space();
3359                    self.write(suffix);
3360                }
3361                Ok(())
3362            }
3363            Expression::Annotated(annotated) => {
3364                self.generate_expression(&annotated.this)?;
3365                for comment in &annotated.trailing_comments {
3366                    self.write(" ");
3367                    self.write_formatted_comment(comment);
3368                }
3369                Ok(())
3370            }
3371
3372            // DDL statements
3373            Expression::CreateTable(ct) => self.generate_create_table(ct),
3374            Expression::DropTable(dt) => self.generate_drop_table(dt),
3375            Expression::Undrop(u) => self.generate_undrop(u),
3376            Expression::AlterTable(at) => self.generate_alter_table(at),
3377            Expression::CreateIndex(ci) => self.generate_create_index(ci),
3378            Expression::DropIndex(di) => self.generate_drop_index(di),
3379            Expression::CreateView(cv) => self.generate_create_view(cv),
3380            Expression::DropView(dv) => self.generate_drop_view(dv),
3381            Expression::AlterView(av) => self.generate_alter_view(av),
3382            Expression::AlterIndex(ai) => self.generate_alter_index(ai),
3383            Expression::Truncate(tr) => self.generate_truncate(tr),
3384            Expression::Use(u) => self.generate_use(u),
3385            // Phase 4: Additional DDL statements
3386            Expression::CreateSchema(cs) => self.generate_create_schema(cs),
3387            Expression::DropSchema(ds) => self.generate_drop_schema(ds),
3388            Expression::DropNamespace(dn) => self.generate_drop_namespace(dn),
3389            Expression::CreateDatabase(cd) => self.generate_create_database(cd),
3390            Expression::DropDatabase(dd) => self.generate_drop_database(dd),
3391            Expression::CreateFunction(cf) => self.generate_create_function(cf),
3392            Expression::DropFunction(df) => self.generate_drop_function(df),
3393            Expression::CreateProcedure(cp) => self.generate_create_procedure(cp),
3394            Expression::DropProcedure(dp) => self.generate_drop_procedure(dp),
3395            Expression::CreateSequence(cs) => self.generate_create_sequence(cs),
3396            Expression::CreateSynonym(cs) => {
3397                self.write_keyword("CREATE SYNONYM");
3398                self.write_space();
3399                self.generate_table(&cs.name)?;
3400                self.write_space();
3401                self.write_keyword("FOR");
3402                self.write_space();
3403                self.generate_table(&cs.target)?;
3404                Ok(())
3405            }
3406            Expression::DropSequence(ds) => self.generate_drop_sequence(ds),
3407            Expression::AlterSequence(als) => self.generate_alter_sequence(als),
3408            Expression::CreateTrigger(ct) => self.generate_create_trigger(ct),
3409            Expression::DropTrigger(dt) => self.generate_drop_trigger(dt),
3410            Expression::CreateType(ct) => self.generate_create_type(ct),
3411            Expression::DropType(dt) => self.generate_drop_type(dt),
3412            Expression::Describe(d) => self.generate_describe(d),
3413            Expression::Show(s) => self.generate_show(s),
3414
3415            // CACHE/UNCACHE/LOAD TABLE (Spark/Hive)
3416            Expression::Cache(c) => self.generate_cache(c),
3417            Expression::Uncache(u) => self.generate_uncache(u),
3418            Expression::LoadData(l) => self.generate_load_data(l),
3419            Expression::Pragma(p) => self.generate_pragma(p),
3420            Expression::Grant(g) => self.generate_grant(g),
3421            Expression::Revoke(r) => self.generate_revoke(r),
3422            Expression::Comment(c) => self.generate_comment(c),
3423            Expression::SetStatement(s) => self.generate_set_statement(s),
3424
3425            // PIVOT/UNPIVOT
3426            Expression::Pivot(pivot) => self.generate_pivot(pivot),
3427            Expression::Unpivot(unpivot) => self.generate_unpivot(unpivot),
3428
3429            // VALUES table constructor
3430            Expression::Values(values) => self.generate_values(values),
3431
3432            // === BATCH-GENERATED MATCH ARMS (481 variants) ===
3433            Expression::AIAgg(e) => self.generate_ai_agg(e),
3434            Expression::AIClassify(e) => self.generate_ai_classify(e),
3435            Expression::AddPartition(e) => self.generate_add_partition(e),
3436            Expression::AlgorithmProperty(e) => self.generate_algorithm_property(e),
3437            Expression::Aliases(e) => self.generate_aliases(e),
3438            Expression::AllowedValuesProperty(e) => self.generate_allowed_values_property(e),
3439            Expression::AlterColumn(e) => self.generate_alter_column(e),
3440            Expression::AlterSession(e) => self.generate_alter_session(e),
3441            Expression::AlterSet(e) => self.generate_alter_set(e),
3442            Expression::AlterSortKey(e) => self.generate_alter_sort_key(e),
3443            Expression::Analyze(e) => self.generate_analyze(e),
3444            Expression::AnalyzeDelete(e) => self.generate_analyze_delete(e),
3445            Expression::AnalyzeHistogram(e) => self.generate_analyze_histogram(e),
3446            Expression::AnalyzeListChainedRows(e) => self.generate_analyze_list_chained_rows(e),
3447            Expression::AnalyzeSample(e) => self.generate_analyze_sample(e),
3448            Expression::AnalyzeStatistics(e) => self.generate_analyze_statistics(e),
3449            Expression::AnalyzeValidate(e) => self.generate_analyze_validate(e),
3450            Expression::AnalyzeWith(e) => self.generate_analyze_with(e),
3451            Expression::Anonymous(e) => self.generate_anonymous(e),
3452            Expression::AnonymousAggFunc(e) => self.generate_anonymous_agg_func(e),
3453            Expression::Apply(e) => self.generate_apply(e),
3454            Expression::ApproxPercentileEstimate(e) => self.generate_approx_percentile_estimate(e),
3455            Expression::ApproxQuantile(e) => self.generate_approx_quantile(e),
3456            Expression::ApproxQuantiles(e) => self.generate_approx_quantiles(e),
3457            Expression::ApproxTopK(e) => self.generate_approx_top_k(e),
3458            Expression::ApproxTopKAccumulate(e) => self.generate_approx_top_k_accumulate(e),
3459            Expression::ApproxTopKCombine(e) => self.generate_approx_top_k_combine(e),
3460            Expression::ApproxTopKEstimate(e) => self.generate_approx_top_k_estimate(e),
3461            Expression::ApproxTopSum(e) => self.generate_approx_top_sum(e),
3462            Expression::ArgMax(e) => self.generate_arg_max(e),
3463            Expression::ArgMin(e) => self.generate_arg_min(e),
3464            Expression::ArrayAll(e) => self.generate_array_all(e),
3465            Expression::ArrayAny(e) => self.generate_array_any(e),
3466            Expression::ArrayConstructCompact(e) => self.generate_array_construct_compact(e),
3467            Expression::ArraySum(e) => self.generate_array_sum(e),
3468            Expression::AtIndex(e) => self.generate_at_index(e),
3469            Expression::Attach(e) => self.generate_attach(e),
3470            Expression::AttachOption(e) => self.generate_attach_option(e),
3471            Expression::AutoIncrementProperty(e) => self.generate_auto_increment_property(e),
3472            Expression::AutoRefreshProperty(e) => self.generate_auto_refresh_property(e),
3473            Expression::BackupProperty(e) => self.generate_backup_property(e),
3474            Expression::Base64DecodeBinary(e) => self.generate_base64_decode_binary(e),
3475            Expression::Base64DecodeString(e) => self.generate_base64_decode_string(e),
3476            Expression::Base64Encode(e) => self.generate_base64_encode(e),
3477            Expression::BlockCompressionProperty(e) => self.generate_block_compression_property(e),
3478            Expression::Booland(e) => self.generate_booland(e),
3479            Expression::Boolor(e) => self.generate_boolor(e),
3480            Expression::BuildProperty(e) => self.generate_build_property(e),
3481            Expression::ByteString(e) => self.generate_byte_string(e),
3482            Expression::CaseSpecificColumnConstraint(e) => {
3483                self.generate_case_specific_column_constraint(e)
3484            }
3485            Expression::CastToStrType(e) => self.generate_cast_to_str_type(e),
3486            Expression::Changes(e) => self.generate_changes(e),
3487            Expression::CharacterSetColumnConstraint(e) => {
3488                self.generate_character_set_column_constraint(e)
3489            }
3490            Expression::CharacterSetProperty(e) => self.generate_character_set_property(e),
3491            Expression::CheckColumnConstraint(e) => self.generate_check_column_constraint(e),
3492            Expression::AssumeColumnConstraint(e) => self.generate_assume_column_constraint(e),
3493            Expression::CheckJson(e) => self.generate_check_json(e),
3494            Expression::CheckXml(e) => self.generate_check_xml(e),
3495            Expression::ChecksumProperty(e) => self.generate_checksum_property(e),
3496            Expression::Clone(e) => self.generate_clone(e),
3497            Expression::ClusterBy(e) => self.generate_cluster_by(e),
3498            Expression::ClusterByColumnsProperty(e) => self.generate_cluster_by_columns_property(e),
3499            Expression::ClusteredByProperty(e) => self.generate_clustered_by_property(e),
3500            Expression::CollateProperty(e) => self.generate_collate_property(e),
3501            Expression::ColumnConstraint(e) => self.generate_column_constraint(e),
3502            Expression::ColumnDef(e) => self.generate_column_def_expr(e),
3503            Expression::ColumnPosition(e) => self.generate_column_position(e),
3504            Expression::ColumnPrefix(e) => self.generate_column_prefix(e),
3505            Expression::Columns(e) => self.generate_columns(e),
3506            Expression::CombinedAggFunc(e) => self.generate_combined_agg_func(e),
3507            Expression::CombinedParameterizedAgg(e) => self.generate_combined_parameterized_agg(e),
3508            Expression::Commit(e) => self.generate_commit(e),
3509            Expression::Comprehension(e) => self.generate_comprehension(e),
3510            Expression::Compress(e) => self.generate_compress(e),
3511            Expression::CompressColumnConstraint(e) => self.generate_compress_column_constraint(e),
3512            Expression::ComputedColumnConstraint(e) => self.generate_computed_column_constraint(e),
3513            Expression::ConditionalInsert(e) => self.generate_conditional_insert(e),
3514            Expression::Constraint(e) => self.generate_constraint(e),
3515            Expression::ConvertTimezone(e) => self.generate_convert_timezone(e),
3516            Expression::ConvertToCharset(e) => self.generate_convert_to_charset(e),
3517            Expression::Copy(e) => self.generate_copy(e),
3518            Expression::CopyParameter(e) => self.generate_copy_parameter(e),
3519            Expression::Corr(e) => self.generate_corr(e),
3520            Expression::CosineDistance(e) => self.generate_cosine_distance(e),
3521            Expression::CovarPop(e) => self.generate_covar_pop(e),
3522            Expression::CovarSamp(e) => self.generate_covar_samp(e),
3523            Expression::Credentials(e) => self.generate_credentials(e),
3524            Expression::CredentialsProperty(e) => self.generate_credentials_property(e),
3525            Expression::Cte(e) => self.generate_cte(e),
3526            Expression::Cube(e) => self.generate_cube(e),
3527            Expression::CurrentDatetime(e) => self.generate_current_datetime(e),
3528            Expression::CurrentSchema(e) => self.generate_current_schema(e),
3529            Expression::CurrentSchemas(e) => self.generate_current_schemas(e),
3530            Expression::CurrentUser(e) => self.generate_current_user(e),
3531            Expression::DPipe(e) => self.generate_d_pipe(e),
3532            Expression::DataBlocksizeProperty(e) => self.generate_data_blocksize_property(e),
3533            Expression::DataDeletionProperty(e) => self.generate_data_deletion_property(e),
3534            Expression::Date(e) => self.generate_date_func(e),
3535            Expression::DateBin(e) => self.generate_date_bin(e),
3536            Expression::DateFormatColumnConstraint(e) => {
3537                self.generate_date_format_column_constraint(e)
3538            }
3539            Expression::DateFromParts(e) => self.generate_date_from_parts(e),
3540            Expression::Datetime(e) => self.generate_datetime(e),
3541            Expression::DatetimeAdd(e) => self.generate_datetime_add(e),
3542            Expression::DatetimeDiff(e) => self.generate_datetime_diff(e),
3543            Expression::DatetimeSub(e) => self.generate_datetime_sub(e),
3544            Expression::DatetimeTrunc(e) => self.generate_datetime_trunc(e),
3545            Expression::Dayname(e) => self.generate_dayname(e),
3546            Expression::Declare(e) => self.generate_declare(e),
3547            Expression::DeclareItem(e) => self.generate_declare_item(e),
3548            Expression::DecodeCase(e) => self.generate_decode_case(e),
3549            Expression::DecompressBinary(e) => self.generate_decompress_binary(e),
3550            Expression::DecompressString(e) => self.generate_decompress_string(e),
3551            Expression::Decrypt(e) => self.generate_decrypt(e),
3552            Expression::DecryptRaw(e) => self.generate_decrypt_raw(e),
3553            Expression::DefaultColumnConstraint(e) => {
3554                self.write_keyword("DEFAULT");
3555                self.write_space();
3556                self.generate_expression(&e.this)?;
3557                if let Some(ref col) = e.for_column {
3558                    self.write_space();
3559                    self.write_keyword("FOR");
3560                    self.write_space();
3561                    self.generate_identifier(col)?;
3562                }
3563                Ok(())
3564            }
3565            Expression::DefinerProperty(e) => self.generate_definer_property(e),
3566            Expression::Detach(e) => self.generate_detach(e),
3567            Expression::DictProperty(e) => self.generate_dict_property(e),
3568            Expression::DictRange(e) => self.generate_dict_range(e),
3569            Expression::Directory(e) => self.generate_directory(e),
3570            Expression::DistKeyProperty(e) => self.generate_dist_key_property(e),
3571            Expression::DistStyleProperty(e) => self.generate_dist_style_property(e),
3572            Expression::DistributeBy(e) => self.generate_distribute_by(e),
3573            Expression::DistributedByProperty(e) => self.generate_distributed_by_property(e),
3574            Expression::DotProduct(e) => self.generate_dot_product(e),
3575            Expression::DropPartition(e) => self.generate_drop_partition(e),
3576            Expression::DuplicateKeyProperty(e) => self.generate_duplicate_key_property(e),
3577            Expression::Elt(e) => self.generate_elt(e),
3578            Expression::Encode(e) => self.generate_encode(e),
3579            Expression::EncodeProperty(e) => self.generate_encode_property(e),
3580            Expression::Encrypt(e) => self.generate_encrypt(e),
3581            Expression::EncryptRaw(e) => self.generate_encrypt_raw(e),
3582            Expression::EngineProperty(e) => self.generate_engine_property(e),
3583            Expression::EnviromentProperty(e) => self.generate_enviroment_property(e),
3584            Expression::EphemeralColumnConstraint(e) => {
3585                self.generate_ephemeral_column_constraint(e)
3586            }
3587            Expression::EqualNull(e) => self.generate_equal_null(e),
3588            Expression::EuclideanDistance(e) => self.generate_euclidean_distance(e),
3589            Expression::ExecuteAsProperty(e) => self.generate_execute_as_property(e),
3590            Expression::Export(e) => self.generate_export(e),
3591            Expression::ExternalProperty(e) => self.generate_external_property(e),
3592            Expression::FallbackProperty(e) => self.generate_fallback_property(e),
3593            Expression::FarmFingerprint(e) => self.generate_farm_fingerprint(e),
3594            Expression::FeaturesAtTime(e) => self.generate_features_at_time(e),
3595            Expression::Fetch(e) => self.generate_fetch(e),
3596            Expression::FileFormatProperty(e) => self.generate_file_format_property(e),
3597            Expression::Filter(e) => self.generate_filter(e),
3598            Expression::Float64(e) => self.generate_float64(e),
3599            Expression::ForIn(e) => self.generate_for_in(e),
3600            Expression::ForeignKey(e) => self.generate_foreign_key(e),
3601            Expression::Format(e) => self.generate_format(e),
3602            Expression::FormatPhrase(e) => self.generate_format_phrase(e),
3603            Expression::FreespaceProperty(e) => self.generate_freespace_property(e),
3604            Expression::From(e) => self.generate_from(e),
3605            Expression::FromBase(e) => self.generate_from_base(e),
3606            Expression::FromTimeZone(e) => self.generate_from_time_zone(e),
3607            Expression::GapFill(e) => self.generate_gap_fill(e),
3608            Expression::GenerateDateArray(e) => self.generate_generate_date_array(e),
3609            Expression::GenerateEmbedding(e) => self.generate_generate_embedding(e),
3610            Expression::GenerateSeries(e) => self.generate_generate_series(e),
3611            Expression::GenerateTimestampArray(e) => self.generate_generate_timestamp_array(e),
3612            Expression::GeneratedAsIdentityColumnConstraint(e) => {
3613                self.generate_generated_as_identity_column_constraint(e)
3614            }
3615            Expression::GeneratedAsRowColumnConstraint(e) => {
3616                self.generate_generated_as_row_column_constraint(e)
3617            }
3618            Expression::Get(e) => self.generate_get(e),
3619            Expression::GetExtract(e) => self.generate_get_extract(e),
3620            Expression::Getbit(e) => self.generate_getbit(e),
3621            Expression::GrantPrincipal(e) => self.generate_grant_principal(e),
3622            Expression::GrantPrivilege(e) => self.generate_grant_privilege(e),
3623            Expression::Group(e) => self.generate_group(e),
3624            Expression::GroupBy(e) => self.generate_group_by(e),
3625            Expression::Grouping(e) => self.generate_grouping(e),
3626            Expression::GroupingId(e) => self.generate_grouping_id(e),
3627            Expression::GroupingSets(e) => self.generate_grouping_sets(e),
3628            Expression::HashAgg(e) => self.generate_hash_agg(e),
3629            Expression::Having(e) => self.generate_having(e),
3630            Expression::HavingMax(e) => self.generate_having_max(e),
3631            Expression::Heredoc(e) => self.generate_heredoc(e),
3632            Expression::HexEncode(e) => self.generate_hex_encode(e),
3633            Expression::Hll(e) => self.generate_hll(e),
3634            Expression::InOutColumnConstraint(e) => self.generate_in_out_column_constraint(e),
3635            Expression::IncludeProperty(e) => self.generate_include_property(e),
3636            Expression::Index(e) => self.generate_index(e),
3637            Expression::IndexColumnConstraint(e) => self.generate_index_column_constraint(e),
3638            Expression::IndexConstraintOption(e) => self.generate_index_constraint_option(e),
3639            Expression::IndexParameters(e) => self.generate_index_parameters(e),
3640            Expression::IndexTableHint(e) => self.generate_index_table_hint(e),
3641            Expression::InheritsProperty(e) => self.generate_inherits_property(e),
3642            Expression::InputModelProperty(e) => self.generate_input_model_property(e),
3643            Expression::InputOutputFormat(e) => self.generate_input_output_format(e),
3644            Expression::Install(e) => self.generate_install(e),
3645            Expression::IntervalOp(e) => self.generate_interval_op(e),
3646            Expression::IntervalSpan(e) => self.generate_interval_span(e),
3647            Expression::IntoClause(e) => self.generate_into_clause(e),
3648            Expression::Introducer(e) => self.generate_introducer(e),
3649            Expression::IsolatedLoadingProperty(e) => self.generate_isolated_loading_property(e),
3650            Expression::JSON(e) => self.generate_json(e),
3651            Expression::JSONArray(e) => self.generate_json_array(e),
3652            Expression::JSONArrayAgg(e) => self.generate_json_array_agg_struct(e),
3653            Expression::JSONArrayAppend(e) => self.generate_json_array_append(e),
3654            Expression::JSONArrayContains(e) => self.generate_json_array_contains(e),
3655            Expression::JSONArrayInsert(e) => self.generate_json_array_insert(e),
3656            Expression::JSONBExists(e) => self.generate_jsonb_exists(e),
3657            Expression::JSONBExtractScalar(e) => self.generate_jsonb_extract_scalar(e),
3658            Expression::JSONBObjectAgg(e) => self.generate_jsonb_object_agg(e),
3659            Expression::JSONObjectAgg(e) => self.generate_json_object_agg_struct(e),
3660            Expression::JSONColumnDef(e) => self.generate_json_column_def(e),
3661            Expression::JSONExists(e) => self.generate_json_exists(e),
3662            Expression::JSONCast(e) => self.generate_json_cast(e),
3663            Expression::JSONExtract(e) => self.generate_json_extract_path(e),
3664            Expression::JSONExtractArray(e) => self.generate_json_extract_array(e),
3665            Expression::JSONExtractQuote(e) => self.generate_json_extract_quote(e),
3666            Expression::JSONExtractScalar(e) => self.generate_json_extract_scalar(e),
3667            Expression::JSONFormat(e) => self.generate_json_format(e),
3668            Expression::JSONKeyValue(e) => self.generate_json_key_value(e),
3669            Expression::JSONKeys(e) => self.generate_json_keys(e),
3670            Expression::JSONKeysAtDepth(e) => self.generate_json_keys_at_depth(e),
3671            Expression::JSONPath(e) => self.generate_json_path_expr(e),
3672            Expression::JSONPathFilter(e) => self.generate_json_path_filter(e),
3673            Expression::JSONPathKey(e) => self.generate_json_path_key(e),
3674            Expression::JSONPathRecursive(e) => self.generate_json_path_recursive(e),
3675            Expression::JSONPathRoot(_) => self.generate_json_path_root(),
3676            Expression::JSONPathScript(e) => self.generate_json_path_script(e),
3677            Expression::JSONPathSelector(e) => self.generate_json_path_selector(e),
3678            Expression::JSONPathSlice(e) => self.generate_json_path_slice(e),
3679            Expression::JSONPathSubscript(e) => self.generate_json_path_subscript(e),
3680            Expression::JSONPathUnion(e) => self.generate_json_path_union(e),
3681            Expression::JSONRemove(e) => self.generate_json_remove(e),
3682            Expression::JSONSchema(e) => self.generate_json_schema(e),
3683            Expression::JSONSet(e) => self.generate_json_set(e),
3684            Expression::JSONStripNulls(e) => self.generate_json_strip_nulls(e),
3685            Expression::JSONTable(e) => self.generate_json_table(e),
3686            Expression::JSONType(e) => self.generate_json_type(e),
3687            Expression::JSONValue(e) => self.generate_json_value(e),
3688            Expression::JSONValueArray(e) => self.generate_json_value_array(e),
3689            Expression::JarowinklerSimilarity(e) => self.generate_jarowinkler_similarity(e),
3690            Expression::JoinHint(e) => self.generate_join_hint(e),
3691            Expression::JournalProperty(e) => self.generate_journal_property(e),
3692            Expression::LanguageProperty(e) => self.generate_language_property(e),
3693            Expression::Lateral(e) => self.generate_lateral(e),
3694            Expression::LikeProperty(e) => self.generate_like_property(e),
3695            Expression::Limit(e) => self.generate_limit(e),
3696            Expression::LimitOptions(e) => self.generate_limit_options(e),
3697            Expression::List(e) => self.generate_list(e),
3698            Expression::ToMap(e) => self.generate_tomap(e),
3699            Expression::Localtime(e) => self.generate_localtime(e),
3700            Expression::Localtimestamp(e) => self.generate_localtimestamp(e),
3701            Expression::LocationProperty(e) => self.generate_location_property(e),
3702            Expression::Lock(e) => self.generate_lock(e),
3703            Expression::LockProperty(e) => self.generate_lock_property(e),
3704            Expression::LockingProperty(e) => self.generate_locking_property(e),
3705            Expression::LockingStatement(e) => self.generate_locking_statement(e),
3706            Expression::LogProperty(e) => self.generate_log_property(e),
3707            Expression::MD5Digest(e) => self.generate_md5_digest(e),
3708            Expression::MLForecast(e) => self.generate_ml_forecast(e),
3709            Expression::MLTranslate(e) => self.generate_ml_translate(e),
3710            Expression::MakeInterval(e) => self.generate_make_interval(e),
3711            Expression::ManhattanDistance(e) => self.generate_manhattan_distance(e),
3712            Expression::Map(e) => self.generate_map(e),
3713            Expression::MapCat(e) => self.generate_map_cat(e),
3714            Expression::MapDelete(e) => self.generate_map_delete(e),
3715            Expression::MapInsert(e) => self.generate_map_insert(e),
3716            Expression::MapPick(e) => self.generate_map_pick(e),
3717            Expression::MaskingPolicyColumnConstraint(e) => {
3718                self.generate_masking_policy_column_constraint(e)
3719            }
3720            Expression::MatchAgainst(e) => self.generate_match_against(e),
3721            Expression::MatchRecognizeMeasure(e) => self.generate_match_recognize_measure(e),
3722            Expression::MaterializedProperty(e) => self.generate_materialized_property(e),
3723            Expression::Merge(e) => self.generate_merge(e),
3724            Expression::MergeBlockRatioProperty(e) => self.generate_merge_block_ratio_property(e),
3725            Expression::MergeTreeTTL(e) => self.generate_merge_tree_ttl(e),
3726            Expression::MergeTreeTTLAction(e) => self.generate_merge_tree_ttl_action(e),
3727            Expression::Minhash(e) => self.generate_minhash(e),
3728            Expression::ModelAttribute(e) => self.generate_model_attribute(e),
3729            Expression::Monthname(e) => self.generate_monthname(e),
3730            Expression::MultitableInserts(e) => self.generate_multitable_inserts(e),
3731            Expression::NextValueFor(e) => self.generate_next_value_for(e),
3732            Expression::Normal(e) => self.generate_normal(e),
3733            Expression::Normalize(e) => self.generate_normalize(e),
3734            Expression::NotNullColumnConstraint(e) => self.generate_not_null_column_constraint(e),
3735            Expression::Nullif(e) => self.generate_nullif(e),
3736            Expression::NumberToStr(e) => self.generate_number_to_str(e),
3737            Expression::ObjectAgg(e) => self.generate_object_agg(e),
3738            Expression::ObjectIdentifier(e) => self.generate_object_identifier(e),
3739            Expression::ObjectInsert(e) => self.generate_object_insert(e),
3740            Expression::Offset(e) => self.generate_offset(e),
3741            Expression::Qualify(e) => self.generate_qualify(e),
3742            Expression::OnCluster(e) => self.generate_on_cluster(e),
3743            Expression::OnCommitProperty(e) => self.generate_on_commit_property(e),
3744            Expression::OnCondition(e) => self.generate_on_condition(e),
3745            Expression::OnConflict(e) => self.generate_on_conflict(e),
3746            Expression::OnProperty(e) => self.generate_on_property(e),
3747            Expression::Opclass(e) => self.generate_opclass(e),
3748            Expression::OpenJSON(e) => self.generate_open_json(e),
3749            Expression::OpenJSONColumnDef(e) => self.generate_open_json_column_def(e),
3750            Expression::Operator(e) => self.generate_operator(e),
3751            Expression::OrderBy(e) => self.generate_order_by(e),
3752            Expression::OutputModelProperty(e) => self.generate_output_model_property(e),
3753            Expression::OverflowTruncateBehavior(e) => self.generate_overflow_truncate_behavior(e),
3754            Expression::ParameterizedAgg(e) => self.generate_parameterized_agg(e),
3755            Expression::ParseDatetime(e) => self.generate_parse_datetime(e),
3756            Expression::ParseIp(e) => self.generate_parse_ip(e),
3757            Expression::ParseJSON(e) => self.generate_parse_json(e),
3758            Expression::ParseTime(e) => self.generate_parse_time(e),
3759            Expression::ParseUrl(e) => self.generate_parse_url(e),
3760            Expression::Partition(e) => self.generate_partition_expr(e),
3761            Expression::PartitionBoundSpec(e) => self.generate_partition_bound_spec(e),
3762            Expression::PartitionByListProperty(e) => self.generate_partition_by_list_property(e),
3763            Expression::PartitionByRangeProperty(e) => self.generate_partition_by_range_property(e),
3764            Expression::PartitionByRangePropertyDynamic(e) => {
3765                self.generate_partition_by_range_property_dynamic(e)
3766            }
3767            Expression::PartitionByTruncate(e) => self.generate_partition_by_truncate(e),
3768            Expression::PartitionList(e) => self.generate_partition_list(e),
3769            Expression::PartitionRange(e) => self.generate_partition_range(e),
3770            Expression::PartitionByProperty(e) => self.generate_partition_by_property(e),
3771            Expression::PartitionedByBucket(e) => self.generate_partitioned_by_bucket(e),
3772            Expression::PartitionedByProperty(e) => self.generate_partitioned_by_property(e),
3773            Expression::PartitionedOfProperty(e) => self.generate_partitioned_of_property(e),
3774            Expression::PeriodForSystemTimeConstraint(e) => {
3775                self.generate_period_for_system_time_constraint(e)
3776            }
3777            Expression::PivotAlias(e) => self.generate_pivot_alias(e),
3778            Expression::PivotAny(e) => self.generate_pivot_any(e),
3779            Expression::Predict(e) => self.generate_predict(e),
3780            Expression::PreviousDay(e) => self.generate_previous_day(e),
3781            Expression::PrimaryKey(e) => self.generate_primary_key(e),
3782            Expression::PrimaryKeyColumnConstraint(e) => {
3783                self.generate_primary_key_column_constraint(e)
3784            }
3785            Expression::PathColumnConstraint(e) => self.generate_path_column_constraint(e),
3786            Expression::ProjectionDef(e) => self.generate_projection_def(e),
3787            Expression::OptionsProperty(e) => self.generate_options_property(e),
3788            Expression::Properties(e) => self.generate_properties(e),
3789            Expression::Property(e) => self.generate_property(e),
3790            Expression::PseudoType(e) => self.generate_pseudo_type(e),
3791            Expression::Put(e) => self.generate_put(e),
3792            Expression::Quantile(e) => self.generate_quantile(e),
3793            Expression::QueryBand(e) => self.generate_query_band(e),
3794            Expression::QueryOption(e) => self.generate_query_option(e),
3795            Expression::QueryTransform(e) => self.generate_query_transform(e),
3796            Expression::Randn(e) => self.generate_randn(e),
3797            Expression::Randstr(e) => self.generate_randstr(e),
3798            Expression::RangeBucket(e) => self.generate_range_bucket(e),
3799            Expression::RangeN(e) => self.generate_range_n(e),
3800            Expression::ReadCSV(e) => self.generate_read_csv(e),
3801            Expression::ReadParquet(e) => self.generate_read_parquet(e),
3802            Expression::RecursiveWithSearch(e) => self.generate_recursive_with_search(e),
3803            Expression::Reduce(e) => self.generate_reduce(e),
3804            Expression::Reference(e) => self.generate_reference(e),
3805            Expression::Refresh(e) => self.generate_refresh(e),
3806            Expression::RefreshTriggerProperty(e) => self.generate_refresh_trigger_property(e),
3807            Expression::RegexpCount(e) => self.generate_regexp_count(e),
3808            Expression::RegexpExtractAll(e) => self.generate_regexp_extract_all(e),
3809            Expression::RegexpFullMatch(e) => self.generate_regexp_full_match(e),
3810            Expression::RegexpILike(e) => self.generate_regexp_i_like(e),
3811            Expression::RegexpInstr(e) => self.generate_regexp_instr(e),
3812            Expression::RegexpSplit(e) => self.generate_regexp_split(e),
3813            Expression::RegrAvgx(e) => self.generate_regr_avgx(e),
3814            Expression::RegrAvgy(e) => self.generate_regr_avgy(e),
3815            Expression::RegrCount(e) => self.generate_regr_count(e),
3816            Expression::RegrIntercept(e) => self.generate_regr_intercept(e),
3817            Expression::RegrR2(e) => self.generate_regr_r2(e),
3818            Expression::RegrSlope(e) => self.generate_regr_slope(e),
3819            Expression::RegrSxx(e) => self.generate_regr_sxx(e),
3820            Expression::RegrSxy(e) => self.generate_regr_sxy(e),
3821            Expression::RegrSyy(e) => self.generate_regr_syy(e),
3822            Expression::RegrValx(e) => self.generate_regr_valx(e),
3823            Expression::RegrValy(e) => self.generate_regr_valy(e),
3824            Expression::RemoteWithConnectionModelProperty(e) => {
3825                self.generate_remote_with_connection_model_property(e)
3826            }
3827            Expression::RenameColumn(e) => self.generate_rename_column(e),
3828            Expression::ReplacePartition(e) => self.generate_replace_partition(e),
3829            Expression::Returning(e) => self.generate_returning(e),
3830            Expression::ReturnsProperty(e) => self.generate_returns_property(e),
3831            Expression::Rollback(e) => self.generate_rollback(e),
3832            Expression::Rollup(e) => self.generate_rollup(e),
3833            Expression::RowFormatDelimitedProperty(e) => {
3834                self.generate_row_format_delimited_property(e)
3835            }
3836            Expression::RowFormatProperty(e) => self.generate_row_format_property(e),
3837            Expression::RowFormatSerdeProperty(e) => self.generate_row_format_serde_property(e),
3838            Expression::SHA2(e) => self.generate_sha2(e),
3839            Expression::SHA2Digest(e) => self.generate_sha2_digest(e),
3840            Expression::SafeAdd(e) => self.generate_safe_add(e),
3841            Expression::SafeDivide(e) => self.generate_safe_divide(e),
3842            Expression::SafeMultiply(e) => self.generate_safe_multiply(e),
3843            Expression::SafeSubtract(e) => self.generate_safe_subtract(e),
3844            Expression::SampleProperty(e) => self.generate_sample_property(e),
3845            Expression::Schema(e) => self.generate_schema(e),
3846            Expression::SchemaCommentProperty(e) => self.generate_schema_comment_property(e),
3847            Expression::ScopeResolution(e) => self.generate_scope_resolution(e),
3848            Expression::Search(e) => self.generate_search(e),
3849            Expression::SearchIp(e) => self.generate_search_ip(e),
3850            Expression::SecurityProperty(e) => self.generate_security_property(e),
3851            Expression::SemanticView(e) => self.generate_semantic_view(e),
3852            Expression::SequenceProperties(e) => self.generate_sequence_properties(e),
3853            Expression::SerdeProperties(e) => self.generate_serde_properties(e),
3854            Expression::SessionParameter(e) => self.generate_session_parameter(e),
3855            Expression::Set(e) => self.generate_set(e),
3856            Expression::SetConfigProperty(e) => self.generate_set_config_property(e),
3857            Expression::SetItem(e) => self.generate_set_item(e),
3858            Expression::SetOperation(e) => self.generate_set_operation(e),
3859            Expression::SetProperty(e) => self.generate_set_property(e),
3860            Expression::SettingsProperty(e) => self.generate_settings_property(e),
3861            Expression::SharingProperty(e) => self.generate_sharing_property(e),
3862            Expression::Slice(e) => self.generate_slice(e),
3863            Expression::SortArray(e) => self.generate_sort_array(e),
3864            Expression::SortBy(e) => self.generate_sort_by(e),
3865            Expression::SortKeyProperty(e) => self.generate_sort_key_property(e),
3866            Expression::SplitPart(e) => self.generate_split_part(e),
3867            Expression::SqlReadWriteProperty(e) => self.generate_sql_read_write_property(e),
3868            Expression::SqlSecurityProperty(e) => self.generate_sql_security_property(e),
3869            Expression::StDistance(e) => self.generate_st_distance(e),
3870            Expression::StPoint(e) => self.generate_st_point(e),
3871            Expression::StabilityProperty(e) => self.generate_stability_property(e),
3872            Expression::StandardHash(e) => self.generate_standard_hash(e),
3873            Expression::StorageHandlerProperty(e) => self.generate_storage_handler_property(e),
3874            Expression::StrPosition(e) => self.generate_str_position(e),
3875            Expression::StrToDate(e) => self.generate_str_to_date(e),
3876            Expression::DateStrToDate(f) => self.generate_simple_func("DATE_STR_TO_DATE", &f.this),
3877            Expression::DateToDateStr(f) => self.generate_simple_func("DATE_TO_DATE_STR", &f.this),
3878            Expression::StrToMap(e) => self.generate_str_to_map(e),
3879            Expression::StrToTime(e) => self.generate_str_to_time(e),
3880            Expression::StrToUnix(e) => self.generate_str_to_unix(e),
3881            Expression::StringToArray(e) => self.generate_string_to_array(e),
3882            Expression::Struct(e) => self.generate_struct(e),
3883            Expression::Stuff(e) => self.generate_stuff(e),
3884            Expression::SubstringIndex(e) => self.generate_substring_index(e),
3885            Expression::Summarize(e) => self.generate_summarize(e),
3886            Expression::Systimestamp(e) => self.generate_systimestamp(e),
3887            Expression::TableAlias(e) => self.generate_table_alias(e),
3888            Expression::TableFromRows(e) => self.generate_table_from_rows(e),
3889            Expression::RowsFrom(e) => self.generate_rows_from(e),
3890            Expression::TableSample(e) => self.generate_table_sample(e),
3891            Expression::Tag(e) => self.generate_tag(e),
3892            Expression::Tags(e) => self.generate_tags(e),
3893            Expression::TemporaryProperty(e) => self.generate_temporary_property(e),
3894            Expression::Time(e) => self.generate_time_func(e),
3895            Expression::TimeAdd(e) => self.generate_time_add(e),
3896            Expression::TimeDiff(e) => self.generate_time_diff(e),
3897            Expression::TimeFromParts(e) => self.generate_time_from_parts(e),
3898            Expression::TimeSlice(e) => self.generate_time_slice(e),
3899            Expression::TimeStrToDate(e) => self.generate_time_str_to_date(e),
3900            Expression::TimeStrToTime(e) => self.generate_time_str_to_time(e),
3901            Expression::TimeSub(e) => self.generate_time_sub(e),
3902            Expression::TimeToStr(e) => self.generate_time_to_str(e),
3903            Expression::TimeToUnix(e) => self.generate_time_to_unix(e),
3904            Expression::TimeTrunc(e) => self.generate_time_trunc(e),
3905            Expression::TimeUnit(e) => self.generate_time_unit(e),
3906            Expression::Timestamp(e) => self.generate_timestamp_func(e),
3907            Expression::TimestampAdd(e) => self.generate_timestamp_add(e),
3908            Expression::TimestampDiff(e) => self.generate_timestamp_diff(e),
3909            Expression::TimestampFromParts(e) => self.generate_timestamp_from_parts(e),
3910            Expression::TimestampSub(e) => self.generate_timestamp_sub(e),
3911            Expression::TimestampTzFromParts(e) => self.generate_timestamp_tz_from_parts(e),
3912            Expression::ToBinary(e) => self.generate_to_binary(e),
3913            Expression::ToBoolean(e) => self.generate_to_boolean(e),
3914            Expression::ToChar(e) => self.generate_to_char(e),
3915            Expression::ToDecfloat(e) => self.generate_to_decfloat(e),
3916            Expression::ToDouble(e) => self.generate_to_double(e),
3917            Expression::ToFile(e) => self.generate_to_file(e),
3918            Expression::ToNumber(e) => self.generate_to_number(e),
3919            Expression::ToTableProperty(e) => self.generate_to_table_property(e),
3920            Expression::Transaction(e) => self.generate_transaction(e),
3921            Expression::Transform(e) => self.generate_transform(e),
3922            Expression::TransformModelProperty(e) => self.generate_transform_model_property(e),
3923            Expression::TransientProperty(e) => self.generate_transient_property(e),
3924            Expression::Translate(e) => self.generate_translate(e),
3925            Expression::TranslateCharacters(e) => self.generate_translate_characters(e),
3926            Expression::TruncateTable(e) => self.generate_truncate_table(e),
3927            Expression::TryBase64DecodeBinary(e) => self.generate_try_base64_decode_binary(e),
3928            Expression::TryBase64DecodeString(e) => self.generate_try_base64_decode_string(e),
3929            Expression::TryToDecfloat(e) => self.generate_try_to_decfloat(e),
3930            Expression::TsOrDsAdd(e) => self.generate_ts_or_ds_add(e),
3931            Expression::TsOrDsDiff(e) => self.generate_ts_or_ds_diff(e),
3932            Expression::TsOrDsToDate(e) => self.generate_ts_or_ds_to_date(e),
3933            Expression::TsOrDsToTime(e) => self.generate_ts_or_ds_to_time(e),
3934            Expression::Unhex(e) => self.generate_unhex(e),
3935            Expression::UnicodeString(e) => self.generate_unicode_string(e),
3936            Expression::Uniform(e) => self.generate_uniform(e),
3937            Expression::UniqueColumnConstraint(e) => self.generate_unique_column_constraint(e),
3938            Expression::UniqueKeyProperty(e) => self.generate_unique_key_property(e),
3939            Expression::RollupProperty(e) => self.generate_rollup_property(e),
3940            Expression::UnixToStr(e) => self.generate_unix_to_str(e),
3941            Expression::UnixToTime(e) => self.generate_unix_to_time(e),
3942            Expression::UnpivotColumns(e) => self.generate_unpivot_columns(e),
3943            Expression::UserDefinedFunction(e) => self.generate_user_defined_function(e),
3944            Expression::UsingTemplateProperty(e) => self.generate_using_template_property(e),
3945            Expression::UtcTime(e) => self.generate_utc_time(e),
3946            Expression::UtcTimestamp(e) => self.generate_utc_timestamp(e),
3947            Expression::Uuid(e) => self.generate_uuid(e),
3948            Expression::Var(v) => {
3949                if matches!(self.config.dialect, Some(DialectType::MySQL))
3950                    && v.this.len() > 2
3951                    && (v.this.starts_with("0x") || v.this.starts_with("0X"))
3952                    && !v.this[2..].chars().all(|c| c.is_ascii_hexdigit())
3953                {
3954                    return self.generate_identifier(&Identifier {
3955                        name: v.this.clone(),
3956                        quoted: true,
3957                        trailing_comments: Vec::new(),
3958                        span: None,
3959                    });
3960                }
3961                self.write(&v.this);
3962                Ok(())
3963            }
3964            Expression::Variadic(e) => {
3965                self.write_keyword("VARIADIC");
3966                self.write_space();
3967                self.generate_expression(&e.this)?;
3968                Ok(())
3969            }
3970            Expression::VarMap(e) => self.generate_var_map(e),
3971            Expression::VectorSearch(e) => self.generate_vector_search(e),
3972            Expression::Version(e) => self.generate_version(e),
3973            Expression::ViewAttributeProperty(e) => self.generate_view_attribute_property(e),
3974            Expression::VolatileProperty(e) => self.generate_volatile_property(e),
3975            Expression::WatermarkColumnConstraint(e) => {
3976                self.generate_watermark_column_constraint(e)
3977            }
3978            Expression::Week(e) => self.generate_week(e),
3979            Expression::When(e) => self.generate_when(e),
3980            Expression::Whens(e) => self.generate_whens(e),
3981            Expression::Where(e) => self.generate_where(e),
3982            Expression::WidthBucket(e) => self.generate_width_bucket(e),
3983            Expression::Window(e) => self.generate_window(e),
3984            Expression::WindowSpec(e) => self.generate_window_spec(e),
3985            Expression::WithDataProperty(e) => self.generate_with_data_property(e),
3986            Expression::WithFill(e) => self.generate_with_fill(e),
3987            Expression::WithJournalTableProperty(e) => self.generate_with_journal_table_property(e),
3988            Expression::WithOperator(e) => self.generate_with_operator(e),
3989            Expression::WithProcedureOptions(e) => self.generate_with_procedure_options(e),
3990            Expression::WithSchemaBindingProperty(e) => {
3991                self.generate_with_schema_binding_property(e)
3992            }
3993            Expression::WithSystemVersioningProperty(e) => {
3994                self.generate_with_system_versioning_property(e)
3995            }
3996            Expression::WithTableHint(e) => self.generate_with_table_hint(e),
3997            Expression::XMLElement(e) => self.generate_xml_element(e),
3998            Expression::XMLGet(e) => self.generate_xml_get(e),
3999            Expression::XMLKeyValueOption(e) => self.generate_xml_key_value_option(e),
4000            Expression::XMLTable(e) => self.generate_xml_table(e),
4001            Expression::Xor(e) => self.generate_xor(e),
4002            Expression::Zipf(e) => self.generate_zipf(e),
4003            _ => self.write_unsupported_comment("unsupported expression"),
4004        }
4005    }
4006
4007    fn generate_select(&mut self, select: &Select) -> Result<()> {
4008        use crate::dialects::DialectType;
4009
4010        // Redshift-style EXCLUDE: for dialects other than Redshift, wrap in a derived table
4011        // e.g., SELECT *, col4 EXCLUDE (col2, col3) FROM t
4012        //   → SELECT * EXCLUDE (col2, col3) FROM (SELECT *, col4 FROM t)
4013        if let Some(exclude) = &select.exclude {
4014            if !exclude.is_empty() && !matches!(self.config.dialect, Some(DialectType::Redshift)) {
4015                // Build the inner select (same as original but without exclude)
4016                let mut inner_select = select.clone();
4017                inner_select.exclude = None;
4018                let inner_expr = Expression::Select(Box::new(inner_select));
4019
4020                // Build the subquery
4021                let subquery = crate::expressions::Subquery {
4022                    this: inner_expr,
4023                    alias: None,
4024                    column_aliases: Vec::new(),
4025                    order_by: None,
4026                    limit: None,
4027                    offset: None,
4028                    distribute_by: None,
4029                    sort_by: None,
4030                    cluster_by: None,
4031                    lateral: false,
4032                    modifiers_inside: false,
4033                    trailing_comments: Vec::new(),
4034                    inferred_type: None,
4035                };
4036
4037                // Build the outer select: SELECT * EXCLUDE (cols) FROM (inner)
4038                let star = Expression::Star(crate::expressions::Star {
4039                    table: None,
4040                    except: Some(
4041                        exclude
4042                            .iter()
4043                            .map(|e| match e {
4044                                Expression::Column(col) => col.name.clone(),
4045                                Expression::Identifier(id) => id.clone(),
4046                                _ => crate::expressions::Identifier::new("unknown".to_string()),
4047                            })
4048                            .collect(),
4049                    ),
4050                    replace: None,
4051                    rename: None,
4052                    trailing_comments: Vec::new(),
4053                    span: None,
4054                });
4055
4056                let outer_select = Select {
4057                    expressions: vec![star],
4058                    from: Some(crate::expressions::From {
4059                        expressions: vec![Expression::Subquery(Box::new(subquery))],
4060                    }),
4061                    ..Select::new()
4062                };
4063
4064                return self.generate_select(&outer_select);
4065            }
4066        }
4067
4068        // Output leading comments before SELECT
4069        for comment in &select.leading_comments {
4070            self.write_formatted_comment(comment);
4071            self.write(" ");
4072        }
4073
4074        // WITH clause
4075        if let Some(with) = &select.with {
4076            self.generate_with(with)?;
4077            if self.config.pretty {
4078                self.write_newline();
4079                self.write_indent();
4080            } else {
4081                self.write_space();
4082            }
4083        }
4084
4085        // Output post-SELECT comments (comments that appeared after SELECT keyword)
4086        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
4087        for comment in &select.post_select_comments {
4088            self.write_formatted_comment(comment);
4089            self.write(" ");
4090        }
4091
4092        self.write_keyword("SELECT");
4093
4094        // Generate query hint if present /*+ ... */
4095        if let Some(hint) = &select.hint {
4096            self.generate_hint(hint)?;
4097        }
4098
4099        // For SQL Server, convert LIMIT to TOP (structural transformation)
4100        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
4101        // TOP clause (SQL Server style - before DISTINCT)
4102        let use_top_from_limit = matches!(self.config.dialect, Some(DialectType::TSQL))
4103            && select.top.is_none()
4104            && select.limit.is_some()
4105            && select.offset.is_none(); // Don't use TOP when there's OFFSET
4106
4107        // For TOP-supporting dialects: DISTINCT before TOP
4108        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
4109        let is_top_dialect = matches!(
4110            self.config.dialect,
4111            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
4112        );
4113        let keep_top_verbatim = !is_top_dialect
4114            && select.limit.is_none()
4115            && select
4116                .top
4117                .as_ref()
4118                .map_or(false, |top| top.percent || top.with_ties);
4119
4120        if select.distinct && (is_top_dialect || select.top.is_some()) {
4121            self.write_space();
4122            self.write_keyword("DISTINCT");
4123        }
4124
4125        if is_top_dialect || keep_top_verbatim {
4126            if let Some(top) = &select.top {
4127                self.write_space();
4128                self.write_keyword("TOP");
4129                if top.parenthesized {
4130                    self.write(" (");
4131                    self.generate_expression(&top.this)?;
4132                    self.write(")");
4133                } else {
4134                    self.write_space();
4135                    self.generate_expression(&top.this)?;
4136                }
4137                if top.percent {
4138                    self.write_space();
4139                    self.write_keyword("PERCENT");
4140                }
4141                if top.with_ties {
4142                    self.write_space();
4143                    self.write_keyword("WITH TIES");
4144                }
4145            } else if use_top_from_limit {
4146                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
4147                if let Some(limit) = &select.limit {
4148                    self.write_space();
4149                    self.write_keyword("TOP");
4150                    // Use parentheses for complex expressions, but not for simple literals
4151                    let is_simple_literal = matches!(&limit.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)));
4152                    if is_simple_literal {
4153                        self.write_space();
4154                        self.generate_expression(&limit.this)?;
4155                    } else {
4156                        self.write(" (");
4157                        self.generate_expression(&limit.this)?;
4158                        self.write(")");
4159                    }
4160                }
4161            }
4162        }
4163
4164        if select.distinct && !is_top_dialect && select.top.is_none() {
4165            self.write_space();
4166            self.write_keyword("DISTINCT");
4167        }
4168
4169        // DISTINCT ON clause (PostgreSQL)
4170        if let Some(distinct_on) = &select.distinct_on {
4171            self.write_space();
4172            self.write_keyword("ON");
4173            self.write(" (");
4174            for (i, expr) in distinct_on.iter().enumerate() {
4175                if i > 0 {
4176                    self.write(", ");
4177                }
4178                self.generate_expression(expr)?;
4179            }
4180            self.write(")");
4181        }
4182
4183        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
4184        for modifier in &select.operation_modifiers {
4185            self.write_space();
4186            self.write_keyword(modifier);
4187        }
4188
4189        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
4190        if let Some(kind) = &select.kind {
4191            self.write_space();
4192            self.write_keyword("AS");
4193            self.write_space();
4194            self.write_keyword(kind);
4195        }
4196
4197        // Expressions (only if there are any)
4198        if !select.expressions.is_empty() {
4199            if self.config.pretty {
4200                self.write_newline();
4201                self.indent_level += 1;
4202            } else {
4203                self.write_space();
4204            }
4205        }
4206
4207        for (i, expr) in select.expressions.iter().enumerate() {
4208            if i > 0 {
4209                self.write(",");
4210                if self.config.pretty {
4211                    self.write_newline();
4212                } else {
4213                    self.write_space();
4214                }
4215            }
4216            if self.config.pretty {
4217                self.write_indent();
4218            }
4219            self.generate_expression(expr)?;
4220        }
4221
4222        if self.config.pretty && !select.expressions.is_empty() {
4223            self.indent_level -= 1;
4224        }
4225
4226        // Redshift-style EXCLUDE clause at the end of the projection list
4227        // For Redshift dialect: append EXCLUDE (col1, col2) after the expressions
4228        // For other dialects (DuckDB, Snowflake): this is handled by wrapping in a derived table
4229        // (done after the full select is generated below)
4230        if let Some(exclude) = &select.exclude {
4231            if !exclude.is_empty() && matches!(self.config.dialect, Some(DialectType::Redshift)) {
4232                self.write_space();
4233                self.write_keyword("EXCLUDE");
4234                self.write(" (");
4235                for (i, col) in exclude.iter().enumerate() {
4236                    if i > 0 {
4237                        self.write(", ");
4238                    }
4239                    self.generate_expression(col)?;
4240                }
4241                self.write(")");
4242            }
4243        }
4244
4245        // INTO clause (SELECT ... INTO table_name)
4246        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4247        if let Some(into) = &select.into {
4248            if self.config.pretty {
4249                self.write_newline();
4250                self.write_indent();
4251            } else {
4252                self.write_space();
4253            }
4254            if into.bulk_collect {
4255                self.write_keyword("BULK COLLECT INTO");
4256            } else {
4257                self.write_keyword("INTO");
4258            }
4259            if into.temporary {
4260                self.write_space();
4261                self.write_keyword("TEMPORARY");
4262            }
4263            if into.unlogged {
4264                self.write_space();
4265                self.write_keyword("UNLOGGED");
4266            }
4267            self.write_space();
4268            // If we have multiple expressions, output them comma-separated
4269            if !into.expressions.is_empty() {
4270                for (i, expr) in into.expressions.iter().enumerate() {
4271                    if i > 0 {
4272                        self.write(", ");
4273                    }
4274                    self.generate_expression(expr)?;
4275                }
4276            } else {
4277                self.generate_expression(&into.this)?;
4278            }
4279        }
4280
4281        // FROM clause
4282        if let Some(from) = &select.from {
4283            if self.config.pretty {
4284                self.write_newline();
4285                self.write_indent();
4286            } else {
4287                self.write_space();
4288            }
4289            self.write_keyword("FROM");
4290            self.write_space();
4291
4292            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4293            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4294            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4295            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4296            let has_tablesample = from
4297                .expressions
4298                .iter()
4299                .any(|e| matches!(e, Expression::TableSample(_)));
4300            let is_cross_join_dialect = matches!(
4301                self.config.dialect,
4302                Some(DialectType::BigQuery)
4303                    | Some(DialectType::Hive)
4304                    | Some(DialectType::Spark)
4305                    | Some(DialectType::Databricks)
4306                    | Some(DialectType::SQLite)
4307                    | Some(DialectType::ClickHouse)
4308            );
4309            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4310            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4311            let source_is_same_as_target = self.config.source_dialect.is_some()
4312                && self.config.source_dialect == self.config.dialect;
4313            let source_is_cross_join_dialect = matches!(
4314                self.config.source_dialect,
4315                Some(DialectType::BigQuery)
4316                    | Some(DialectType::Hive)
4317                    | Some(DialectType::Spark)
4318                    | Some(DialectType::Databricks)
4319                    | Some(DialectType::SQLite)
4320                    | Some(DialectType::ClickHouse)
4321            );
4322            let use_cross_join = !has_tablesample
4323                && is_cross_join_dialect
4324                && (source_is_same_as_target
4325                    || source_is_cross_join_dialect
4326                    || self.config.source_dialect.is_none());
4327
4328            // Snowflake wraps standalone VALUES in FROM clause with parentheses
4329            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
4330
4331            for (i, expr) in from.expressions.iter().enumerate() {
4332                if i > 0 {
4333                    if use_cross_join {
4334                        self.write(" CROSS JOIN ");
4335                    } else {
4336                        self.write(", ");
4337                    }
4338                }
4339                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
4340                    self.write("(");
4341                    self.generate_expression(expr)?;
4342                    self.write(")");
4343                } else {
4344                    self.generate_expression(expr)?;
4345                }
4346                // Output leading comments that were on the table name before FROM
4347                // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
4348                let leading = Self::extract_table_leading_comments(expr);
4349                for comment in &leading {
4350                    self.write_space();
4351                    self.write_formatted_comment(comment);
4352                }
4353            }
4354        }
4355
4356        // JOINs - handle nested join structure for pretty printing
4357        // Deferred-condition joins "own" the non-deferred joins that follow them
4358        // until the next deferred join or end of list
4359        if self.config.pretty {
4360            self.generate_joins_with_nesting(&select.joins)?;
4361        } else {
4362            for join in &select.joins {
4363                self.generate_join(join)?;
4364            }
4365            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4366            for join in select.joins.iter().rev() {
4367                if join.deferred_condition {
4368                    self.generate_join_condition(join)?;
4369                }
4370            }
4371        }
4372
4373        // LATERAL VIEW clauses (Hive/Spark)
4374        for (lv_idx, lateral_view) in select.lateral_views.iter().enumerate() {
4375            self.generate_lateral_view(lateral_view, lv_idx)?;
4376        }
4377
4378        // PREWHERE (ClickHouse)
4379        if let Some(prewhere) = &select.prewhere {
4380            self.write_clause_condition("PREWHERE", prewhere)?;
4381        }
4382
4383        // WHERE
4384        if let Some(where_clause) = &select.where_clause {
4385            self.write_clause_condition("WHERE", &where_clause.this)?;
4386        }
4387
4388        // CONNECT BY (Oracle hierarchical queries)
4389        if let Some(connect) = &select.connect {
4390            self.generate_connect(connect)?;
4391        }
4392
4393        // GROUP BY
4394        if let Some(group_by) = &select.group_by {
4395            if self.config.pretty {
4396                // Output leading comments on their own lines before GROUP BY
4397                for comment in &group_by.comments {
4398                    self.write_newline();
4399                    self.write_indent();
4400                    self.write_formatted_comment(comment);
4401                }
4402                self.write_newline();
4403                self.write_indent();
4404            } else {
4405                self.write_space();
4406                // In non-pretty mode, output comments inline
4407                for comment in &group_by.comments {
4408                    self.write_formatted_comment(comment);
4409                    self.write_space();
4410                }
4411            }
4412            self.write_keyword("GROUP BY");
4413            // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
4414            match group_by.all {
4415                Some(true) => {
4416                    self.write_space();
4417                    self.write_keyword("ALL");
4418                }
4419                Some(false) => {
4420                    self.write_space();
4421                    self.write_keyword("DISTINCT");
4422                }
4423                None => {}
4424            }
4425            if !group_by.expressions.is_empty() {
4426                // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
4427                // These are represented as Cube/Rollup expressions with empty expressions at the end
4428                let mut trailing_cube = false;
4429                let mut trailing_rollup = false;
4430                let mut plain_expressions: Vec<&Expression> = Vec::new();
4431                let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
4432                let mut cube_expressions: Vec<&Expression> = Vec::new();
4433                let mut rollup_expressions: Vec<&Expression> = Vec::new();
4434
4435                for expr in &group_by.expressions {
4436                    match expr {
4437                        Expression::Cube(c) if c.expressions.is_empty() => {
4438                            trailing_cube = true;
4439                        }
4440                        Expression::Rollup(r) if r.expressions.is_empty() => {
4441                            trailing_rollup = true;
4442                        }
4443                        Expression::Function(f) if f.name == "CUBE" => {
4444                            cube_expressions.push(expr);
4445                        }
4446                        Expression::Function(f) if f.name == "ROLLUP" => {
4447                            rollup_expressions.push(expr);
4448                        }
4449                        Expression::Function(f) if f.name == "GROUPING SETS" => {
4450                            grouping_sets_expressions.push(expr);
4451                        }
4452                        _ => {
4453                            plain_expressions.push(expr);
4454                        }
4455                    }
4456                }
4457
4458                // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
4459                let mut regular_expressions: Vec<&Expression> = Vec::new();
4460                regular_expressions.extend(plain_expressions);
4461                regular_expressions.extend(grouping_sets_expressions);
4462                regular_expressions.extend(cube_expressions);
4463                regular_expressions.extend(rollup_expressions);
4464
4465                if self.config.pretty {
4466                    self.write_newline();
4467                    self.indent_level += 1;
4468                    self.write_indent();
4469                } else {
4470                    self.write_space();
4471                }
4472
4473                for (i, expr) in regular_expressions.iter().enumerate() {
4474                    if i > 0 {
4475                        if self.config.pretty {
4476                            self.write(",");
4477                            self.write_newline();
4478                            self.write_indent();
4479                        } else {
4480                            self.write(", ");
4481                        }
4482                    }
4483                    self.generate_expression(expr)?;
4484                }
4485
4486                if self.config.pretty {
4487                    self.indent_level -= 1;
4488                }
4489
4490                // Output trailing WITH CUBE or WITH ROLLUP
4491                if trailing_cube {
4492                    self.write_space();
4493                    self.write_keyword("WITH CUBE");
4494                } else if trailing_rollup {
4495                    self.write_space();
4496                    self.write_keyword("WITH ROLLUP");
4497                }
4498            }
4499
4500            // ClickHouse: WITH TOTALS
4501            if group_by.totals {
4502                self.write_space();
4503                self.write_keyword("WITH TOTALS");
4504            }
4505        }
4506
4507        // HAVING
4508        if let Some(having) = &select.having {
4509            if self.config.pretty {
4510                // Output leading comments on their own lines before HAVING
4511                for comment in &having.comments {
4512                    self.write_newline();
4513                    self.write_indent();
4514                    self.write_formatted_comment(comment);
4515                }
4516            } else {
4517                for comment in &having.comments {
4518                    self.write_space();
4519                    self.write_formatted_comment(comment);
4520                }
4521            }
4522            self.write_clause_condition("HAVING", &having.this)?;
4523        }
4524
4525        // QUALIFY and WINDOW clause ordering depends on input SQL
4526        if select.qualify_after_window {
4527            // WINDOW before QUALIFY (DuckDB style)
4528            if let Some(windows) = &select.windows {
4529                self.write_window_clause(windows)?;
4530            }
4531            if let Some(qualify) = &select.qualify {
4532                self.write_clause_condition("QUALIFY", &qualify.this)?;
4533            }
4534        } else {
4535            // QUALIFY before WINDOW (Snowflake/BigQuery default)
4536            if let Some(qualify) = &select.qualify {
4537                self.write_clause_condition("QUALIFY", &qualify.this)?;
4538            }
4539            if let Some(windows) = &select.windows {
4540                self.write_window_clause(windows)?;
4541            }
4542        }
4543
4544        // DISTRIBUTE BY (Hive/Spark)
4545        if let Some(distribute_by) = &select.distribute_by {
4546            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
4547        }
4548
4549        // CLUSTER BY (Hive/Spark)
4550        if let Some(cluster_by) = &select.cluster_by {
4551            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
4552        }
4553
4554        // SORT BY (Hive/Spark - comes before ORDER BY)
4555        if let Some(sort_by) = &select.sort_by {
4556            self.write_order_clause("SORT BY", &sort_by.expressions)?;
4557        }
4558
4559        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
4560        if let Some(order_by) = &select.order_by {
4561            if self.config.pretty {
4562                // Output leading comments on their own lines before ORDER BY
4563                for comment in &order_by.comments {
4564                    self.write_newline();
4565                    self.write_indent();
4566                    self.write_formatted_comment(comment);
4567                }
4568            } else {
4569                for comment in &order_by.comments {
4570                    self.write_space();
4571                    self.write_formatted_comment(comment);
4572                }
4573            }
4574            let keyword = if order_by.siblings {
4575                "ORDER SIBLINGS BY"
4576            } else {
4577                "ORDER BY"
4578            };
4579            self.write_order_clause(keyword, &order_by.expressions)?;
4580        }
4581
4582        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
4583        if select.order_by.is_none()
4584            && select.fetch.is_some()
4585            && matches!(
4586                self.config.dialect,
4587                Some(DialectType::TSQL) | Some(DialectType::Fabric)
4588            )
4589        {
4590            if self.config.pretty {
4591                self.write_newline();
4592                self.write_indent();
4593            } else {
4594                self.write_space();
4595            }
4596            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
4597        }
4598
4599        // LIMIT and OFFSET
4600        // PostgreSQL and others use: LIMIT count OFFSET offset
4601        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
4602        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
4603        let is_presto_like = matches!(
4604            self.config.dialect,
4605            Some(DialectType::Presto) | Some(DialectType::Trino)
4606        );
4607
4608        if is_presto_like && select.offset.is_some() {
4609            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
4610            if let Some(offset) = &select.offset {
4611                if self.config.pretty {
4612                    self.write_newline();
4613                    self.write_indent();
4614                } else {
4615                    self.write_space();
4616                }
4617                self.write_keyword("OFFSET");
4618                self.write_space();
4619                self.write_limit_expr(&offset.this)?;
4620                if offset.rows == Some(true) {
4621                    self.write_space();
4622                    self.write_keyword("ROWS");
4623                }
4624            }
4625            if let Some(limit) = &select.limit {
4626                if self.config.pretty {
4627                    self.write_newline();
4628                    self.write_indent();
4629                } else {
4630                    self.write_space();
4631                }
4632                self.write_keyword("LIMIT");
4633                self.write_space();
4634                self.write_limit_expr(&limit.this)?;
4635                if limit.percent {
4636                    self.write_space();
4637                    self.write_keyword("PERCENT");
4638                }
4639                // Emit any comments that were captured from before the LIMIT keyword
4640                for comment in &limit.comments {
4641                    self.write(" ");
4642                    self.write_formatted_comment(comment);
4643                }
4644            }
4645        } else {
4646            // Check if FETCH will be converted to LIMIT (used for ordering)
4647            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
4648                !fetch.percent
4649                    && !fetch.with_ties
4650                    && fetch.count.is_some()
4651                    && matches!(
4652                        self.config.dialect,
4653                        Some(DialectType::Spark)
4654                            | Some(DialectType::Hive)
4655                            | Some(DialectType::DuckDB)
4656                            | Some(DialectType::SQLite)
4657                            | Some(DialectType::MySQL)
4658                            | Some(DialectType::BigQuery)
4659                            | Some(DialectType::Databricks)
4660                            | Some(DialectType::StarRocks)
4661                            | Some(DialectType::Doris)
4662                            | Some(DialectType::Athena)
4663                            | Some(DialectType::ClickHouse)
4664                            | Some(DialectType::Redshift)
4665                    )
4666            });
4667
4668            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
4669            if let Some(limit) = &select.limit {
4670                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
4671                if !matches!(self.config.dialect, Some(DialectType::TSQL)) {
4672                    if self.config.pretty {
4673                        self.write_newline();
4674                        self.write_indent();
4675                    } else {
4676                        self.write_space();
4677                    }
4678                    self.write_keyword("LIMIT");
4679                    self.write_space();
4680                    self.write_limit_expr(&limit.this)?;
4681                    if limit.percent {
4682                        self.write_space();
4683                        self.write_keyword("PERCENT");
4684                    }
4685                    // Emit any comments that were captured from before the LIMIT keyword
4686                    for comment in &limit.comments {
4687                        self.write(" ");
4688                        self.write_formatted_comment(comment);
4689                    }
4690                }
4691            }
4692
4693            // Convert TOP to LIMIT for non-TOP dialects
4694            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
4695                if let Some(top) = &select.top {
4696                    if !top.percent && !top.with_ties {
4697                        if self.config.pretty {
4698                            self.write_newline();
4699                            self.write_indent();
4700                        } else {
4701                            self.write_space();
4702                        }
4703                        self.write_keyword("LIMIT");
4704                        self.write_space();
4705                        self.generate_expression(&top.this)?;
4706                    }
4707                }
4708            }
4709
4710            // If FETCH will be converted to LIMIT and there's also OFFSET,
4711            // emit LIMIT from FETCH BEFORE the OFFSET
4712            if fetch_as_limit && select.offset.is_some() {
4713                if let Some(fetch) = &select.fetch {
4714                    if self.config.pretty {
4715                        self.write_newline();
4716                        self.write_indent();
4717                    } else {
4718                        self.write_space();
4719                    }
4720                    self.write_keyword("LIMIT");
4721                    self.write_space();
4722                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4723                }
4724            }
4725
4726            // OFFSET
4727            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
4728            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
4729            if let Some(offset) = &select.offset {
4730                if self.config.pretty {
4731                    self.write_newline();
4732                    self.write_indent();
4733                } else {
4734                    self.write_space();
4735                }
4736                if matches!(self.config.dialect, Some(DialectType::TSQL)) {
4737                    // SQL Server 2012+ OFFSET ... FETCH syntax
4738                    self.write_keyword("OFFSET");
4739                    self.write_space();
4740                    self.write_limit_expr(&offset.this)?;
4741                    self.write_space();
4742                    self.write_keyword("ROWS");
4743                    // If there was a LIMIT, use FETCH NEXT ... ROWS ONLY
4744                    if let Some(limit) = &select.limit {
4745                        self.write_space();
4746                        self.write_keyword("FETCH NEXT");
4747                        self.write_space();
4748                        self.write_limit_expr(&limit.this)?;
4749                        self.write_space();
4750                        self.write_keyword("ROWS ONLY");
4751                    }
4752                } else {
4753                    self.write_keyword("OFFSET");
4754                    self.write_space();
4755                    self.write_limit_expr(&offset.this)?;
4756                    // Output ROWS keyword if it was in the original SQL
4757                    if offset.rows == Some(true) {
4758                        self.write_space();
4759                        self.write_keyword("ROWS");
4760                    }
4761                }
4762            }
4763        }
4764
4765        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
4766        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4767            if let Some(limit_by) = &select.limit_by {
4768                if !limit_by.is_empty() {
4769                    self.write_space();
4770                    self.write_keyword("BY");
4771                    self.write_space();
4772                    for (i, expr) in limit_by.iter().enumerate() {
4773                        if i > 0 {
4774                            self.write(", ");
4775                        }
4776                        self.generate_expression(expr)?;
4777                    }
4778                }
4779            }
4780        }
4781
4782        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
4783        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4784            if let Some(settings) = &select.settings {
4785                if self.config.pretty {
4786                    self.write_newline();
4787                    self.write_indent();
4788                } else {
4789                    self.write_space();
4790                }
4791                self.write_keyword("SETTINGS");
4792                self.write_space();
4793                for (i, expr) in settings.iter().enumerate() {
4794                    if i > 0 {
4795                        self.write(", ");
4796                    }
4797                    self.generate_expression(expr)?;
4798                }
4799            }
4800
4801            if let Some(format_expr) = &select.format {
4802                if self.config.pretty {
4803                    self.write_newline();
4804                    self.write_indent();
4805                } else {
4806                    self.write_space();
4807                }
4808                self.write_keyword("FORMAT");
4809                self.write_space();
4810                self.generate_expression(format_expr)?;
4811            }
4812        }
4813
4814        // FETCH FIRST/NEXT
4815        if let Some(fetch) = &select.fetch {
4816            // Check if we already emitted LIMIT from FETCH before OFFSET
4817            let fetch_already_as_limit = select.offset.is_some()
4818                && !fetch.percent
4819                && !fetch.with_ties
4820                && fetch.count.is_some()
4821                && matches!(
4822                    self.config.dialect,
4823                    Some(DialectType::Spark)
4824                        | Some(DialectType::Hive)
4825                        | Some(DialectType::DuckDB)
4826                        | Some(DialectType::SQLite)
4827                        | Some(DialectType::MySQL)
4828                        | Some(DialectType::BigQuery)
4829                        | Some(DialectType::Databricks)
4830                        | Some(DialectType::StarRocks)
4831                        | Some(DialectType::Doris)
4832                        | Some(DialectType::Athena)
4833                        | Some(DialectType::ClickHouse)
4834                        | Some(DialectType::Redshift)
4835                );
4836
4837            if fetch_already_as_limit {
4838                // Already emitted as LIMIT before OFFSET, skip
4839            } else {
4840                if self.config.pretty {
4841                    self.write_newline();
4842                    self.write_indent();
4843                } else {
4844                    self.write_space();
4845                }
4846
4847                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
4848                let use_limit = !fetch.percent
4849                    && !fetch.with_ties
4850                    && fetch.count.is_some()
4851                    && matches!(
4852                        self.config.dialect,
4853                        Some(DialectType::Spark)
4854                            | Some(DialectType::Hive)
4855                            | Some(DialectType::DuckDB)
4856                            | Some(DialectType::SQLite)
4857                            | Some(DialectType::MySQL)
4858                            | Some(DialectType::BigQuery)
4859                            | Some(DialectType::Databricks)
4860                            | Some(DialectType::StarRocks)
4861                            | Some(DialectType::Doris)
4862                            | Some(DialectType::Athena)
4863                            | Some(DialectType::ClickHouse)
4864                            | Some(DialectType::Redshift)
4865                    );
4866
4867                if use_limit {
4868                    self.write_keyword("LIMIT");
4869                    self.write_space();
4870                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4871                } else {
4872                    self.write_keyword("FETCH");
4873                    self.write_space();
4874                    self.write_keyword(&fetch.direction);
4875                    if let Some(ref count) = fetch.count {
4876                        self.write_space();
4877                        self.generate_expression(count)?;
4878                    }
4879                    if fetch.percent {
4880                        self.write_space();
4881                        self.write_keyword("PERCENT");
4882                    }
4883                    if fetch.rows {
4884                        self.write_space();
4885                        self.write_keyword("ROWS");
4886                    }
4887                    if fetch.with_ties {
4888                        self.write_space();
4889                        self.write_keyword("WITH TIES");
4890                    } else {
4891                        self.write_space();
4892                        self.write_keyword("ONLY");
4893                    }
4894                }
4895            } // close fetch_already_as_limit else
4896        }
4897
4898        // SAMPLE / TABLESAMPLE
4899        if let Some(sample) = &select.sample {
4900            use crate::dialects::DialectType;
4901            if self.config.pretty {
4902                self.write_newline();
4903            } else {
4904                self.write_space();
4905            }
4906
4907            if sample.is_using_sample {
4908                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
4909                self.write_keyword("USING SAMPLE");
4910                self.generate_sample_body(sample)?;
4911            } else {
4912                self.write_keyword("TABLESAMPLE");
4913
4914                // Snowflake defaults to BERNOULLI when no explicit method is given
4915                let snowflake_bernoulli =
4916                    matches!(self.config.dialect, Some(DialectType::Snowflake))
4917                        && !sample.explicit_method;
4918                if snowflake_bernoulli {
4919                    self.write_space();
4920                    self.write_keyword("BERNOULLI");
4921                }
4922
4923                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
4924                if matches!(sample.method, SampleMethod::Bucket) {
4925                    self.write_space();
4926                    self.write("(");
4927                    self.write_keyword("BUCKET");
4928                    self.write_space();
4929                    if let Some(ref num) = sample.bucket_numerator {
4930                        self.generate_expression(num)?;
4931                    }
4932                    self.write_space();
4933                    self.write_keyword("OUT OF");
4934                    self.write_space();
4935                    if let Some(ref denom) = sample.bucket_denominator {
4936                        self.generate_expression(denom)?;
4937                    }
4938                    if let Some(ref field) = sample.bucket_field {
4939                        self.write_space();
4940                        self.write_keyword("ON");
4941                        self.write_space();
4942                        self.generate_expression(field)?;
4943                    }
4944                    self.write(")");
4945                } else if sample.unit_after_size {
4946                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
4947                    if sample.explicit_method && sample.method_before_size {
4948                        self.write_space();
4949                        match sample.method {
4950                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4951                            SampleMethod::System => self.write_keyword("SYSTEM"),
4952                            SampleMethod::Block => self.write_keyword("BLOCK"),
4953                            SampleMethod::Row => self.write_keyword("ROW"),
4954                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4955                            _ => {}
4956                        }
4957                    }
4958                    self.write(" (");
4959                    self.generate_expression(&sample.size)?;
4960                    self.write_space();
4961                    match sample.method {
4962                        SampleMethod::Percent => self.write_keyword("PERCENT"),
4963                        SampleMethod::Row => self.write_keyword("ROWS"),
4964                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
4965                        _ => {
4966                            self.write_keyword("PERCENT");
4967                        }
4968                    }
4969                    self.write(")");
4970                } else {
4971                    // Syntax: TABLESAMPLE METHOD (size)
4972                    self.write_space();
4973                    match sample.method {
4974                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
4975                        SampleMethod::System => self.write_keyword("SYSTEM"),
4976                        SampleMethod::Block => self.write_keyword("BLOCK"),
4977                        SampleMethod::Row => self.write_keyword("ROW"),
4978                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
4979                        SampleMethod::Bucket => {}
4980                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
4981                    }
4982                    self.write(" (");
4983                    self.generate_expression(&sample.size)?;
4984                    if matches!(sample.method, SampleMethod::Percent) {
4985                        self.write_space();
4986                        self.write_keyword("PERCENT");
4987                    }
4988                    self.write(")");
4989                }
4990            }
4991
4992            if let Some(seed) = &sample.seed {
4993                self.write_space();
4994                // Databricks/Spark use REPEATABLE, not SEED
4995                let use_seed = sample.use_seed_keyword
4996                    && !matches!(
4997                        self.config.dialect,
4998                        Some(crate::dialects::DialectType::Databricks)
4999                            | Some(crate::dialects::DialectType::Spark)
5000                    );
5001                if use_seed {
5002                    self.write_keyword("SEED");
5003                } else {
5004                    self.write_keyword("REPEATABLE");
5005                }
5006                self.write(" (");
5007                self.generate_expression(seed)?;
5008                self.write(")");
5009            }
5010        }
5011
5012        // FOR UPDATE/SHARE locks
5013        // Skip locking clauses for dialects that don't support them
5014        if self.config.locking_reads_supported {
5015            for lock in &select.locks {
5016                if self.config.pretty {
5017                    self.write_newline();
5018                    self.write_indent();
5019                } else {
5020                    self.write_space();
5021                }
5022                self.generate_lock(lock)?;
5023            }
5024        }
5025
5026        // FOR XML clause (T-SQL)
5027        if !select.for_xml.is_empty() {
5028            if self.config.pretty {
5029                self.write_newline();
5030                self.write_indent();
5031            } else {
5032                self.write_space();
5033            }
5034            self.write_keyword("FOR XML");
5035            for (i, opt) in select.for_xml.iter().enumerate() {
5036                if self.config.pretty {
5037                    if i > 0 {
5038                        self.write(",");
5039                    }
5040                    self.write_newline();
5041                    self.write_indent();
5042                    self.write("  "); // extra indent for options
5043                } else {
5044                    if i > 0 {
5045                        self.write(",");
5046                    }
5047                    self.write_space();
5048                }
5049                self.generate_for_xml_option(opt)?;
5050            }
5051        }
5052
5053        // FOR JSON clause (T-SQL)
5054        if !select.for_json.is_empty() {
5055            if self.config.pretty {
5056                self.write_newline();
5057                self.write_indent();
5058            } else {
5059                self.write_space();
5060            }
5061            self.write_keyword("FOR JSON");
5062            for (i, opt) in select.for_json.iter().enumerate() {
5063                if self.config.pretty {
5064                    if i > 0 {
5065                        self.write(",");
5066                    }
5067                    self.write_newline();
5068                    self.write_indent();
5069                    self.write("  "); // extra indent for options
5070                } else {
5071                    if i > 0 {
5072                        self.write(",");
5073                    }
5074                    self.write_space();
5075                }
5076                self.generate_for_xml_option(opt)?;
5077            }
5078        }
5079
5080        // TSQL: OPTION clause
5081        if let Some(ref option) = select.option {
5082            if matches!(
5083                self.config.dialect,
5084                Some(crate::dialects::DialectType::TSQL)
5085                    | Some(crate::dialects::DialectType::Fabric)
5086            ) {
5087                self.write_space();
5088                self.write(option);
5089            }
5090        }
5091
5092        Ok(())
5093    }
5094
5095    /// Generate a single FOR XML option
5096    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
5097        match opt {
5098            Expression::QueryOption(qo) => {
5099                // Extract the option name from Var
5100                if let Expression::Var(var) = &*qo.this {
5101                    self.write(&var.this);
5102                } else {
5103                    self.generate_expression(&qo.this)?;
5104                }
5105                // If there's an expression (like PATH('element')), output it in parens
5106                if let Some(expr) = &qo.expression {
5107                    self.write("(");
5108                    self.generate_expression(expr)?;
5109                    self.write(")");
5110                }
5111            }
5112            _ => {
5113                self.generate_expression(opt)?;
5114            }
5115        }
5116        Ok(())
5117    }
5118
5119    fn generate_with(&mut self, with: &With) -> Result<()> {
5120        use crate::dialects::DialectType;
5121
5122        // Output leading comments before WITH
5123        for comment in &with.leading_comments {
5124            self.write_formatted_comment(comment);
5125            self.write(" ");
5126        }
5127        self.write_keyword("WITH");
5128        if with.recursive && self.config.cte_recursive_keyword_required {
5129            self.write_space();
5130            self.write_keyword("RECURSIVE");
5131        }
5132        self.write_space();
5133
5134        // BigQuery doesn't support column aliases in CTE definitions
5135        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
5136
5137        for (i, cte) in with.ctes.iter().enumerate() {
5138            if i > 0 {
5139                self.write(",");
5140                if self.config.pretty {
5141                    self.write_space();
5142                } else {
5143                    self.write(" ");
5144                }
5145            }
5146            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
5147                self.generate_expression(&cte.this)?;
5148                self.write_space();
5149                self.write_keyword("AS");
5150                self.write_space();
5151                self.generate_identifier(&cte.alias)?;
5152                continue;
5153            }
5154            self.generate_identifier(&cte.alias)?;
5155            // Output CTE comments after alias name, before AS
5156            for comment in &cte.comments {
5157                self.write_space();
5158                self.write_formatted_comment(comment);
5159            }
5160            if !cte.columns.is_empty() && !skip_cte_columns {
5161                self.write("(");
5162                for (j, col) in cte.columns.iter().enumerate() {
5163                    if j > 0 {
5164                        self.write(", ");
5165                    }
5166                    self.generate_identifier(col)?;
5167                }
5168                self.write(")");
5169            }
5170            // USING KEY (columns) for DuckDB recursive CTEs
5171            if !cte.key_expressions.is_empty() {
5172                self.write_space();
5173                self.write_keyword("USING KEY");
5174                self.write(" (");
5175                for (i, key) in cte.key_expressions.iter().enumerate() {
5176                    if i > 0 {
5177                        self.write(", ");
5178                    }
5179                    self.generate_identifier(key)?;
5180                }
5181                self.write(")");
5182            }
5183            self.write_space();
5184            self.write_keyword("AS");
5185            // MATERIALIZED / NOT MATERIALIZED
5186            if let Some(materialized) = cte.materialized {
5187                self.write_space();
5188                if materialized {
5189                    self.write_keyword("MATERIALIZED");
5190                } else {
5191                    self.write_keyword("NOT MATERIALIZED");
5192                }
5193            }
5194            self.write(" (");
5195            if self.config.pretty {
5196                self.write_newline();
5197                self.indent_level += 1;
5198                self.write_indent();
5199            }
5200            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
5201            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
5202            let wrap_values_in_select = matches!(
5203                self.config.dialect,
5204                Some(DialectType::Spark) | Some(DialectType::Databricks)
5205            ) && matches!(&cte.this, Expression::Values(_));
5206
5207            if wrap_values_in_select {
5208                self.write_keyword("SELECT");
5209                self.write(" * ");
5210                self.write_keyword("FROM");
5211                self.write_space();
5212            }
5213            self.generate_expression(&cte.this)?;
5214            if self.config.pretty {
5215                self.write_newline();
5216                self.indent_level -= 1;
5217                self.write_indent();
5218            }
5219            self.write(")");
5220        }
5221
5222        // Generate SEARCH/CYCLE clause if present
5223        if let Some(search) = &with.search {
5224            self.write_space();
5225            self.generate_expression(search)?;
5226        }
5227
5228        Ok(())
5229    }
5230
5231    /// Generate joins with proper nesting structure for pretty printing.
5232    /// Deferred-condition joins "own" the non-deferred joins that follow them
5233    /// within the same nesting_group.
5234    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
5235        let mut i = 0;
5236        while i < joins.len() {
5237            if joins[i].deferred_condition {
5238                let parent_group = joins[i].nesting_group;
5239
5240                // This join owns the following non-deferred joins in the same nesting_group
5241                // First output the join keyword and table (without condition)
5242                self.generate_join_without_condition(&joins[i])?;
5243
5244                // Find the range of child joins: same nesting_group and not deferred
5245                let child_start = i + 1;
5246                let mut child_end = child_start;
5247                while child_end < joins.len()
5248                    && !joins[child_end].deferred_condition
5249                    && joins[child_end].nesting_group == parent_group
5250                {
5251                    child_end += 1;
5252                }
5253
5254                // Output child joins with extra indentation
5255                if child_start < child_end {
5256                    self.indent_level += 1;
5257                    for j in child_start..child_end {
5258                        self.generate_join(&joins[j])?;
5259                    }
5260                    self.indent_level -= 1;
5261                }
5262
5263                // Output the deferred condition at the parent level
5264                self.generate_join_condition(&joins[i])?;
5265
5266                i = child_end;
5267            } else {
5268                // Regular join (no nesting)
5269                self.generate_join(&joins[i])?;
5270                i += 1;
5271            }
5272        }
5273        Ok(())
5274    }
5275
5276    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5277    /// Used for deferred-condition joins where the condition is output after child joins.
5278    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5279        // Save and temporarily clear the condition to prevent generate_join from outputting it
5280        // We achieve this by creating a modified copy
5281        let mut join_copy = join.clone();
5282        join_copy.on = None;
5283        join_copy.using = Vec::new();
5284        join_copy.deferred_condition = false;
5285        self.generate_join(&join_copy)
5286    }
5287
5288    fn generate_join(&mut self, join: &Join) -> Result<()> {
5289        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5290        if join.kind == JoinKind::Implicit {
5291            self.write(",");
5292            if self.config.pretty {
5293                self.write_newline();
5294                self.write_indent();
5295            } else {
5296                self.write_space();
5297            }
5298            self.generate_expression(&join.this)?;
5299            return Ok(());
5300        }
5301
5302        if self.config.pretty {
5303            self.write_newline();
5304            self.write_indent();
5305        } else {
5306            self.write_space();
5307        }
5308
5309        // Helper: format hint suffix (e.g., " LOOP" or "")
5310        // Only include join hints for dialects that support them
5311        let hint_str = if self.config.join_hints {
5312            join.join_hint
5313                .as_ref()
5314                .map(|h| format!(" {}", h))
5315                .unwrap_or_default()
5316        } else {
5317            String::new()
5318        };
5319
5320        let clickhouse_join_keyword =
5321            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5322                if let Some(hint) = &join.join_hint {
5323                    let mut global = false;
5324                    let mut strictness: Option<&'static str> = None;
5325                    for part in hint.split_whitespace() {
5326                        if part.eq_ignore_ascii_case("GLOBAL") {
5327                            global = true;
5328                        } else if part.eq_ignore_ascii_case("ANY") {
5329                            strictness = Some("ANY");
5330                        } else if part.eq_ignore_ascii_case("ASOF") {
5331                            strictness = Some("ASOF");
5332                        } else if part.eq_ignore_ascii_case("SEMI") {
5333                            strictness = Some("SEMI");
5334                        } else if part.eq_ignore_ascii_case("ANTI") {
5335                            strictness = Some("ANTI");
5336                        }
5337                    }
5338
5339                    if global || strictness.is_some() {
5340                        let join_type = match join.kind {
5341                            JoinKind::Left => {
5342                                if join.use_outer_keyword {
5343                                    "LEFT OUTER"
5344                                } else if join.use_inner_keyword {
5345                                    "LEFT INNER"
5346                                } else {
5347                                    "LEFT"
5348                                }
5349                            }
5350                            JoinKind::Right => {
5351                                if join.use_outer_keyword {
5352                                    "RIGHT OUTER"
5353                                } else if join.use_inner_keyword {
5354                                    "RIGHT INNER"
5355                                } else {
5356                                    "RIGHT"
5357                                }
5358                            }
5359                            JoinKind::Full => {
5360                                if join.use_outer_keyword {
5361                                    "FULL OUTER"
5362                                } else {
5363                                    "FULL"
5364                                }
5365                            }
5366                            JoinKind::Inner => {
5367                                if join.use_inner_keyword {
5368                                    "INNER"
5369                                } else {
5370                                    ""
5371                                }
5372                            }
5373                            _ => "",
5374                        };
5375
5376                        let mut parts = Vec::new();
5377                        if global {
5378                            parts.push("GLOBAL");
5379                        }
5380                        if !join_type.is_empty() {
5381                            parts.push(join_type);
5382                        }
5383                        if let Some(strict) = strictness {
5384                            parts.push(strict);
5385                        }
5386                        parts.push("JOIN");
5387                        Some(parts.join(" "))
5388                    } else {
5389                        None
5390                    }
5391                } else {
5392                    None
5393                }
5394            } else {
5395                None
5396            };
5397
5398        // Output any comments associated with this join
5399        // In pretty mode, comments go on their own line before the join keyword
5400        // In non-pretty mode, comments go inline before the join keyword
5401        if !join.comments.is_empty() {
5402            if self.config.pretty {
5403                // In pretty mode, go back before the newline+indent we just wrote
5404                // and output comments on their own lines
5405                // We need to output comments BEFORE the join keyword on separate lines
5406                // Trim the trailing newline+indent we already wrote
5407                let trimmed = self.output.trim_end().len();
5408                self.output.truncate(trimmed);
5409                for comment in &join.comments {
5410                    self.write_newline();
5411                    self.write_indent();
5412                    self.write_formatted_comment(comment);
5413                }
5414                self.write_newline();
5415                self.write_indent();
5416            } else {
5417                for comment in &join.comments {
5418                    self.write_formatted_comment(comment);
5419                    self.write_space();
5420                }
5421            }
5422        }
5423
5424        let directed_str = if join.directed { " DIRECTED" } else { "" };
5425
5426        if let Some(keyword) = clickhouse_join_keyword {
5427            self.write_keyword(&keyword);
5428        } else {
5429            match join.kind {
5430                JoinKind::Inner => {
5431                    if join.use_inner_keyword {
5432                        if hint_str.is_empty() && directed_str.is_empty() {
5433                            self.write_keyword("INNER JOIN");
5434                        } else {
5435                            self.write_keyword("INNER");
5436                            if !hint_str.is_empty() {
5437                                self.write_keyword(&hint_str);
5438                            }
5439                            if !directed_str.is_empty() {
5440                                self.write_keyword(directed_str);
5441                            }
5442                            self.write_keyword(" JOIN");
5443                        }
5444                    } else {
5445                        if !hint_str.is_empty() {
5446                            self.write_keyword(hint_str.trim());
5447                            self.write_keyword(" ");
5448                        }
5449                        if !directed_str.is_empty() {
5450                            self.write_keyword("DIRECTED ");
5451                        }
5452                        self.write_keyword("JOIN");
5453                    }
5454                }
5455                JoinKind::Left => {
5456                    if join.use_outer_keyword {
5457                        if hint_str.is_empty() && directed_str.is_empty() {
5458                            self.write_keyword("LEFT OUTER JOIN");
5459                        } else {
5460                            self.write_keyword("LEFT OUTER");
5461                            if !hint_str.is_empty() {
5462                                self.write_keyword(&hint_str);
5463                            }
5464                            if !directed_str.is_empty() {
5465                                self.write_keyword(directed_str);
5466                            }
5467                            self.write_keyword(" JOIN");
5468                        }
5469                    } else if join.use_inner_keyword {
5470                        if hint_str.is_empty() && directed_str.is_empty() {
5471                            self.write_keyword("LEFT INNER JOIN");
5472                        } else {
5473                            self.write_keyword("LEFT INNER");
5474                            if !hint_str.is_empty() {
5475                                self.write_keyword(&hint_str);
5476                            }
5477                            if !directed_str.is_empty() {
5478                                self.write_keyword(directed_str);
5479                            }
5480                            self.write_keyword(" JOIN");
5481                        }
5482                    } else {
5483                        if hint_str.is_empty() && directed_str.is_empty() {
5484                            self.write_keyword("LEFT JOIN");
5485                        } else {
5486                            self.write_keyword("LEFT");
5487                            if !hint_str.is_empty() {
5488                                self.write_keyword(&hint_str);
5489                            }
5490                            if !directed_str.is_empty() {
5491                                self.write_keyword(directed_str);
5492                            }
5493                            self.write_keyword(" JOIN");
5494                        }
5495                    }
5496                }
5497                JoinKind::Right => {
5498                    if join.use_outer_keyword {
5499                        if hint_str.is_empty() && directed_str.is_empty() {
5500                            self.write_keyword("RIGHT OUTER JOIN");
5501                        } else {
5502                            self.write_keyword("RIGHT OUTER");
5503                            if !hint_str.is_empty() {
5504                                self.write_keyword(&hint_str);
5505                            }
5506                            if !directed_str.is_empty() {
5507                                self.write_keyword(directed_str);
5508                            }
5509                            self.write_keyword(" JOIN");
5510                        }
5511                    } else if join.use_inner_keyword {
5512                        if hint_str.is_empty() && directed_str.is_empty() {
5513                            self.write_keyword("RIGHT INNER JOIN");
5514                        } else {
5515                            self.write_keyword("RIGHT INNER");
5516                            if !hint_str.is_empty() {
5517                                self.write_keyword(&hint_str);
5518                            }
5519                            if !directed_str.is_empty() {
5520                                self.write_keyword(directed_str);
5521                            }
5522                            self.write_keyword(" JOIN");
5523                        }
5524                    } else {
5525                        if hint_str.is_empty() && directed_str.is_empty() {
5526                            self.write_keyword("RIGHT JOIN");
5527                        } else {
5528                            self.write_keyword("RIGHT");
5529                            if !hint_str.is_empty() {
5530                                self.write_keyword(&hint_str);
5531                            }
5532                            if !directed_str.is_empty() {
5533                                self.write_keyword(directed_str);
5534                            }
5535                            self.write_keyword(" JOIN");
5536                        }
5537                    }
5538                }
5539                JoinKind::Full => {
5540                    if join.use_outer_keyword {
5541                        if hint_str.is_empty() && directed_str.is_empty() {
5542                            self.write_keyword("FULL OUTER JOIN");
5543                        } else {
5544                            self.write_keyword("FULL OUTER");
5545                            if !hint_str.is_empty() {
5546                                self.write_keyword(&hint_str);
5547                            }
5548                            if !directed_str.is_empty() {
5549                                self.write_keyword(directed_str);
5550                            }
5551                            self.write_keyword(" JOIN");
5552                        }
5553                    } else {
5554                        if hint_str.is_empty() && directed_str.is_empty() {
5555                            self.write_keyword("FULL JOIN");
5556                        } else {
5557                            self.write_keyword("FULL");
5558                            if !hint_str.is_empty() {
5559                                self.write_keyword(&hint_str);
5560                            }
5561                            if !directed_str.is_empty() {
5562                                self.write_keyword(directed_str);
5563                            }
5564                            self.write_keyword(" JOIN");
5565                        }
5566                    }
5567                }
5568                JoinKind::Outer => {
5569                    if directed_str.is_empty() {
5570                        self.write_keyword("OUTER JOIN");
5571                    } else {
5572                        self.write_keyword("OUTER");
5573                        self.write_keyword(directed_str);
5574                        self.write_keyword(" JOIN");
5575                    }
5576                }
5577                JoinKind::Cross => {
5578                    if directed_str.is_empty() {
5579                        self.write_keyword("CROSS JOIN");
5580                    } else {
5581                        self.write_keyword("CROSS");
5582                        self.write_keyword(directed_str);
5583                        self.write_keyword(" JOIN");
5584                    }
5585                }
5586                JoinKind::Natural => {
5587                    if join.use_inner_keyword {
5588                        if directed_str.is_empty() {
5589                            self.write_keyword("NATURAL INNER JOIN");
5590                        } else {
5591                            self.write_keyword("NATURAL INNER");
5592                            self.write_keyword(directed_str);
5593                            self.write_keyword(" JOIN");
5594                        }
5595                    } else {
5596                        if directed_str.is_empty() {
5597                            self.write_keyword("NATURAL JOIN");
5598                        } else {
5599                            self.write_keyword("NATURAL");
5600                            self.write_keyword(directed_str);
5601                            self.write_keyword(" JOIN");
5602                        }
5603                    }
5604                }
5605                JoinKind::NaturalLeft => {
5606                    if join.use_outer_keyword {
5607                        if directed_str.is_empty() {
5608                            self.write_keyword("NATURAL LEFT OUTER JOIN");
5609                        } else {
5610                            self.write_keyword("NATURAL LEFT OUTER");
5611                            self.write_keyword(directed_str);
5612                            self.write_keyword(" JOIN");
5613                        }
5614                    } else {
5615                        if directed_str.is_empty() {
5616                            self.write_keyword("NATURAL LEFT JOIN");
5617                        } else {
5618                            self.write_keyword("NATURAL LEFT");
5619                            self.write_keyword(directed_str);
5620                            self.write_keyword(" JOIN");
5621                        }
5622                    }
5623                }
5624                JoinKind::NaturalRight => {
5625                    if join.use_outer_keyword {
5626                        if directed_str.is_empty() {
5627                            self.write_keyword("NATURAL RIGHT OUTER JOIN");
5628                        } else {
5629                            self.write_keyword("NATURAL RIGHT OUTER");
5630                            self.write_keyword(directed_str);
5631                            self.write_keyword(" JOIN");
5632                        }
5633                    } else {
5634                        if directed_str.is_empty() {
5635                            self.write_keyword("NATURAL RIGHT JOIN");
5636                        } else {
5637                            self.write_keyword("NATURAL RIGHT");
5638                            self.write_keyword(directed_str);
5639                            self.write_keyword(" JOIN");
5640                        }
5641                    }
5642                }
5643                JoinKind::NaturalFull => {
5644                    if join.use_outer_keyword {
5645                        if directed_str.is_empty() {
5646                            self.write_keyword("NATURAL FULL OUTER JOIN");
5647                        } else {
5648                            self.write_keyword("NATURAL FULL OUTER");
5649                            self.write_keyword(directed_str);
5650                            self.write_keyword(" JOIN");
5651                        }
5652                    } else {
5653                        if directed_str.is_empty() {
5654                            self.write_keyword("NATURAL FULL JOIN");
5655                        } else {
5656                            self.write_keyword("NATURAL FULL");
5657                            self.write_keyword(directed_str);
5658                            self.write_keyword(" JOIN");
5659                        }
5660                    }
5661                }
5662                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
5663                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
5664                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
5665                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
5666                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
5667                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
5668                JoinKind::CrossApply => {
5669                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL dialects
5670                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5671                        self.write_keyword("CROSS APPLY");
5672                    } else {
5673                        self.write_keyword("INNER JOIN LATERAL");
5674                    }
5675                }
5676                JoinKind::OuterApply => {
5677                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL dialects
5678                    if matches!(self.config.dialect, Some(DialectType::TSQL) | None) {
5679                        self.write_keyword("OUTER APPLY");
5680                    } else {
5681                        self.write_keyword("LEFT JOIN LATERAL");
5682                    }
5683                }
5684                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
5685                JoinKind::AsOfLeft => {
5686                    if join.use_outer_keyword {
5687                        self.write_keyword("ASOF LEFT OUTER JOIN");
5688                    } else {
5689                        self.write_keyword("ASOF LEFT JOIN");
5690                    }
5691                }
5692                JoinKind::AsOfRight => {
5693                    if join.use_outer_keyword {
5694                        self.write_keyword("ASOF RIGHT OUTER JOIN");
5695                    } else {
5696                        self.write_keyword("ASOF RIGHT JOIN");
5697                    }
5698                }
5699                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
5700                JoinKind::LeftLateral => {
5701                    if join.use_outer_keyword {
5702                        self.write_keyword("LEFT OUTER LATERAL JOIN");
5703                    } else {
5704                        self.write_keyword("LEFT LATERAL JOIN");
5705                    }
5706                }
5707                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
5708                JoinKind::Implicit => {
5709                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
5710                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
5711                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
5712                    use crate::dialects::DialectType;
5713                    let is_cj_dialect = matches!(
5714                        self.config.dialect,
5715                        Some(DialectType::BigQuery)
5716                            | Some(DialectType::Hive)
5717                            | Some(DialectType::Spark)
5718                            | Some(DialectType::Databricks)
5719                    );
5720                    let source_is_same = self.config.source_dialect.is_some()
5721                        && self.config.source_dialect == self.config.dialect;
5722                    let source_is_cj = matches!(
5723                        self.config.source_dialect,
5724                        Some(DialectType::BigQuery)
5725                            | Some(DialectType::Hive)
5726                            | Some(DialectType::Spark)
5727                            | Some(DialectType::Databricks)
5728                    );
5729                    if is_cj_dialect
5730                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
5731                    {
5732                        self.write_keyword("CROSS JOIN");
5733                    } else {
5734                        // Implicit join uses comma: FROM a, b
5735                        // We already wrote a space before the match, so replace with comma
5736                        // by removing trailing space and writing ", "
5737                        self.output.truncate(self.output.trim_end().len());
5738                        self.write(",");
5739                    }
5740                }
5741                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
5742                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
5743                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
5744                JoinKind::Positional => self.write_keyword("POSITIONAL JOIN"),
5745            }
5746        }
5747
5748        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
5749        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
5750            self.write_space();
5751            match &join.this {
5752                Expression::Tuple(t) => {
5753                    for (i, item) in t.expressions.iter().enumerate() {
5754                        if i > 0 {
5755                            self.write(", ");
5756                        }
5757                        self.generate_expression(item)?;
5758                    }
5759                }
5760                other => {
5761                    self.generate_expression(other)?;
5762                }
5763            }
5764        } else {
5765            self.write_space();
5766            self.generate_expression(&join.this)?;
5767        }
5768
5769        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
5770        if !join.deferred_condition {
5771            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
5772            if let Some(match_cond) = &join.match_condition {
5773                self.write_space();
5774                self.write_keyword("MATCH_CONDITION");
5775                self.write(" (");
5776                self.generate_expression(match_cond)?;
5777                self.write(")");
5778            }
5779
5780            if let Some(on) = &join.on {
5781                if self.config.pretty {
5782                    self.write_newline();
5783                    self.indent_level += 1;
5784                    self.write_indent();
5785                    self.write_keyword("ON");
5786                    self.write_space();
5787                    self.generate_join_on_condition(on)?;
5788                    self.indent_level -= 1;
5789                } else {
5790                    self.write_space();
5791                    self.write_keyword("ON");
5792                    self.write_space();
5793                    self.generate_expression(on)?;
5794                }
5795            }
5796
5797            if !join.using.is_empty() {
5798                if self.config.pretty {
5799                    self.write_newline();
5800                    self.indent_level += 1;
5801                    self.write_indent();
5802                    self.write_keyword("USING");
5803                    self.write(" (");
5804                    for (i, col) in join.using.iter().enumerate() {
5805                        if i > 0 {
5806                            self.write(", ");
5807                        }
5808                        self.generate_identifier(col)?;
5809                    }
5810                    self.write(")");
5811                    self.indent_level -= 1;
5812                } else {
5813                    self.write_space();
5814                    self.write_keyword("USING");
5815                    self.write(" (");
5816                    for (i, col) in join.using.iter().enumerate() {
5817                        if i > 0 {
5818                            self.write(", ");
5819                        }
5820                        self.generate_identifier(col)?;
5821                    }
5822                    self.write(")");
5823                }
5824            }
5825        }
5826
5827        // Generate PIVOT/UNPIVOT expressions that follow this join
5828        for pivot in &join.pivots {
5829            self.write_space();
5830            self.generate_expression(pivot)?;
5831        }
5832
5833        Ok(())
5834    }
5835
5836    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
5837    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
5838        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
5839        if let Some(match_cond) = &join.match_condition {
5840            self.write_space();
5841            self.write_keyword("MATCH_CONDITION");
5842            self.write(" (");
5843            self.generate_expression(match_cond)?;
5844            self.write(")");
5845        }
5846
5847        if let Some(on) = &join.on {
5848            if self.config.pretty {
5849                self.write_newline();
5850                self.indent_level += 1;
5851                self.write_indent();
5852                self.write_keyword("ON");
5853                self.write_space();
5854                // In pretty mode, split AND conditions onto separate lines
5855                self.generate_join_on_condition(on)?;
5856                self.indent_level -= 1;
5857            } else {
5858                self.write_space();
5859                self.write_keyword("ON");
5860                self.write_space();
5861                self.generate_expression(on)?;
5862            }
5863        }
5864
5865        if !join.using.is_empty() {
5866            if self.config.pretty {
5867                self.write_newline();
5868                self.indent_level += 1;
5869                self.write_indent();
5870                self.write_keyword("USING");
5871                self.write(" (");
5872                for (i, col) in join.using.iter().enumerate() {
5873                    if i > 0 {
5874                        self.write(", ");
5875                    }
5876                    self.generate_identifier(col)?;
5877                }
5878                self.write(")");
5879                self.indent_level -= 1;
5880            } else {
5881                self.write_space();
5882                self.write_keyword("USING");
5883                self.write(" (");
5884                for (i, col) in join.using.iter().enumerate() {
5885                    if i > 0 {
5886                        self.write(", ");
5887                    }
5888                    self.generate_identifier(col)?;
5889                }
5890                self.write(")");
5891            }
5892        }
5893
5894        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
5895        for pivot in &join.pivots {
5896            self.write_space();
5897            self.generate_expression(pivot)?;
5898        }
5899
5900        Ok(())
5901    }
5902
5903    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
5904    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
5905        if let Expression::And(and_op) = expr {
5906            if let Some(conditions) = self.flatten_connector_terms(and_op, ConnectorOperator::And) {
5907                self.generate_expression(conditions[0])?;
5908                for condition in conditions.iter().skip(1) {
5909                    self.write_newline();
5910                    self.write_indent();
5911                    self.write_keyword("AND");
5912                    self.write_space();
5913                    self.generate_expression(condition)?;
5914                }
5915                return Ok(());
5916            }
5917        }
5918
5919        self.generate_expression(expr)
5920    }
5921
5922    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
5923        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
5924        self.write("(");
5925        self.generate_expression(&jt.left)?;
5926
5927        // Generate all joins
5928        for join in &jt.joins {
5929            self.generate_join(join)?;
5930        }
5931
5932        // Generate LATERAL VIEW clauses (Hive/Spark)
5933        for (lv_idx, lv) in jt.lateral_views.iter().enumerate() {
5934            self.generate_lateral_view(lv, lv_idx)?;
5935        }
5936
5937        self.write(")");
5938
5939        // Alias
5940        if let Some(alias) = &jt.alias {
5941            self.write_space();
5942            self.write_keyword("AS");
5943            self.write_space();
5944            self.generate_identifier(alias)?;
5945        }
5946
5947        Ok(())
5948    }
5949
5950    fn generate_lateral_view(&mut self, lv: &LateralView, lv_index: usize) -> Result<()> {
5951        use crate::dialects::DialectType;
5952
5953        if self.config.pretty {
5954            self.write_newline();
5955            self.write_indent();
5956        } else {
5957            self.write_space();
5958        }
5959
5960        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
5961        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
5962        let use_lateral_join = matches!(
5963            self.config.dialect,
5964            Some(DialectType::PostgreSQL)
5965                | Some(DialectType::DuckDB)
5966                | Some(DialectType::Snowflake)
5967                | Some(DialectType::TSQL)
5968                | Some(DialectType::Presto)
5969                | Some(DialectType::Trino)
5970                | Some(DialectType::Athena)
5971        );
5972
5973        // Check if target dialect should use UNNEST instead of EXPLODE
5974        let use_unnest = matches!(
5975            self.config.dialect,
5976            Some(DialectType::DuckDB)
5977                | Some(DialectType::Presto)
5978                | Some(DialectType::Trino)
5979                | Some(DialectType::Athena)
5980        );
5981
5982        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
5983        let (is_posexplode, is_inline, func_args) = match &lv.this {
5984            Expression::Explode(uf) => {
5985                // Expression::Explode is the dedicated EXPLODE expression type
5986                (false, false, vec![uf.this.clone()])
5987            }
5988            Expression::Unnest(uf) => {
5989                let mut args = vec![uf.this.clone()];
5990                args.extend(uf.expressions.clone());
5991                (false, false, args)
5992            }
5993            Expression::Function(func) => {
5994                if func.name.eq_ignore_ascii_case("POSEXPLODE")
5995                    || func.name.eq_ignore_ascii_case("POSEXPLODE_OUTER")
5996                {
5997                    (true, false, func.args.clone())
5998                } else if func.name.eq_ignore_ascii_case("INLINE") {
5999                    (false, true, func.args.clone())
6000                } else if func.name.eq_ignore_ascii_case("EXPLODE")
6001                    || func.name.eq_ignore_ascii_case("EXPLODE_OUTER")
6002                {
6003                    (false, false, func.args.clone())
6004                } else {
6005                    (false, false, vec![])
6006                }
6007            }
6008            _ => (false, false, vec![]),
6009        };
6010
6011        if use_lateral_join {
6012            // Convert to CROSS JOIN for PostgreSQL-like dialects
6013            if lv.outer {
6014                self.write_keyword("LEFT JOIN LATERAL");
6015            } else {
6016                self.write_keyword("CROSS JOIN");
6017            }
6018            self.write_space();
6019
6020            if use_unnest && !func_args.is_empty() {
6021                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
6022                // For DuckDB, also convert ARRAY(y) -> [y]
6023                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6024                    // DuckDB: ARRAY(y) -> [y]
6025                    func_args
6026                        .iter()
6027                        .map(|a| {
6028                            if let Expression::Function(ref f) = a {
6029                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() == 1 {
6030                                    return Expression::ArrayFunc(Box::new(
6031                                        crate::expressions::ArrayConstructor {
6032                                            expressions: f.args.clone(),
6033                                            bracket_notation: true,
6034                                            use_list_keyword: false,
6035                                        },
6036                                    ));
6037                                }
6038                            }
6039                            a.clone()
6040                        })
6041                        .collect::<Vec<_>>()
6042                } else if matches!(
6043                    self.config.dialect,
6044                    Some(DialectType::Presto)
6045                        | Some(DialectType::Trino)
6046                        | Some(DialectType::Athena)
6047                ) {
6048                    // Presto: ARRAY(y) -> ARRAY[y]
6049                    func_args
6050                        .iter()
6051                        .map(|a| {
6052                            if let Expression::Function(ref f) = a {
6053                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() >= 1 {
6054                                    return Expression::ArrayFunc(Box::new(
6055                                        crate::expressions::ArrayConstructor {
6056                                            expressions: f.args.clone(),
6057                                            bracket_notation: true,
6058                                            use_list_keyword: false,
6059                                        },
6060                                    ));
6061                                }
6062                            }
6063                            a.clone()
6064                        })
6065                        .collect::<Vec<_>>()
6066                } else {
6067                    func_args
6068                };
6069
6070                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
6071                if is_posexplode {
6072                    self.write_keyword("LATERAL");
6073                    self.write(" (");
6074                    self.write_keyword("SELECT");
6075                    self.write_space();
6076
6077                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
6078                    // column_aliases[0] is the position column, rest are data columns
6079                    let pos_alias = if !lv.column_aliases.is_empty() {
6080                        lv.column_aliases[0].clone()
6081                    } else {
6082                        Identifier::new("pos")
6083                    };
6084                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
6085                        lv.column_aliases[1..].to_vec()
6086                    } else {
6087                        vec![Identifier::new("col")]
6088                    };
6089
6090                    // pos - 1 AS pos
6091                    self.generate_identifier(&pos_alias)?;
6092                    self.write(" - 1");
6093                    self.write_space();
6094                    self.write_keyword("AS");
6095                    self.write_space();
6096                    self.generate_identifier(&pos_alias)?;
6097
6098                    // , col [, key, value ...]
6099                    for data_col in &data_aliases {
6100                        self.write(", ");
6101                        self.generate_identifier(data_col)?;
6102                    }
6103
6104                    self.write_space();
6105                    self.write_keyword("FROM");
6106                    self.write_space();
6107                    self.write_keyword("UNNEST");
6108                    self.write("(");
6109                    for (i, arg) in unnest_args.iter().enumerate() {
6110                        if i > 0 {
6111                            self.write(", ");
6112                        }
6113                        self.generate_expression(arg)?;
6114                    }
6115                    self.write(")");
6116                    self.write_space();
6117                    self.write_keyword("WITH ORDINALITY");
6118                    self.write_space();
6119                    self.write_keyword("AS");
6120                    self.write_space();
6121
6122                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
6123                    let table_alias_ident = lv
6124                        .table_alias
6125                        .clone()
6126                        .unwrap_or_else(|| Identifier::new("t"));
6127                    self.generate_identifier(&table_alias_ident)?;
6128                    self.write("(");
6129                    for (i, data_col) in data_aliases.iter().enumerate() {
6130                        if i > 0 {
6131                            self.write(", ");
6132                        }
6133                        self.generate_identifier(data_col)?;
6134                    }
6135                    self.write(", ");
6136                    self.generate_identifier(&pos_alias)?;
6137                    self.write("))");
6138                } else if is_inline && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6139                    // INLINE -> LATERAL (SELECT UNNEST(arg, max_depth => 2)) AS alias
6140                    self.write_keyword("LATERAL");
6141                    self.write(" (");
6142                    self.write_keyword("SELECT");
6143                    self.write_space();
6144                    self.write_keyword("UNNEST");
6145                    self.write("(");
6146                    for (i, arg) in unnest_args.iter().enumerate() {
6147                        if i > 0 {
6148                            self.write(", ");
6149                        }
6150                        self.generate_expression(arg)?;
6151                    }
6152                    self.write(", ");
6153                    self.write_keyword("max_depth");
6154                    self.write(" => 2))");
6155
6156                    // Add table and column aliases
6157                    if let Some(alias) = &lv.table_alias {
6158                        self.write_space();
6159                        self.write_keyword("AS");
6160                        self.write_space();
6161                        self.generate_identifier(alias)?;
6162                        if !lv.column_aliases.is_empty() {
6163                            self.write("(");
6164                            for (i, col) in lv.column_aliases.iter().enumerate() {
6165                                if i > 0 {
6166                                    self.write(", ");
6167                                }
6168                                self.generate_identifier(col)?;
6169                            }
6170                            self.write(")");
6171                        }
6172                    } else if !lv.column_aliases.is_empty() {
6173                        // Auto-generate alias like _u_N
6174                        self.write_space();
6175                        self.write_keyword("AS");
6176                        self.write_space();
6177                        self.write(&format!("_u_{}", lv_index));
6178                        self.write("(");
6179                        for (i, col) in lv.column_aliases.iter().enumerate() {
6180                            if i > 0 {
6181                                self.write(", ");
6182                            }
6183                            self.generate_identifier(col)?;
6184                        }
6185                        self.write(")");
6186                    }
6187                } else {
6188                    self.write_keyword("UNNEST");
6189                    self.write("(");
6190                    for (i, arg) in unnest_args.iter().enumerate() {
6191                        if i > 0 {
6192                            self.write(", ");
6193                        }
6194                        self.generate_expression(arg)?;
6195                    }
6196                    self.write(")");
6197
6198                    // Add table and column aliases for non-POSEXPLODE
6199                    if let Some(alias) = &lv.table_alias {
6200                        self.write_space();
6201                        self.write_keyword("AS");
6202                        self.write_space();
6203                        self.generate_identifier(alias)?;
6204                        if !lv.column_aliases.is_empty() {
6205                            self.write("(");
6206                            for (i, col) in lv.column_aliases.iter().enumerate() {
6207                                if i > 0 {
6208                                    self.write(", ");
6209                                }
6210                                self.generate_identifier(col)?;
6211                            }
6212                            self.write(")");
6213                        }
6214                    } else if !lv.column_aliases.is_empty() {
6215                        self.write_space();
6216                        self.write_keyword("AS");
6217                        self.write(" t(");
6218                        for (i, col) in lv.column_aliases.iter().enumerate() {
6219                            if i > 0 {
6220                                self.write(", ");
6221                            }
6222                            self.generate_identifier(col)?;
6223                        }
6224                        self.write(")");
6225                    }
6226                }
6227            } else {
6228                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
6229                if !lv.outer {
6230                    self.write_keyword("LATERAL");
6231                    self.write_space();
6232                }
6233                self.generate_expression(&lv.this)?;
6234
6235                // Add table and column aliases
6236                if let Some(alias) = &lv.table_alias {
6237                    self.write_space();
6238                    self.write_keyword("AS");
6239                    self.write_space();
6240                    self.generate_identifier(alias)?;
6241                    if !lv.column_aliases.is_empty() {
6242                        self.write("(");
6243                        for (i, col) in lv.column_aliases.iter().enumerate() {
6244                            if i > 0 {
6245                                self.write(", ");
6246                            }
6247                            self.generate_identifier(col)?;
6248                        }
6249                        self.write(")");
6250                    }
6251                } else if !lv.column_aliases.is_empty() {
6252                    self.write_space();
6253                    self.write_keyword("AS");
6254                    self.write(" t(");
6255                    for (i, col) in lv.column_aliases.iter().enumerate() {
6256                        if i > 0 {
6257                            self.write(", ");
6258                        }
6259                        self.generate_identifier(col)?;
6260                    }
6261                    self.write(")");
6262                }
6263            }
6264
6265            // For LEFT JOIN LATERAL, need ON TRUE
6266            if lv.outer {
6267                self.write_space();
6268                self.write_keyword("ON TRUE");
6269            }
6270        } else {
6271            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
6272            self.write_keyword("LATERAL VIEW");
6273            if lv.outer {
6274                self.write_space();
6275                self.write_keyword("OUTER");
6276            }
6277            if self.config.pretty {
6278                self.write_newline();
6279                self.write_indent();
6280            } else {
6281                self.write_space();
6282            }
6283            self.generate_expression(&lv.this)?;
6284
6285            // Table alias
6286            if let Some(alias) = &lv.table_alias {
6287                self.write_space();
6288                self.generate_identifier(alias)?;
6289            }
6290
6291            // Column aliases
6292            if !lv.column_aliases.is_empty() {
6293                self.write_space();
6294                self.write_keyword("AS");
6295                self.write_space();
6296                for (i, col) in lv.column_aliases.iter().enumerate() {
6297                    if i > 0 {
6298                        self.write(", ");
6299                    }
6300                    self.generate_identifier(col)?;
6301                }
6302            }
6303        }
6304
6305        Ok(())
6306    }
6307
6308    fn generate_union(&mut self, outermost: &Union) -> Result<()> {
6309        // Collect the left-recursive chain of Union nodes iteratively.
6310        // This avoids stack overflow for deeply nested chains like
6311        // SELECT 1 UNION ALL SELECT 2 UNION ALL ... UNION ALL SELECT N
6312        // where the parser builds: Union(Union(Union(A, B), C), D)
6313        let mut chain: Vec<&Union> = vec![outermost];
6314        let mut leftmost: &Expression = &outermost.left;
6315        while let Expression::Union(inner) = leftmost {
6316            chain.push(inner);
6317            leftmost = &inner.left;
6318        }
6319        // chain[0] = outermost, chain[last] = innermost
6320        // leftmost = innermost.left (a non-Union expression, typically Select)
6321
6322        // WITH clause (only on outermost)
6323        if let Some(with) = &outermost.with {
6324            self.generate_with(with)?;
6325            self.write_space();
6326        }
6327
6328        // Generate the base (leftmost) expression
6329        self.generate_expression(leftmost)?;
6330
6331        // Generate each union step from innermost to outermost
6332        for union in chain.iter().rev() {
6333            self.generate_union_step(union)?;
6334        }
6335        Ok(())
6336    }
6337
6338    /// Generate a single UNION step: keyword, right expression, and trailing modifiers.
6339    fn generate_union_step(&mut self, union: &Union) -> Result<()> {
6340        if self.config.pretty {
6341            self.write_newline();
6342            self.write_indent();
6343        } else {
6344            self.write_space();
6345        }
6346
6347        // BigQuery set operation modifiers: [side] [kind] UNION
6348        if let Some(side) = &union.side {
6349            self.write_keyword(side);
6350            self.write_space();
6351        }
6352        if let Some(kind) = &union.kind {
6353            self.write_keyword(kind);
6354            self.write_space();
6355        }
6356
6357        self.write_keyword("UNION");
6358        if union.all {
6359            self.write_space();
6360            self.write_keyword("ALL");
6361        } else if union.distinct {
6362            self.write_space();
6363            self.write_keyword("DISTINCT");
6364        }
6365
6366        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6367        // DuckDB: BY NAME
6368        if union.corresponding || union.by_name {
6369            self.write_space();
6370            self.write_keyword("BY NAME");
6371        }
6372        if !union.on_columns.is_empty() {
6373            self.write_space();
6374            self.write_keyword("ON");
6375            self.write(" (");
6376            for (i, col) in union.on_columns.iter().enumerate() {
6377                if i > 0 {
6378                    self.write(", ");
6379                }
6380                self.generate_expression(col)?;
6381            }
6382            self.write(")");
6383        }
6384
6385        if self.config.pretty {
6386            self.write_newline();
6387            self.write_indent();
6388        } else {
6389            self.write_space();
6390        }
6391        self.generate_expression(&union.right)?;
6392        // ORDER BY, LIMIT, OFFSET for the set operation
6393        if let Some(order_by) = &union.order_by {
6394            if self.config.pretty {
6395                self.write_newline();
6396            } else {
6397                self.write_space();
6398            }
6399            self.write_keyword("ORDER BY");
6400            self.write_space();
6401            for (i, ordered) in order_by.expressions.iter().enumerate() {
6402                if i > 0 {
6403                    self.write(", ");
6404                }
6405                self.generate_ordered(ordered)?;
6406            }
6407        }
6408        if let Some(limit) = &union.limit {
6409            if self.config.pretty {
6410                self.write_newline();
6411            } else {
6412                self.write_space();
6413            }
6414            self.write_keyword("LIMIT");
6415            self.write_space();
6416            self.generate_expression(limit)?;
6417        }
6418        if let Some(offset) = &union.offset {
6419            if self.config.pretty {
6420                self.write_newline();
6421            } else {
6422                self.write_space();
6423            }
6424            self.write_keyword("OFFSET");
6425            self.write_space();
6426            self.generate_expression(offset)?;
6427        }
6428        // DISTRIBUTE BY (Hive/Spark)
6429        if let Some(distribute_by) = &union.distribute_by {
6430            self.write_space();
6431            self.write_keyword("DISTRIBUTE BY");
6432            self.write_space();
6433            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6434                if i > 0 {
6435                    self.write(", ");
6436                }
6437                self.generate_expression(expr)?;
6438            }
6439        }
6440        // SORT BY (Hive/Spark)
6441        if let Some(sort_by) = &union.sort_by {
6442            self.write_space();
6443            self.write_keyword("SORT BY");
6444            self.write_space();
6445            for (i, ord) in sort_by.expressions.iter().enumerate() {
6446                if i > 0 {
6447                    self.write(", ");
6448                }
6449                self.generate_ordered(ord)?;
6450            }
6451        }
6452        // CLUSTER BY (Hive/Spark)
6453        if let Some(cluster_by) = &union.cluster_by {
6454            self.write_space();
6455            self.write_keyword("CLUSTER BY");
6456            self.write_space();
6457            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6458                if i > 0 {
6459                    self.write(", ");
6460                }
6461                self.generate_ordered(ord)?;
6462            }
6463        }
6464        Ok(())
6465    }
6466
6467    fn generate_intersect(&mut self, outermost: &Intersect) -> Result<()> {
6468        // Collect the left-recursive chain iteratively to avoid stack overflow
6469        let mut chain: Vec<&Intersect> = vec![outermost];
6470        let mut leftmost: &Expression = &outermost.left;
6471        while let Expression::Intersect(inner) = leftmost {
6472            chain.push(inner);
6473            leftmost = &inner.left;
6474        }
6475
6476        if let Some(with) = &outermost.with {
6477            self.generate_with(with)?;
6478            self.write_space();
6479        }
6480
6481        self.generate_expression(leftmost)?;
6482
6483        for intersect in chain.iter().rev() {
6484            self.generate_intersect_step(intersect)?;
6485        }
6486        Ok(())
6487    }
6488
6489    /// Generate a single INTERSECT step: keyword, right expression, and trailing modifiers.
6490    fn generate_intersect_step(&mut self, intersect: &Intersect) -> Result<()> {
6491        if self.config.pretty {
6492            self.write_newline();
6493            self.write_indent();
6494        } else {
6495            self.write_space();
6496        }
6497
6498        // BigQuery set operation modifiers: [side] [kind] INTERSECT
6499        if let Some(side) = &intersect.side {
6500            self.write_keyword(side);
6501            self.write_space();
6502        }
6503        if let Some(kind) = &intersect.kind {
6504            self.write_keyword(kind);
6505            self.write_space();
6506        }
6507
6508        self.write_keyword("INTERSECT");
6509        if intersect.all {
6510            self.write_space();
6511            self.write_keyword("ALL");
6512        } else if intersect.distinct {
6513            self.write_space();
6514            self.write_keyword("DISTINCT");
6515        }
6516
6517        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6518        // DuckDB: BY NAME
6519        if intersect.corresponding || intersect.by_name {
6520            self.write_space();
6521            self.write_keyword("BY NAME");
6522        }
6523        if !intersect.on_columns.is_empty() {
6524            self.write_space();
6525            self.write_keyword("ON");
6526            self.write(" (");
6527            for (i, col) in intersect.on_columns.iter().enumerate() {
6528                if i > 0 {
6529                    self.write(", ");
6530                }
6531                self.generate_expression(col)?;
6532            }
6533            self.write(")");
6534        }
6535
6536        if self.config.pretty {
6537            self.write_newline();
6538            self.write_indent();
6539        } else {
6540            self.write_space();
6541        }
6542        self.generate_expression(&intersect.right)?;
6543        // ORDER BY, LIMIT, OFFSET for the set operation
6544        if let Some(order_by) = &intersect.order_by {
6545            if self.config.pretty {
6546                self.write_newline();
6547            } else {
6548                self.write_space();
6549            }
6550            self.write_keyword("ORDER BY");
6551            self.write_space();
6552            for (i, ordered) in order_by.expressions.iter().enumerate() {
6553                if i > 0 {
6554                    self.write(", ");
6555                }
6556                self.generate_ordered(ordered)?;
6557            }
6558        }
6559        if let Some(limit) = &intersect.limit {
6560            if self.config.pretty {
6561                self.write_newline();
6562            } else {
6563                self.write_space();
6564            }
6565            self.write_keyword("LIMIT");
6566            self.write_space();
6567            self.generate_expression(limit)?;
6568        }
6569        if let Some(offset) = &intersect.offset {
6570            if self.config.pretty {
6571                self.write_newline();
6572            } else {
6573                self.write_space();
6574            }
6575            self.write_keyword("OFFSET");
6576            self.write_space();
6577            self.generate_expression(offset)?;
6578        }
6579        // DISTRIBUTE BY (Hive/Spark)
6580        if let Some(distribute_by) = &intersect.distribute_by {
6581            self.write_space();
6582            self.write_keyword("DISTRIBUTE BY");
6583            self.write_space();
6584            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6585                if i > 0 {
6586                    self.write(", ");
6587                }
6588                self.generate_expression(expr)?;
6589            }
6590        }
6591        // SORT BY (Hive/Spark)
6592        if let Some(sort_by) = &intersect.sort_by {
6593            self.write_space();
6594            self.write_keyword("SORT BY");
6595            self.write_space();
6596            for (i, ord) in sort_by.expressions.iter().enumerate() {
6597                if i > 0 {
6598                    self.write(", ");
6599                }
6600                self.generate_ordered(ord)?;
6601            }
6602        }
6603        // CLUSTER BY (Hive/Spark)
6604        if let Some(cluster_by) = &intersect.cluster_by {
6605            self.write_space();
6606            self.write_keyword("CLUSTER BY");
6607            self.write_space();
6608            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6609                if i > 0 {
6610                    self.write(", ");
6611                }
6612                self.generate_ordered(ord)?;
6613            }
6614        }
6615        Ok(())
6616    }
6617
6618    fn generate_except(&mut self, outermost: &Except) -> Result<()> {
6619        // Collect the left-recursive chain iteratively to avoid stack overflow
6620        let mut chain: Vec<&Except> = vec![outermost];
6621        let mut leftmost: &Expression = &outermost.left;
6622        while let Expression::Except(inner) = leftmost {
6623            chain.push(inner);
6624            leftmost = &inner.left;
6625        }
6626
6627        if let Some(with) = &outermost.with {
6628            self.generate_with(with)?;
6629            self.write_space();
6630        }
6631
6632        self.generate_expression(leftmost)?;
6633
6634        for except in chain.iter().rev() {
6635            self.generate_except_step(except)?;
6636        }
6637        Ok(())
6638    }
6639
6640    /// Generate a single EXCEPT step: keyword, right expression, and trailing modifiers.
6641    fn generate_except_step(&mut self, except: &Except) -> Result<()> {
6642        use crate::dialects::DialectType;
6643
6644        if self.config.pretty {
6645            self.write_newline();
6646            self.write_indent();
6647        } else {
6648            self.write_space();
6649        }
6650
6651        // BigQuery set operation modifiers: [side] [kind] EXCEPT
6652        if let Some(side) = &except.side {
6653            self.write_keyword(side);
6654            self.write_space();
6655        }
6656        if let Some(kind) = &except.kind {
6657            self.write_keyword(kind);
6658            self.write_space();
6659        }
6660
6661        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
6662        match self.config.dialect {
6663            Some(DialectType::Oracle) if !except.all => {
6664                self.write_keyword("MINUS");
6665            }
6666            Some(DialectType::ClickHouse) => {
6667                // ClickHouse: drop ALL from EXCEPT ALL
6668                self.write_keyword("EXCEPT");
6669                if except.distinct {
6670                    self.write_space();
6671                    self.write_keyword("DISTINCT");
6672                }
6673            }
6674            Some(DialectType::BigQuery) => {
6675                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
6676                self.write_keyword("EXCEPT");
6677                if except.all {
6678                    self.write_space();
6679                    self.write_keyword("ALL");
6680                } else {
6681                    self.write_space();
6682                    self.write_keyword("DISTINCT");
6683                }
6684            }
6685            _ => {
6686                self.write_keyword("EXCEPT");
6687                if except.all {
6688                    self.write_space();
6689                    self.write_keyword("ALL");
6690                } else if except.distinct {
6691                    self.write_space();
6692                    self.write_keyword("DISTINCT");
6693                }
6694            }
6695        }
6696
6697        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6698        // DuckDB: BY NAME
6699        if except.corresponding || except.by_name {
6700            self.write_space();
6701            self.write_keyword("BY NAME");
6702        }
6703        if !except.on_columns.is_empty() {
6704            self.write_space();
6705            self.write_keyword("ON");
6706            self.write(" (");
6707            for (i, col) in except.on_columns.iter().enumerate() {
6708                if i > 0 {
6709                    self.write(", ");
6710                }
6711                self.generate_expression(col)?;
6712            }
6713            self.write(")");
6714        }
6715
6716        if self.config.pretty {
6717            self.write_newline();
6718            self.write_indent();
6719        } else {
6720            self.write_space();
6721        }
6722        self.generate_expression(&except.right)?;
6723        // ORDER BY, LIMIT, OFFSET for the set operation
6724        if let Some(order_by) = &except.order_by {
6725            if self.config.pretty {
6726                self.write_newline();
6727            } else {
6728                self.write_space();
6729            }
6730            self.write_keyword("ORDER BY");
6731            self.write_space();
6732            for (i, ordered) in order_by.expressions.iter().enumerate() {
6733                if i > 0 {
6734                    self.write(", ");
6735                }
6736                self.generate_ordered(ordered)?;
6737            }
6738        }
6739        if let Some(limit) = &except.limit {
6740            if self.config.pretty {
6741                self.write_newline();
6742            } else {
6743                self.write_space();
6744            }
6745            self.write_keyword("LIMIT");
6746            self.write_space();
6747            self.generate_expression(limit)?;
6748        }
6749        if let Some(offset) = &except.offset {
6750            if self.config.pretty {
6751                self.write_newline();
6752            } else {
6753                self.write_space();
6754            }
6755            self.write_keyword("OFFSET");
6756            self.write_space();
6757            self.generate_expression(offset)?;
6758        }
6759        // DISTRIBUTE BY (Hive/Spark)
6760        if let Some(distribute_by) = &except.distribute_by {
6761            self.write_space();
6762            self.write_keyword("DISTRIBUTE BY");
6763            self.write_space();
6764            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6765                if i > 0 {
6766                    self.write(", ");
6767                }
6768                self.generate_expression(expr)?;
6769            }
6770        }
6771        // SORT BY (Hive/Spark)
6772        if let Some(sort_by) = &except.sort_by {
6773            self.write_space();
6774            self.write_keyword("SORT BY");
6775            self.write_space();
6776            for (i, ord) in sort_by.expressions.iter().enumerate() {
6777                if i > 0 {
6778                    self.write(", ");
6779                }
6780                self.generate_ordered(ord)?;
6781            }
6782        }
6783        // CLUSTER BY (Hive/Spark)
6784        if let Some(cluster_by) = &except.cluster_by {
6785            self.write_space();
6786            self.write_keyword("CLUSTER BY");
6787            self.write_space();
6788            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6789                if i > 0 {
6790                    self.write(", ");
6791                }
6792                self.generate_ordered(ord)?;
6793            }
6794        }
6795        Ok(())
6796    }
6797
6798    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
6799        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
6800        let prepend_query_cte = if insert.with.is_none() {
6801            use crate::dialects::DialectType;
6802            let should_prepend = matches!(
6803                self.config.dialect,
6804                Some(DialectType::TSQL)
6805                    | Some(DialectType::Fabric)
6806                    | Some(DialectType::Spark)
6807                    | Some(DialectType::Databricks)
6808                    | Some(DialectType::Hive)
6809            );
6810            if should_prepend {
6811                if let Some(Expression::Select(select)) = &insert.query {
6812                    select.with.clone()
6813                } else {
6814                    None
6815                }
6816            } else {
6817                None
6818            }
6819        } else {
6820            None
6821        };
6822
6823        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
6824        if let Some(with) = &insert.with {
6825            self.generate_with(with)?;
6826            self.write_space();
6827        } else if let Some(with) = &prepend_query_cte {
6828            self.generate_with(with)?;
6829            self.write_space();
6830        }
6831
6832        // Output leading comments before INSERT
6833        for comment in &insert.leading_comments {
6834            self.write_formatted_comment(comment);
6835            self.write(" ");
6836        }
6837
6838        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
6839        if let Some(dir) = &insert.directory {
6840            self.write_keyword("INSERT OVERWRITE");
6841            if dir.local {
6842                self.write_space();
6843                self.write_keyword("LOCAL");
6844            }
6845            self.write_space();
6846            self.write_keyword("DIRECTORY");
6847            self.write_space();
6848            self.write("'");
6849            self.write(&dir.path);
6850            self.write("'");
6851
6852            // ROW FORMAT clause
6853            if let Some(row_format) = &dir.row_format {
6854                self.write_space();
6855                self.write_keyword("ROW FORMAT");
6856                if row_format.delimited {
6857                    self.write_space();
6858                    self.write_keyword("DELIMITED");
6859                }
6860                if let Some(val) = &row_format.fields_terminated_by {
6861                    self.write_space();
6862                    self.write_keyword("FIELDS TERMINATED BY");
6863                    self.write_space();
6864                    self.write("'");
6865                    self.write(val);
6866                    self.write("'");
6867                }
6868                if let Some(val) = &row_format.collection_items_terminated_by {
6869                    self.write_space();
6870                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
6871                    self.write_space();
6872                    self.write("'");
6873                    self.write(val);
6874                    self.write("'");
6875                }
6876                if let Some(val) = &row_format.map_keys_terminated_by {
6877                    self.write_space();
6878                    self.write_keyword("MAP KEYS TERMINATED BY");
6879                    self.write_space();
6880                    self.write("'");
6881                    self.write(val);
6882                    self.write("'");
6883                }
6884                if let Some(val) = &row_format.lines_terminated_by {
6885                    self.write_space();
6886                    self.write_keyword("LINES TERMINATED BY");
6887                    self.write_space();
6888                    self.write("'");
6889                    self.write(val);
6890                    self.write("'");
6891                }
6892                if let Some(val) = &row_format.null_defined_as {
6893                    self.write_space();
6894                    self.write_keyword("NULL DEFINED AS");
6895                    self.write_space();
6896                    self.write("'");
6897                    self.write(val);
6898                    self.write("'");
6899                }
6900            }
6901
6902            // STORED AS clause
6903            if let Some(format) = &dir.stored_as {
6904                self.write_space();
6905                self.write_keyword("STORED AS");
6906                self.write_space();
6907                self.write_keyword(format);
6908            }
6909
6910            // Query (SELECT statement)
6911            if let Some(query) = &insert.query {
6912                self.write_space();
6913                self.generate_expression(query)?;
6914            }
6915
6916            return Ok(());
6917        }
6918
6919        if insert.is_replace {
6920            // MySQL/SQLite REPLACE INTO statement
6921            self.write_keyword("REPLACE INTO");
6922        } else if insert.overwrite {
6923            // Use dialect-specific INSERT OVERWRITE format
6924            self.write_keyword("INSERT");
6925            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6926            if let Some(ref hint) = insert.hint {
6927                self.generate_hint(hint)?;
6928            }
6929            self.write(&self.config.insert_overwrite.to_ascii_uppercase());
6930        } else if let Some(ref action) = insert.conflict_action {
6931            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
6932            self.write_keyword("INSERT OR");
6933            self.write_space();
6934            self.write_keyword(action);
6935            self.write_space();
6936            self.write_keyword("INTO");
6937        } else if insert.ignore {
6938            // MySQL INSERT IGNORE syntax
6939            self.write_keyword("INSERT IGNORE INTO");
6940        } else {
6941            self.write_keyword("INSERT");
6942            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
6943            if let Some(ref hint) = insert.hint {
6944                self.generate_hint(hint)?;
6945            }
6946            self.write_space();
6947            self.write_keyword("INTO");
6948        }
6949        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
6950        if let Some(ref func) = insert.function_target {
6951            self.write_space();
6952            self.write_keyword("FUNCTION");
6953            self.write_space();
6954            self.generate_expression(func)?;
6955        } else {
6956            self.write_space();
6957            self.generate_table(&insert.table)?;
6958        }
6959
6960        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
6961        if let Some(ref alias) = insert.alias {
6962            self.write_space();
6963            if insert.alias_explicit_as {
6964                self.write_keyword("AS");
6965                self.write_space();
6966            }
6967            self.generate_identifier(alias)?;
6968        }
6969
6970        // IF EXISTS clause (Hive)
6971        if insert.if_exists {
6972            self.write_space();
6973            self.write_keyword("IF EXISTS");
6974        }
6975
6976        // REPLACE WHERE clause (Databricks)
6977        if let Some(ref replace_where) = insert.replace_where {
6978            if self.config.pretty {
6979                self.write_newline();
6980                self.write_indent();
6981            } else {
6982                self.write_space();
6983            }
6984            self.write_keyword("REPLACE WHERE");
6985            self.write_space();
6986            self.generate_expression(replace_where)?;
6987        }
6988
6989        // Generate PARTITION clause if present
6990        if !insert.partition.is_empty() {
6991            self.write_space();
6992            self.write_keyword("PARTITION");
6993            self.write("(");
6994            for (i, (col, val)) in insert.partition.iter().enumerate() {
6995                if i > 0 {
6996                    self.write(", ");
6997                }
6998                self.generate_identifier(col)?;
6999                if let Some(v) = val {
7000                    self.write(" = ");
7001                    self.generate_expression(v)?;
7002                }
7003            }
7004            self.write(")");
7005        }
7006
7007        // ClickHouse: PARTITION BY expr
7008        if let Some(ref partition_by) = insert.partition_by {
7009            self.write_space();
7010            self.write_keyword("PARTITION BY");
7011            self.write_space();
7012            self.generate_expression(partition_by)?;
7013        }
7014
7015        // ClickHouse: SETTINGS key = val, ...
7016        if !insert.settings.is_empty() {
7017            self.write_space();
7018            self.write_keyword("SETTINGS");
7019            self.write_space();
7020            for (i, setting) in insert.settings.iter().enumerate() {
7021                if i > 0 {
7022                    self.write(", ");
7023                }
7024                self.generate_expression(setting)?;
7025            }
7026        }
7027
7028        if !insert.columns.is_empty() {
7029            if insert.alias.is_some() && insert.alias_explicit_as {
7030                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
7031                self.write("(");
7032            } else {
7033                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
7034                self.write(" (");
7035            }
7036            for (i, col) in insert.columns.iter().enumerate() {
7037                if i > 0 {
7038                    self.write(", ");
7039                }
7040                self.generate_identifier(col)?;
7041            }
7042            self.write(")");
7043        }
7044
7045        // OUTPUT clause (TSQL)
7046        if let Some(ref output) = insert.output {
7047            self.generate_output_clause(output)?;
7048        }
7049
7050        // BY NAME modifier (DuckDB)
7051        if insert.by_name {
7052            self.write_space();
7053            self.write_keyword("BY NAME");
7054        }
7055
7056        if insert.default_values {
7057            self.write_space();
7058            self.write_keyword("DEFAULT VALUES");
7059        } else if let Some(query) = &insert.query {
7060            if self.config.pretty {
7061                self.write_newline();
7062            } else {
7063                self.write_space();
7064            }
7065            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
7066            if prepend_query_cte.is_some() {
7067                if let Expression::Select(select) = query {
7068                    let mut select_no_with = select.clone();
7069                    select_no_with.with = None;
7070                    self.generate_select(&select_no_with)?;
7071                } else {
7072                    self.generate_expression(query)?;
7073                }
7074            } else {
7075                self.generate_expression(query)?;
7076            }
7077        } else if !insert.values.is_empty() {
7078            if self.config.pretty {
7079                // Pretty printing: VALUES on new line, each tuple indented
7080                self.write_newline();
7081                self.write_keyword("VALUES");
7082                self.write_newline();
7083                self.indent_level += 1;
7084                for (i, row) in insert.values.iter().enumerate() {
7085                    if i > 0 {
7086                        self.write(",");
7087                        self.write_newline();
7088                    }
7089                    self.write_indent();
7090                    self.write("(");
7091                    for (j, val) in row.iter().enumerate() {
7092                        if j > 0 {
7093                            self.write(", ");
7094                        }
7095                        self.generate_expression(val)?;
7096                    }
7097                    self.write(")");
7098                }
7099                self.indent_level -= 1;
7100            } else {
7101                // Non-pretty: single line
7102                self.write_space();
7103                self.write_keyword("VALUES");
7104                for (i, row) in insert.values.iter().enumerate() {
7105                    if i > 0 {
7106                        self.write(",");
7107                    }
7108                    self.write(" (");
7109                    for (j, val) in row.iter().enumerate() {
7110                        if j > 0 {
7111                            self.write(", ");
7112                        }
7113                        self.generate_expression(val)?;
7114                    }
7115                    self.write(")");
7116                }
7117            }
7118        }
7119
7120        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
7121        if let Some(ref source) = insert.source {
7122            self.write_space();
7123            self.write_keyword("TABLE");
7124            self.write_space();
7125            self.generate_expression(source)?;
7126        }
7127
7128        // Source alias (MySQL: VALUES (...) AS new_data)
7129        if let Some(alias) = &insert.source_alias {
7130            self.write_space();
7131            self.write_keyword("AS");
7132            self.write_space();
7133            self.generate_identifier(alias)?;
7134        }
7135
7136        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
7137        if let Some(on_conflict) = &insert.on_conflict {
7138            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
7139                self.write_space();
7140                self.generate_expression(on_conflict)?;
7141            }
7142        }
7143
7144        // RETURNING clause
7145        if !insert.returning.is_empty() {
7146            self.write_space();
7147            self.write_keyword("RETURNING");
7148            self.write_space();
7149            for (i, expr) in insert.returning.iter().enumerate() {
7150                if i > 0 {
7151                    self.write(", ");
7152                }
7153                self.generate_expression(expr)?;
7154            }
7155        }
7156
7157        Ok(())
7158    }
7159
7160    fn generate_update(&mut self, update: &Update) -> Result<()> {
7161        // Output leading comments before UPDATE
7162        for comment in &update.leading_comments {
7163            self.write_formatted_comment(comment);
7164            self.write(" ");
7165        }
7166
7167        // WITH clause (CTEs)
7168        if let Some(ref with) = update.with {
7169            self.generate_with(with)?;
7170            self.write_space();
7171        }
7172
7173        self.write_keyword("UPDATE");
7174        self.write_space();
7175        self.generate_table(&update.table)?;
7176
7177        let mysql_like_update_from = matches!(
7178            self.config.dialect,
7179            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
7180        ) && update.from_clause.is_some();
7181
7182        let mut set_pairs = update.set.clone();
7183
7184        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
7185        let mut pre_set_joins = update.table_joins.clone();
7186        if mysql_like_update_from {
7187            let target_name = update
7188                .table
7189                .alias
7190                .as_ref()
7191                .map(|a| a.name.clone())
7192                .unwrap_or_else(|| update.table.name.name.clone());
7193
7194            for (col, _) in &mut set_pairs {
7195                if !col.name.contains('.') {
7196                    col.name = format!("{}.{}", target_name, col.name);
7197                }
7198            }
7199
7200            if let Some(from_clause) = &update.from_clause {
7201                for table_expr in &from_clause.expressions {
7202                    pre_set_joins.push(crate::expressions::Join {
7203                        this: table_expr.clone(),
7204                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7205                            value: true,
7206                        })),
7207                        using: Vec::new(),
7208                        kind: crate::expressions::JoinKind::Inner,
7209                        use_inner_keyword: false,
7210                        use_outer_keyword: false,
7211                        deferred_condition: false,
7212                        join_hint: None,
7213                        match_condition: None,
7214                        pivots: Vec::new(),
7215                        comments: Vec::new(),
7216                        nesting_group: 0,
7217                        directed: false,
7218                    });
7219                }
7220            }
7221            for join in &update.from_joins {
7222                let mut join = join.clone();
7223                if join.on.is_none() && join.using.is_empty() {
7224                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7225                        value: true,
7226                    }));
7227                }
7228                pre_set_joins.push(join);
7229            }
7230        }
7231
7232        // Extra tables for multi-table UPDATE (MySQL syntax)
7233        for extra_table in &update.extra_tables {
7234            self.write(", ");
7235            self.generate_table(extra_table)?;
7236        }
7237
7238        // JOINs attached to the table list (MySQL multi-table syntax)
7239        for join in &pre_set_joins {
7240            // generate_join already adds a leading space
7241            self.generate_join(join)?;
7242        }
7243
7244        // Teradata: FROM clause comes before SET
7245        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
7246        if teradata_from_before_set && !mysql_like_update_from {
7247            if let Some(ref from_clause) = update.from_clause {
7248                self.write_space();
7249                self.write_keyword("FROM");
7250                self.write_space();
7251                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7252                    if i > 0 {
7253                        self.write(", ");
7254                    }
7255                    self.generate_expression(table_expr)?;
7256                }
7257            }
7258            for join in &update.from_joins {
7259                self.generate_join(join)?;
7260            }
7261        }
7262
7263        self.write_space();
7264        self.write_keyword("SET");
7265        self.write_space();
7266
7267        for (i, (col, val)) in set_pairs.iter().enumerate() {
7268            if i > 0 {
7269                self.write(", ");
7270            }
7271            self.generate_identifier(col)?;
7272            self.write(" = ");
7273            self.generate_expression(val)?;
7274        }
7275
7276        // OUTPUT clause (TSQL)
7277        if let Some(ref output) = update.output {
7278            self.generate_output_clause(output)?;
7279        }
7280
7281        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
7282        if !mysql_like_update_from && !teradata_from_before_set {
7283            if let Some(ref from_clause) = update.from_clause {
7284                self.write_space();
7285                self.write_keyword("FROM");
7286                self.write_space();
7287                // Generate each table in the FROM clause
7288                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7289                    if i > 0 {
7290                        self.write(", ");
7291                    }
7292                    self.generate_expression(table_expr)?;
7293                }
7294            }
7295        }
7296
7297        if !mysql_like_update_from && !teradata_from_before_set {
7298            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
7299            for join in &update.from_joins {
7300                self.generate_join(join)?;
7301            }
7302        }
7303
7304        if let Some(where_clause) = &update.where_clause {
7305            self.write_space();
7306            self.write_keyword("WHERE");
7307            self.write_space();
7308            self.generate_expression(&where_clause.this)?;
7309        }
7310
7311        // RETURNING clause
7312        if !update.returning.is_empty() {
7313            self.write_space();
7314            self.write_keyword("RETURNING");
7315            self.write_space();
7316            for (i, expr) in update.returning.iter().enumerate() {
7317                if i > 0 {
7318                    self.write(", ");
7319                }
7320                self.generate_expression(expr)?;
7321            }
7322        }
7323
7324        // ORDER BY clause (MySQL)
7325        if let Some(ref order_by) = update.order_by {
7326            self.write_space();
7327            self.generate_order_by(order_by)?;
7328        }
7329
7330        // LIMIT clause (MySQL)
7331        if let Some(ref limit) = update.limit {
7332            self.write_space();
7333            self.write_keyword("LIMIT");
7334            self.write_space();
7335            self.generate_expression(limit)?;
7336        }
7337
7338        Ok(())
7339    }
7340
7341    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
7342        // Output WITH clause if present
7343        if let Some(with) = &delete.with {
7344            self.generate_with(with)?;
7345            self.write_space();
7346        }
7347
7348        // Output leading comments before DELETE
7349        for comment in &delete.leading_comments {
7350            self.write_formatted_comment(comment);
7351            self.write(" ");
7352        }
7353
7354        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
7355        if !delete.tables.is_empty() && !delete.tables_from_using {
7356            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
7357            self.write_keyword("DELETE");
7358            self.write_space();
7359            for (i, tbl) in delete.tables.iter().enumerate() {
7360                if i > 0 {
7361                    self.write(", ");
7362                }
7363                self.generate_table(tbl)?;
7364            }
7365            // TSQL: OUTPUT clause between target table and FROM
7366            if let Some(ref output) = delete.output {
7367                self.generate_output_clause(output)?;
7368            }
7369            self.write_space();
7370            self.write_keyword("FROM");
7371            self.write_space();
7372            self.generate_table(&delete.table)?;
7373        } else if !delete.tables.is_empty() && delete.tables_from_using {
7374            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
7375            self.write_keyword("DELETE FROM");
7376            self.write_space();
7377            for (i, tbl) in delete.tables.iter().enumerate() {
7378                if i > 0 {
7379                    self.write(", ");
7380                }
7381                self.generate_table(tbl)?;
7382            }
7383        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
7384            // BigQuery-style DELETE without FROM keyword
7385            self.write_keyword("DELETE");
7386            self.write_space();
7387            self.generate_table(&delete.table)?;
7388        } else {
7389            self.write_keyword("DELETE FROM");
7390            self.write_space();
7391            self.generate_table(&delete.table)?;
7392        }
7393
7394        // ClickHouse: ON CLUSTER clause
7395        if let Some(ref on_cluster) = delete.on_cluster {
7396            self.write_space();
7397            self.generate_on_cluster(on_cluster)?;
7398        }
7399
7400        // FORCE INDEX hint (MySQL)
7401        if let Some(ref idx) = delete.force_index {
7402            self.write_space();
7403            self.write_keyword("FORCE INDEX");
7404            self.write(" (");
7405            self.write(idx);
7406            self.write(")");
7407        }
7408
7409        // Optional alias
7410        if let Some(ref alias) = delete.alias {
7411            self.write_space();
7412            if delete.alias_explicit_as
7413                || matches!(self.config.dialect, Some(DialectType::BigQuery))
7414            {
7415                self.write_keyword("AS");
7416                self.write_space();
7417            }
7418            self.generate_identifier(alias)?;
7419        }
7420
7421        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
7422        if !delete.tables_from_using {
7423            for join in &delete.joins {
7424                self.generate_join(join)?;
7425            }
7426        }
7427
7428        // USING clause (PostgreSQL/DuckDB/MySQL)
7429        if !delete.using.is_empty() {
7430            self.write_space();
7431            self.write_keyword("USING");
7432            for (i, table) in delete.using.iter().enumerate() {
7433                if i > 0 {
7434                    self.write(",");
7435                }
7436                self.write_space();
7437                // Check if the table has subquery hints (DuckDB USING with subquery)
7438                if !table.hints.is_empty() && table.name.is_empty() {
7439                    // Subquery in USING: (VALUES ...) AS alias(cols)
7440                    self.generate_expression(&table.hints[0])?;
7441                    if let Some(ref alias) = table.alias {
7442                        self.write_space();
7443                        if table.alias_explicit_as {
7444                            self.write_keyword("AS");
7445                            self.write_space();
7446                        }
7447                        self.generate_identifier(alias)?;
7448                        if !table.column_aliases.is_empty() {
7449                            self.write("(");
7450                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
7451                                if j > 0 {
7452                                    self.write(", ");
7453                                }
7454                                self.generate_identifier(col_alias)?;
7455                            }
7456                            self.write(")");
7457                        }
7458                    }
7459                } else {
7460                    self.generate_table(table)?;
7461                }
7462            }
7463        }
7464
7465        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
7466        if delete.tables_from_using {
7467            for join in &delete.joins {
7468                self.generate_join(join)?;
7469            }
7470        }
7471
7472        // OUTPUT clause (TSQL) - only if not already emitted in the early position
7473        let output_already_emitted =
7474            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
7475        if !output_already_emitted {
7476            if let Some(ref output) = delete.output {
7477                self.generate_output_clause(output)?;
7478            }
7479        }
7480
7481        if let Some(where_clause) = &delete.where_clause {
7482            self.write_space();
7483            self.write_keyword("WHERE");
7484            self.write_space();
7485            self.generate_expression(&where_clause.this)?;
7486        }
7487
7488        // ORDER BY clause (MySQL)
7489        if let Some(ref order_by) = delete.order_by {
7490            self.write_space();
7491            self.generate_order_by(order_by)?;
7492        }
7493
7494        // LIMIT clause (MySQL)
7495        if let Some(ref limit) = delete.limit {
7496            self.write_space();
7497            self.write_keyword("LIMIT");
7498            self.write_space();
7499            self.generate_expression(limit)?;
7500        }
7501
7502        // RETURNING clause (PostgreSQL)
7503        if !delete.returning.is_empty() {
7504            self.write_space();
7505            self.write_keyword("RETURNING");
7506            self.write_space();
7507            for (i, expr) in delete.returning.iter().enumerate() {
7508                if i > 0 {
7509                    self.write(", ");
7510                }
7511                self.generate_expression(expr)?;
7512            }
7513        }
7514
7515        Ok(())
7516    }
7517
7518    // ==================== DDL Generation ====================
7519
7520    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
7521        // Athena: Determine if this is Hive-style DDL or Trino-style DML
7522        // CREATE TABLE AS SELECT uses Trino (double quotes)
7523        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
7524        let saved_athena_hive_context = self.athena_hive_context;
7525        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
7526        if matches!(
7527            self.config.dialect,
7528            Some(crate::dialects::DialectType::Athena)
7529        ) {
7530            // Use Hive context if:
7531            // 1. It's an EXTERNAL table, OR
7532            // 2. There's no AS SELECT clause
7533            let is_external = ct
7534                .table_modifier
7535                .as_ref()
7536                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
7537                .unwrap_or(false);
7538            let has_as_select = ct.as_select.is_some();
7539            self.athena_hive_context = is_external || !has_as_select;
7540        }
7541
7542        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
7543        if matches!(
7544            self.config.dialect,
7545            Some(crate::dialects::DialectType::TSQL)
7546        ) {
7547            if let Some(ref query) = ct.as_select {
7548                // Output WITH CTE clause if present
7549                if let Some(with_cte) = &ct.with_cte {
7550                    self.generate_with(with_cte)?;
7551                    self.write_space();
7552                }
7553
7554                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
7555                self.write_keyword("SELECT");
7556                self.write(" * ");
7557                self.write_keyword("INTO");
7558                self.write_space();
7559
7560                // If temporary, prefix with # for TSQL temp table
7561                if ct.temporary {
7562                    self.write("#");
7563                }
7564                self.generate_table(&ct.name)?;
7565
7566                self.write_space();
7567                self.write_keyword("FROM");
7568                self.write(" (");
7569                // For TSQL, add aliases to select columns to preserve column names
7570                let aliased_query = Self::add_column_aliases_to_query(query.clone());
7571                self.generate_expression(&aliased_query)?;
7572                self.write(") ");
7573                self.write_keyword("AS");
7574                self.write(" temp");
7575                return Ok(());
7576            }
7577        }
7578
7579        // Output WITH CTE clause if present
7580        if let Some(with_cte) = &ct.with_cte {
7581            self.generate_with(with_cte)?;
7582            self.write_space();
7583        }
7584
7585        // Output leading comments before CREATE
7586        for comment in &ct.leading_comments {
7587            self.write_formatted_comment(comment);
7588            self.write(" ");
7589        }
7590        self.write_keyword("CREATE");
7591
7592        if ct.or_replace {
7593            self.write_space();
7594            self.write_keyword("OR REPLACE");
7595        }
7596
7597        if ct.temporary {
7598            self.write_space();
7599            // Oracle uses GLOBAL TEMPORARY TABLE syntax
7600            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
7601                self.write_keyword("GLOBAL TEMPORARY");
7602            } else {
7603                self.write_keyword("TEMPORARY");
7604            }
7605        }
7606
7607        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
7608        let is_dictionary = ct
7609            .table_modifier
7610            .as_ref()
7611            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
7612            .unwrap_or(false);
7613        if let Some(ref modifier) = ct.table_modifier {
7614            // TRANSIENT is Snowflake-specific - skip for other dialects
7615            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
7616                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
7617            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
7618            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
7619                || modifier.eq_ignore_ascii_case("SET")
7620                || modifier.eq_ignore_ascii_case("MULTISET")
7621                || modifier.to_ascii_uppercase().contains("VOLATILE")
7622                || modifier.to_ascii_uppercase().starts_with("SET ")
7623                || modifier.to_ascii_uppercase().starts_with("MULTISET ");
7624            let skip_teradata =
7625                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
7626            if !skip_transient && !skip_teradata {
7627                self.write_space();
7628                self.write_keyword(modifier);
7629            }
7630        }
7631
7632        if !is_dictionary {
7633            self.write_space();
7634            self.write_keyword("TABLE");
7635        }
7636
7637        if ct.if_not_exists {
7638            self.write_space();
7639            self.write_keyword("IF NOT EXISTS");
7640        }
7641
7642        self.write_space();
7643        self.generate_table(&ct.name)?;
7644
7645        // ClickHouse: UUID 'xxx' clause after table name
7646        if let Some(ref uuid) = ct.uuid {
7647            self.write_space();
7648            self.write_keyword("UUID");
7649            self.write(" '");
7650            self.write(uuid);
7651            self.write("'");
7652        }
7653
7654        // ClickHouse: ON CLUSTER clause
7655        if let Some(ref on_cluster) = ct.on_cluster {
7656            self.write_space();
7657            self.generate_on_cluster(on_cluster)?;
7658        }
7659
7660        // Teradata: options after table name before column list (comma-separated)
7661        if matches!(
7662            self.config.dialect,
7663            Some(crate::dialects::DialectType::Teradata)
7664        ) && !ct.teradata_post_name_options.is_empty()
7665        {
7666            for opt in &ct.teradata_post_name_options {
7667                self.write(", ");
7668                self.write(opt);
7669            }
7670        }
7671
7672        // Snowflake: COPY GRANTS clause
7673        if ct.copy_grants {
7674            self.write_space();
7675            self.write_keyword("COPY GRANTS");
7676        }
7677
7678        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
7679        if let Some(ref using_template) = ct.using_template {
7680            self.write_space();
7681            self.write_keyword("USING TEMPLATE");
7682            self.write_space();
7683            self.generate_expression(using_template)?;
7684            return Ok(());
7685        }
7686
7687        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
7688        if let Some(ref clone_source) = ct.clone_source {
7689            self.write_space();
7690            if ct.is_copy && self.config.supports_table_copy {
7691                // BigQuery uses COPY
7692                self.write_keyword("COPY");
7693            } else if ct.shallow_clone {
7694                self.write_keyword("SHALLOW CLONE");
7695            } else {
7696                self.write_keyword("CLONE");
7697            }
7698            self.write_space();
7699            self.generate_table(clone_source)?;
7700            // Generate AT/BEFORE time travel clause (stored as Raw expression)
7701            if let Some(ref at_clause) = ct.clone_at_clause {
7702                self.write_space();
7703                self.generate_expression(at_clause)?;
7704            }
7705            return Ok(());
7706        }
7707
7708        // Handle PARTITION OF property
7709        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
7710        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
7711        if let Some(ref partition_of) = ct.partition_of {
7712            self.write_space();
7713
7714            // Extract the PartitionedOfProperty parts to generate them separately
7715            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
7716                // Output: PARTITION OF <table>
7717                self.write_keyword("PARTITION OF");
7718                self.write_space();
7719                self.generate_expression(&pop.this)?;
7720
7721                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
7722                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7723                    self.write(" (");
7724                    let mut first = true;
7725                    for col in &ct.columns {
7726                        if !first {
7727                            self.write(", ");
7728                        }
7729                        first = false;
7730                        self.generate_column_def(col)?;
7731                    }
7732                    for constraint in &ct.constraints {
7733                        if !first {
7734                            self.write(", ");
7735                        }
7736                        first = false;
7737                        self.generate_table_constraint(constraint)?;
7738                    }
7739                    self.write(")");
7740                }
7741
7742                // Output partition bound spec: FOR VALUES ... or DEFAULT
7743                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
7744                    self.write_space();
7745                    self.write_keyword("FOR VALUES");
7746                    self.write_space();
7747                    self.generate_expression(&pop.expression)?;
7748                } else {
7749                    self.write_space();
7750                    self.write_keyword("DEFAULT");
7751                }
7752            } else {
7753                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
7754                self.generate_expression(partition_of)?;
7755
7756                // Output columns/constraints if present
7757                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
7758                    self.write(" (");
7759                    let mut first = true;
7760                    for col in &ct.columns {
7761                        if !first {
7762                            self.write(", ");
7763                        }
7764                        first = false;
7765                        self.generate_column_def(col)?;
7766                    }
7767                    for constraint in &ct.constraints {
7768                        if !first {
7769                            self.write(", ");
7770                        }
7771                        first = false;
7772                        self.generate_table_constraint(constraint)?;
7773                    }
7774                    self.write(")");
7775                }
7776            }
7777
7778            // Output table properties (e.g., PARTITION BY RANGE(population))
7779            for prop in &ct.properties {
7780                self.write_space();
7781                self.generate_expression(prop)?;
7782            }
7783
7784            return Ok(());
7785        }
7786
7787        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
7788        // This matches Python sqlglot's behavior for SQLite dialect
7789        self.sqlite_inline_pk_columns.clear();
7790        if matches!(
7791            self.config.dialect,
7792            Some(crate::dialects::DialectType::SQLite)
7793        ) {
7794            for constraint in &ct.constraints {
7795                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7796                    // Only inline if: single column, no constraint name, and column exists in table
7797                    if columns.len() == 1 && name.is_none() {
7798                        let pk_col_name = columns[0].name.to_ascii_lowercase();
7799                        // Check if this column exists in the table
7800                        if ct
7801                            .columns
7802                            .iter()
7803                            .any(|c| c.name.name.to_ascii_lowercase() == pk_col_name)
7804                        {
7805                            self.sqlite_inline_pk_columns.insert(pk_col_name);
7806                        }
7807                    }
7808                }
7809            }
7810        }
7811
7812        // Output columns if present (even for CTAS with columns)
7813        if !ct.columns.is_empty() {
7814            if self.config.pretty {
7815                // Pretty print: each column on new line
7816                self.write(" (");
7817                self.write_newline();
7818                self.indent_level += 1;
7819                for (i, col) in ct.columns.iter().enumerate() {
7820                    if i > 0 {
7821                        self.write(",");
7822                        self.write_newline();
7823                    }
7824                    self.write_indent();
7825                    self.generate_column_def(col)?;
7826                }
7827                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7828                for constraint in &ct.constraints {
7829                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7830                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7831                        if columns.len() == 1
7832                            && name.is_none()
7833                            && self
7834                                .sqlite_inline_pk_columns
7835                                .contains(&columns[0].name.to_ascii_lowercase())
7836                        {
7837                            continue;
7838                        }
7839                    }
7840                    self.write(",");
7841                    self.write_newline();
7842                    self.write_indent();
7843                    self.generate_table_constraint(constraint)?;
7844                }
7845                self.indent_level -= 1;
7846                self.write_newline();
7847                self.write(")");
7848            } else {
7849                self.write(" (");
7850                for (i, col) in ct.columns.iter().enumerate() {
7851                    if i > 0 {
7852                        self.write(", ");
7853                    }
7854                    self.generate_column_def(col)?;
7855                }
7856                // Table constraints (skip inlined PRIMARY KEY for SQLite)
7857                let mut first_constraint = true;
7858                for constraint in &ct.constraints {
7859                    // Skip single-column PRIMARY KEY that was inlined for SQLite
7860                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
7861                        if columns.len() == 1
7862                            && name.is_none()
7863                            && self
7864                                .sqlite_inline_pk_columns
7865                                .contains(&columns[0].name.to_ascii_lowercase())
7866                        {
7867                            continue;
7868                        }
7869                    }
7870                    if first_constraint {
7871                        self.write(", ");
7872                        first_constraint = false;
7873                    } else {
7874                        self.write(", ");
7875                    }
7876                    self.generate_table_constraint(constraint)?;
7877                }
7878                self.write(")");
7879            }
7880        } else if !ct.constraints.is_empty() {
7881            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
7882            let has_like_only = ct
7883                .constraints
7884                .iter()
7885                .all(|c| matches!(c, TableConstraint::Like { .. }));
7886            let has_tags_only = ct
7887                .constraints
7888                .iter()
7889                .all(|c| matches!(c, TableConstraint::Tags(_)));
7890            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
7891            // Most dialects: CREATE TABLE A LIKE B (no parens)
7892            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
7893            let is_pg_like = matches!(
7894                self.config.dialect,
7895                Some(crate::dialects::DialectType::PostgreSQL)
7896                    | Some(crate::dialects::DialectType::CockroachDB)
7897                    | Some(crate::dialects::DialectType::Materialize)
7898                    | Some(crate::dialects::DialectType::RisingWave)
7899                    | Some(crate::dialects::DialectType::Redshift)
7900                    | Some(crate::dialects::DialectType::Presto)
7901                    | Some(crate::dialects::DialectType::Trino)
7902                    | Some(crate::dialects::DialectType::Athena)
7903            );
7904            let use_parens = if has_like_only {
7905                is_pg_like
7906            } else {
7907                !has_tags_only
7908            };
7909            if self.config.pretty && use_parens {
7910                self.write(" (");
7911                self.write_newline();
7912                self.indent_level += 1;
7913                for (i, constraint) in ct.constraints.iter().enumerate() {
7914                    if i > 0 {
7915                        self.write(",");
7916                        self.write_newline();
7917                    }
7918                    self.write_indent();
7919                    self.generate_table_constraint(constraint)?;
7920                }
7921                self.indent_level -= 1;
7922                self.write_newline();
7923                self.write(")");
7924            } else {
7925                if use_parens {
7926                    self.write(" (");
7927                } else {
7928                    self.write_space();
7929                }
7930                for (i, constraint) in ct.constraints.iter().enumerate() {
7931                    if i > 0 {
7932                        self.write(", ");
7933                    }
7934                    self.generate_table_constraint(constraint)?;
7935                }
7936                if use_parens {
7937                    self.write(")");
7938                }
7939            }
7940        }
7941
7942        // TSQL ON filegroup or ON filegroup (partition_column) clause
7943        if let Some(ref on_prop) = ct.on_property {
7944            self.write(" ");
7945            self.write_keyword("ON");
7946            self.write(" ");
7947            self.generate_expression(&on_prop.this)?;
7948        }
7949
7950        // BigQuery: WITH PARTITION COLUMNS (col_name col_type, ...)
7951        if !ct.with_partition_columns.is_empty() {
7952            if self.config.pretty {
7953                self.write_newline();
7954            } else {
7955                self.write_space();
7956            }
7957            self.write_keyword("WITH PARTITION COLUMNS");
7958            self.write(" (");
7959            if self.config.pretty {
7960                self.write_newline();
7961                self.indent_level += 1;
7962                for (i, col) in ct.with_partition_columns.iter().enumerate() {
7963                    if i > 0 {
7964                        self.write(",");
7965                        self.write_newline();
7966                    }
7967                    self.write_indent();
7968                    self.generate_column_def(col)?;
7969                }
7970                self.indent_level -= 1;
7971                self.write_newline();
7972            } else {
7973                for (i, col) in ct.with_partition_columns.iter().enumerate() {
7974                    if i > 0 {
7975                        self.write(", ");
7976                    }
7977                    self.generate_column_def(col)?;
7978                }
7979            }
7980            self.write(")");
7981        }
7982
7983        // BigQuery: WITH CONNECTION `project.region.connection`
7984        if let Some(ref conn) = ct.with_connection {
7985            if self.config.pretty {
7986                self.write_newline();
7987            } else {
7988                self.write_space();
7989            }
7990            self.write_keyword("WITH CONNECTION");
7991            self.write_space();
7992            self.generate_table(conn)?;
7993        }
7994
7995        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
7996        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
7997        if !is_clickhouse {
7998            for prop in &ct.properties {
7999                if let Expression::SchemaCommentProperty(_) = prop {
8000                    if self.config.pretty {
8001                        self.write_newline();
8002                    } else {
8003                        self.write_space();
8004                    }
8005                    self.generate_expression(prop)?;
8006                }
8007            }
8008        }
8009
8010        // WITH properties (output after columns if columns exist, otherwise before AS)
8011        if !ct.with_properties.is_empty() {
8012            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
8013            let is_snowflake_special_table = matches!(
8014                self.config.dialect,
8015                Some(crate::dialects::DialectType::Snowflake)
8016            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
8017                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
8018            if is_snowflake_special_table {
8019                for (key, value) in &ct.with_properties {
8020                    self.write_space();
8021                    self.write(key);
8022                    self.write("=");
8023                    self.write(value);
8024                }
8025            } else if self.config.pretty {
8026                self.write_newline();
8027                self.write_keyword("WITH");
8028                self.write(" (");
8029                self.write_newline();
8030                self.indent_level += 1;
8031                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8032                    if i > 0 {
8033                        self.write(",");
8034                        self.write_newline();
8035                    }
8036                    self.write_indent();
8037                    self.write(key);
8038                    self.write("=");
8039                    self.write(value);
8040                }
8041                self.indent_level -= 1;
8042                self.write_newline();
8043                self.write(")");
8044            } else {
8045                self.write_space();
8046                self.write_keyword("WITH");
8047                self.write(" (");
8048                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8049                    if i > 0 {
8050                        self.write(", ");
8051                    }
8052                    self.write(key);
8053                    self.write("=");
8054                    self.write(value);
8055                }
8056                self.write(")");
8057            }
8058        }
8059
8060        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
8061            if is_clickhouse && ct.as_select.is_some() {
8062                let mut pre = Vec::new();
8063                let mut post = Vec::new();
8064                for prop in &ct.properties {
8065                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
8066                        post.push(prop);
8067                    } else {
8068                        pre.push(prop);
8069                    }
8070                }
8071                (pre, post)
8072            } else {
8073                (ct.properties.iter().collect(), Vec::new())
8074            };
8075
8076        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
8077        for prop in pre_as_properties {
8078            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
8079            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
8080                continue;
8081            }
8082            if self.config.pretty {
8083                self.write_newline();
8084            } else {
8085                self.write_space();
8086            }
8087            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
8088            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
8089            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
8090            if let Expression::Properties(props) = prop {
8091                let is_hive_dialect = matches!(
8092                    self.config.dialect,
8093                    Some(crate::dialects::DialectType::Hive)
8094                        | Some(crate::dialects::DialectType::Spark)
8095                        | Some(crate::dialects::DialectType::Databricks)
8096                        | Some(crate::dialects::DialectType::Athena)
8097                );
8098                let is_doris_starrocks = matches!(
8099                    self.config.dialect,
8100                    Some(crate::dialects::DialectType::Doris)
8101                        | Some(crate::dialects::DialectType::StarRocks)
8102                );
8103                if is_hive_dialect {
8104                    self.generate_tblproperties_clause(&props.expressions)?;
8105                } else if is_doris_starrocks {
8106                    self.generate_properties_clause(&props.expressions)?;
8107                } else {
8108                    self.generate_options_clause(&props.expressions)?;
8109                }
8110            } else {
8111                self.generate_expression(prop)?;
8112            }
8113        }
8114
8115        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
8116        for prop in &ct.post_table_properties {
8117            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
8118                self.write(" WITH(");
8119                self.generate_system_versioning_content(svp)?;
8120                self.write(")");
8121            } else if let Expression::Properties(props) = prop {
8122                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
8123                let is_doris_starrocks = matches!(
8124                    self.config.dialect,
8125                    Some(crate::dialects::DialectType::Doris)
8126                        | Some(crate::dialects::DialectType::StarRocks)
8127                );
8128                self.write_space();
8129                if is_doris_starrocks {
8130                    self.generate_properties_clause(&props.expressions)?;
8131                } else {
8132                    self.generate_options_clause(&props.expressions)?;
8133                }
8134            } else {
8135                self.write_space();
8136                self.generate_expression(prop)?;
8137            }
8138        }
8139
8140        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
8141        // Only output for StarRocks target
8142        if let Some(ref rollup) = ct.rollup {
8143            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
8144                self.write_space();
8145                self.generate_rollup_property(rollup)?;
8146            }
8147        }
8148
8149        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
8150        // Only output for MySQL-compatible dialects; strip for others during transpilation
8151        // COMMENT is also used by Hive/Spark so we selectively preserve it
8152        let is_mysql_compatible = matches!(
8153            self.config.dialect,
8154            Some(DialectType::MySQL)
8155                | Some(DialectType::SingleStore)
8156                | Some(DialectType::Doris)
8157                | Some(DialectType::StarRocks)
8158                | None
8159        );
8160        let is_hive_compatible = matches!(
8161            self.config.dialect,
8162            Some(DialectType::Hive)
8163                | Some(DialectType::Spark)
8164                | Some(DialectType::Databricks)
8165                | Some(DialectType::Athena)
8166        );
8167        let mysql_pretty_options =
8168            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
8169        for (key, value) in &ct.mysql_table_options {
8170            // Skip non-MySQL-specific options for non-MySQL targets
8171            let should_output = if is_mysql_compatible {
8172                true
8173            } else if is_hive_compatible && key == "COMMENT" {
8174                true // COMMENT is valid in Hive/Spark table definitions
8175            } else {
8176                false
8177            };
8178            if should_output {
8179                if mysql_pretty_options {
8180                    self.write_newline();
8181                    self.write_indent();
8182                } else {
8183                    self.write_space();
8184                }
8185                self.write_keyword(key);
8186                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
8187                if key == "COMMENT" && !self.config.schema_comment_with_eq {
8188                    self.write_space();
8189                } else {
8190                    self.write("=");
8191                }
8192                self.write(value);
8193            }
8194        }
8195
8196        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
8197        if ct.temporary
8198            && matches!(
8199                self.config.dialect,
8200                Some(DialectType::Spark) | Some(DialectType::Databricks)
8201            )
8202            && ct.as_select.is_none()
8203        {
8204            self.write_space();
8205            self.write_keyword("USING PARQUET");
8206        }
8207
8208        // PostgreSQL INHERITS clause
8209        if !ct.inherits.is_empty() {
8210            self.write_space();
8211            self.write_keyword("INHERITS");
8212            self.write(" (");
8213            for (i, parent) in ct.inherits.iter().enumerate() {
8214                if i > 0 {
8215                    self.write(", ");
8216                }
8217                self.generate_table(parent)?;
8218            }
8219            self.write(")");
8220        }
8221
8222        // CREATE TABLE AS SELECT
8223        if let Some(ref query) = ct.as_select {
8224            self.write_space();
8225            self.write_keyword("AS");
8226            self.write_space();
8227            if ct.as_select_parenthesized {
8228                self.write("(");
8229            }
8230            self.generate_expression(query)?;
8231            if ct.as_select_parenthesized {
8232                self.write(")");
8233            }
8234
8235            // Teradata: WITH DATA / WITH NO DATA
8236            if let Some(with_data) = ct.with_data {
8237                self.write_space();
8238                self.write_keyword("WITH");
8239                if !with_data {
8240                    self.write_space();
8241                    self.write_keyword("NO");
8242                }
8243                self.write_space();
8244                self.write_keyword("DATA");
8245            }
8246
8247            // Teradata: AND STATISTICS / AND NO STATISTICS
8248            if let Some(with_statistics) = ct.with_statistics {
8249                self.write_space();
8250                self.write_keyword("AND");
8251                if !with_statistics {
8252                    self.write_space();
8253                    self.write_keyword("NO");
8254                }
8255                self.write_space();
8256                self.write_keyword("STATISTICS");
8257            }
8258
8259            // Teradata: Index specifications
8260            for index in &ct.teradata_indexes {
8261                self.write_space();
8262                match index.kind {
8263                    TeradataIndexKind::NoPrimary => {
8264                        self.write_keyword("NO PRIMARY INDEX");
8265                    }
8266                    TeradataIndexKind::Primary => {
8267                        self.write_keyword("PRIMARY INDEX");
8268                    }
8269                    TeradataIndexKind::PrimaryAmp => {
8270                        self.write_keyword("PRIMARY AMP INDEX");
8271                    }
8272                    TeradataIndexKind::Unique => {
8273                        self.write_keyword("UNIQUE INDEX");
8274                    }
8275                    TeradataIndexKind::UniquePrimary => {
8276                        self.write_keyword("UNIQUE PRIMARY INDEX");
8277                    }
8278                    TeradataIndexKind::Secondary => {
8279                        self.write_keyword("INDEX");
8280                    }
8281                }
8282                // Output index name if present
8283                if let Some(ref name) = index.name {
8284                    self.write_space();
8285                    self.write(name);
8286                }
8287                // Output columns if present
8288                if !index.columns.is_empty() {
8289                    self.write(" (");
8290                    for (i, col) in index.columns.iter().enumerate() {
8291                        if i > 0 {
8292                            self.write(", ");
8293                        }
8294                        self.write(col);
8295                    }
8296                    self.write(")");
8297                }
8298            }
8299
8300            // Teradata: ON COMMIT behavior for volatile tables
8301            if let Some(ref on_commit) = ct.on_commit {
8302                self.write_space();
8303                self.write_keyword("ON COMMIT");
8304                self.write_space();
8305                match on_commit {
8306                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8307                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8308                }
8309            }
8310
8311            if !post_as_properties.is_empty() {
8312                for prop in post_as_properties {
8313                    self.write_space();
8314                    self.generate_expression(prop)?;
8315                }
8316            }
8317
8318            // Restore Athena Hive context before early return
8319            self.athena_hive_context = saved_athena_hive_context;
8320            return Ok(());
8321        }
8322
8323        // ON COMMIT behavior (for non-CTAS tables)
8324        if let Some(ref on_commit) = ct.on_commit {
8325            self.write_space();
8326            self.write_keyword("ON COMMIT");
8327            self.write_space();
8328            match on_commit {
8329                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8330                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8331            }
8332        }
8333
8334        // Restore Athena Hive context
8335        self.athena_hive_context = saved_athena_hive_context;
8336
8337        Ok(())
8338    }
8339
8340    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
8341    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
8342    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
8343        // Output column name
8344        self.generate_identifier(&col.name)?;
8345        // Output data type if known
8346        if !matches!(col.data_type, DataType::Unknown) {
8347            self.write_space();
8348            self.generate_data_type(&col.data_type)?;
8349        }
8350        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
8351        for constraint in &col.constraints {
8352            if let ColumnConstraint::Path(path_expr) = constraint {
8353                self.write_space();
8354                self.write_keyword("PATH");
8355                self.write_space();
8356                self.generate_expression(path_expr)?;
8357            }
8358        }
8359        Ok(())
8360    }
8361
8362    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
8363        // Check if this is a TSQL computed column (no data type)
8364        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8365            && col
8366                .constraints
8367                .iter()
8368                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8369        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
8370        let omit_computed_type = !self.config.computed_column_with_type
8371            && col
8372                .constraints
8373                .iter()
8374                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8375
8376        // Check if this is a partition column spec (no data type, type is Unknown)
8377        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
8378        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
8379
8380        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
8381        // Also check the no_type flag for SQLite columns without types
8382        let has_no_type = col.no_type
8383            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8384                && col.constraints.is_empty());
8385
8386        self.generate_identifier(&col.name)?;
8387
8388        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
8389        let serial_expansion = if matches!(
8390            self.config.dialect,
8391            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
8392        ) {
8393            if let DataType::Custom { ref name } = col.data_type {
8394                if name.eq_ignore_ascii_case("SERIAL") {
8395                    Some("INT")
8396                } else if name.eq_ignore_ascii_case("BIGSERIAL") {
8397                    Some("BIGINT")
8398                } else if name.eq_ignore_ascii_case("SMALLSERIAL") {
8399                    Some("SMALLINT")
8400                } else {
8401                    None
8402                }
8403            } else {
8404                None
8405            }
8406        } else {
8407            None
8408        };
8409
8410        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
8411        {
8412            self.write_space();
8413            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
8414            // since ClickHouse uses explicit Nullable() in its type system.
8415            let saved_nullable_depth = self.clickhouse_nullable_depth;
8416            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
8417                self.clickhouse_nullable_depth = -1;
8418            }
8419            if let Some(int_type) = serial_expansion {
8420                // SERIAL -> INT (+ constraints added below)
8421                self.write_keyword(int_type);
8422            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
8423                // For DuckDB: convert unsigned integer types to their unsigned equivalents
8424                let unsigned_type = match &col.data_type {
8425                    DataType::Int { .. } => Some("UINTEGER"),
8426                    DataType::BigInt { .. } => Some("UBIGINT"),
8427                    DataType::SmallInt { .. } => Some("USMALLINT"),
8428                    DataType::TinyInt { .. } => Some("UTINYINT"),
8429                    _ => None,
8430                };
8431                if let Some(utype) = unsigned_type {
8432                    self.write_keyword(utype);
8433                } else {
8434                    self.generate_data_type(&col.data_type)?;
8435                }
8436            } else {
8437                self.generate_data_type(&col.data_type)?;
8438            }
8439            self.clickhouse_nullable_depth = saved_nullable_depth;
8440        }
8441
8442        // MySQL type modifiers (must come right after data type)
8443        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
8444        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
8445            self.write_space();
8446            self.write_keyword("UNSIGNED");
8447        }
8448        if col.zerofill {
8449            self.write_space();
8450            self.write_keyword("ZEROFILL");
8451        }
8452
8453        // Teradata column attributes (must come right after data type, in specific order)
8454        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
8455
8456        if let Some(ref charset) = col.character_set {
8457            self.write_space();
8458            self.write_keyword("CHARACTER SET");
8459            self.write_space();
8460            self.write(charset);
8461        }
8462
8463        if col.uppercase {
8464            self.write_space();
8465            self.write_keyword("UPPERCASE");
8466        }
8467
8468        if let Some(casespecific) = col.casespecific {
8469            self.write_space();
8470            if casespecific {
8471                self.write_keyword("CASESPECIFIC");
8472            } else {
8473                self.write_keyword("NOT CASESPECIFIC");
8474            }
8475        }
8476
8477        if let Some(ref format) = col.format {
8478            self.write_space();
8479            self.write_keyword("FORMAT");
8480            self.write(" '");
8481            self.write(format);
8482            self.write("'");
8483        }
8484
8485        if let Some(ref title) = col.title {
8486            self.write_space();
8487            self.write_keyword("TITLE");
8488            self.write(" '");
8489            self.write(title);
8490            self.write("'");
8491        }
8492
8493        if let Some(length) = col.inline_length {
8494            self.write_space();
8495            self.write_keyword("INLINE LENGTH");
8496            self.write(" ");
8497            self.write(&length.to_string());
8498        }
8499
8500        if let Some(ref compress) = col.compress {
8501            self.write_space();
8502            self.write_keyword("COMPRESS");
8503            if !compress.is_empty() {
8504                // Single string literal: output without parentheses (Teradata syntax)
8505                if compress.len() == 1 {
8506                    if let Expression::Literal(lit) = &compress[0] {
8507                        if let Literal::String(_) = lit.as_ref() {
8508                            self.write_space();
8509                            self.generate_expression(&compress[0])?;
8510                        }
8511                    } else {
8512                        self.write(" (");
8513                        self.generate_expression(&compress[0])?;
8514                        self.write(")");
8515                    }
8516                } else {
8517                    self.write(" (");
8518                    for (i, val) in compress.iter().enumerate() {
8519                        if i > 0 {
8520                            self.write(", ");
8521                        }
8522                        self.generate_expression(val)?;
8523                    }
8524                    self.write(")");
8525                }
8526            }
8527        }
8528
8529        // Column constraints - output in original order if constraint_order is populated
8530        // Otherwise fall back to legacy fixed order for backward compatibility
8531        if !col.constraint_order.is_empty() {
8532            // Use constraint_order for original ordering
8533            // Track indices for constraints stored in the constraints Vec
8534            let mut references_idx = 0;
8535            let mut check_idx = 0;
8536            let mut generated_idx = 0;
8537            let mut collate_idx = 0;
8538            let mut comment_idx = 0;
8539            // The preprocessing in dialects/mod.rs now handles the correct ordering of
8540            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
8541            let defer_not_null_after_identity = false;
8542            let mut pending_not_null_after_identity = false;
8543
8544            for constraint_type in &col.constraint_order {
8545                match constraint_type {
8546                    ConstraintType::PrimaryKey => {
8547                        // Materialize doesn't support PRIMARY KEY column constraints
8548                        if col.primary_key
8549                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
8550                        {
8551                            if let Some(ref cname) = col.primary_key_constraint_name {
8552                                self.write_space();
8553                                self.write_keyword("CONSTRAINT");
8554                                self.write_space();
8555                                self.write(cname);
8556                            }
8557                            self.write_space();
8558                            self.write_keyword("PRIMARY KEY");
8559                            if let Some(ref order) = col.primary_key_order {
8560                                self.write_space();
8561                                match order {
8562                                    SortOrder::Asc => self.write_keyword("ASC"),
8563                                    SortOrder::Desc => self.write_keyword("DESC"),
8564                                }
8565                            }
8566                        }
8567                    }
8568                    ConstraintType::Unique => {
8569                        if col.unique {
8570                            if let Some(ref cname) = col.unique_constraint_name {
8571                                self.write_space();
8572                                self.write_keyword("CONSTRAINT");
8573                                self.write_space();
8574                                self.write(cname);
8575                            }
8576                            self.write_space();
8577                            self.write_keyword("UNIQUE");
8578                            // PostgreSQL 15+: NULLS NOT DISTINCT
8579                            if col.unique_nulls_not_distinct {
8580                                self.write(" NULLS NOT DISTINCT");
8581                            }
8582                        }
8583                    }
8584                    ConstraintType::NotNull => {
8585                        if col.nullable == Some(false) {
8586                            if defer_not_null_after_identity {
8587                                pending_not_null_after_identity = true;
8588                                continue;
8589                            }
8590                            if let Some(ref cname) = col.not_null_constraint_name {
8591                                self.write_space();
8592                                self.write_keyword("CONSTRAINT");
8593                                self.write_space();
8594                                self.write(cname);
8595                            }
8596                            self.write_space();
8597                            self.write_keyword("NOT NULL");
8598                        }
8599                    }
8600                    ConstraintType::Null => {
8601                        if col.nullable == Some(true) {
8602                            self.write_space();
8603                            self.write_keyword("NULL");
8604                        }
8605                    }
8606                    ConstraintType::Default => {
8607                        if let Some(ref default) = col.default {
8608                            self.write_space();
8609                            self.write_keyword("DEFAULT");
8610                            self.write_space();
8611                            self.generate_expression(default)?;
8612                        }
8613                    }
8614                    ConstraintType::AutoIncrement => {
8615                        if col.auto_increment {
8616                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
8617                            if matches!(
8618                                self.config.dialect,
8619                                Some(crate::dialects::DialectType::DuckDB)
8620                            ) {
8621                                // Skip - DuckDB uses sequences or rowid instead
8622                            } else if matches!(
8623                                self.config.dialect,
8624                                Some(crate::dialects::DialectType::Materialize)
8625                            ) {
8626                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
8627                                if !matches!(col.nullable, Some(false)) {
8628                                    self.write_space();
8629                                    self.write_keyword("NOT NULL");
8630                                }
8631                            } else if matches!(
8632                                self.config.dialect,
8633                                Some(crate::dialects::DialectType::PostgreSQL)
8634                            ) {
8635                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
8636                                self.write_space();
8637                                self.generate_auto_increment_keyword(col)?;
8638                            } else {
8639                                self.write_space();
8640                                self.generate_auto_increment_keyword(col)?;
8641                                if pending_not_null_after_identity {
8642                                    self.write_space();
8643                                    self.write_keyword("NOT NULL");
8644                                    pending_not_null_after_identity = false;
8645                                }
8646                            }
8647                        } // close else for DuckDB skip
8648                    }
8649                    ConstraintType::References => {
8650                        // Find next References constraint
8651                        while references_idx < col.constraints.len() {
8652                            if let ColumnConstraint::References(fk_ref) =
8653                                &col.constraints[references_idx]
8654                            {
8655                                // CONSTRAINT name if present
8656                                if let Some(ref name) = fk_ref.constraint_name {
8657                                    self.write_space();
8658                                    self.write_keyword("CONSTRAINT");
8659                                    self.write_space();
8660                                    self.write(name);
8661                                }
8662                                self.write_space();
8663                                if fk_ref.has_foreign_key_keywords {
8664                                    self.write_keyword("FOREIGN KEY");
8665                                    self.write_space();
8666                                }
8667                                self.write_keyword("REFERENCES");
8668                                self.write_space();
8669                                self.generate_table(&fk_ref.table)?;
8670                                if !fk_ref.columns.is_empty() {
8671                                    self.write(" (");
8672                                    for (i, c) in fk_ref.columns.iter().enumerate() {
8673                                        if i > 0 {
8674                                            self.write(", ");
8675                                        }
8676                                        self.generate_identifier(c)?;
8677                                    }
8678                                    self.write(")");
8679                                }
8680                                self.generate_referential_actions(fk_ref)?;
8681                                references_idx += 1;
8682                                break;
8683                            }
8684                            references_idx += 1;
8685                        }
8686                    }
8687                    ConstraintType::Check => {
8688                        // Find next Check constraint
8689                        while check_idx < col.constraints.len() {
8690                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
8691                                // Output CONSTRAINT name if present (only for first CHECK)
8692                                if check_idx == 0 {
8693                                    if let Some(ref cname) = col.check_constraint_name {
8694                                        self.write_space();
8695                                        self.write_keyword("CONSTRAINT");
8696                                        self.write_space();
8697                                        self.write(cname);
8698                                    }
8699                                }
8700                                self.write_space();
8701                                self.write_keyword("CHECK");
8702                                self.write(" (");
8703                                self.generate_expression(expr)?;
8704                                self.write(")");
8705                                check_idx += 1;
8706                                break;
8707                            }
8708                            check_idx += 1;
8709                        }
8710                    }
8711                    ConstraintType::GeneratedAsIdentity => {
8712                        // Find next GeneratedAsIdentity constraint
8713                        while generated_idx < col.constraints.len() {
8714                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
8715                                &col.constraints[generated_idx]
8716                            {
8717                                self.write_space();
8718                                // Redshift uses IDENTITY(start, increment) syntax
8719                                if matches!(
8720                                    self.config.dialect,
8721                                    Some(crate::dialects::DialectType::Redshift)
8722                                ) {
8723                                    self.write_keyword("IDENTITY");
8724                                    self.write("(");
8725                                    if let Some(ref start) = gen.start {
8726                                        self.generate_expression(start)?;
8727                                    } else {
8728                                        self.write("0");
8729                                    }
8730                                    self.write(", ");
8731                                    if let Some(ref incr) = gen.increment {
8732                                        self.generate_expression(incr)?;
8733                                    } else {
8734                                        self.write("1");
8735                                    }
8736                                    self.write(")");
8737                                } else {
8738                                    self.write_keyword("GENERATED");
8739                                    if gen.always {
8740                                        self.write_space();
8741                                        self.write_keyword("ALWAYS");
8742                                    } else {
8743                                        self.write_space();
8744                                        self.write_keyword("BY DEFAULT");
8745                                        if gen.on_null {
8746                                            self.write_space();
8747                                            self.write_keyword("ON NULL");
8748                                        }
8749                                    }
8750                                    self.write_space();
8751                                    self.write_keyword("AS IDENTITY");
8752
8753                                    let has_options = gen.start.is_some()
8754                                        || gen.increment.is_some()
8755                                        || gen.minvalue.is_some()
8756                                        || gen.maxvalue.is_some()
8757                                        || gen.cycle.is_some();
8758                                    if has_options {
8759                                        self.write(" (");
8760                                        let mut first = true;
8761                                        if let Some(ref start) = gen.start {
8762                                            if !first {
8763                                                self.write(" ");
8764                                            }
8765                                            first = false;
8766                                            self.write_keyword("START WITH");
8767                                            self.write_space();
8768                                            self.generate_expression(start)?;
8769                                        }
8770                                        if let Some(ref incr) = gen.increment {
8771                                            if !first {
8772                                                self.write(" ");
8773                                            }
8774                                            first = false;
8775                                            self.write_keyword("INCREMENT BY");
8776                                            self.write_space();
8777                                            self.generate_expression(incr)?;
8778                                        }
8779                                        if let Some(ref minv) = gen.minvalue {
8780                                            if !first {
8781                                                self.write(" ");
8782                                            }
8783                                            first = false;
8784                                            self.write_keyword("MINVALUE");
8785                                            self.write_space();
8786                                            self.generate_expression(minv)?;
8787                                        }
8788                                        if let Some(ref maxv) = gen.maxvalue {
8789                                            if !first {
8790                                                self.write(" ");
8791                                            }
8792                                            first = false;
8793                                            self.write_keyword("MAXVALUE");
8794                                            self.write_space();
8795                                            self.generate_expression(maxv)?;
8796                                        }
8797                                        if let Some(cycle) = gen.cycle {
8798                                            if !first {
8799                                                self.write(" ");
8800                                            }
8801                                            if cycle {
8802                                                self.write_keyword("CYCLE");
8803                                            } else {
8804                                                self.write_keyword("NO CYCLE");
8805                                            }
8806                                        }
8807                                        self.write(")");
8808                                    }
8809                                }
8810                                generated_idx += 1;
8811                                break;
8812                            }
8813                            generated_idx += 1;
8814                        }
8815                    }
8816                    ConstraintType::Collate => {
8817                        // Find next Collate constraint
8818                        while collate_idx < col.constraints.len() {
8819                            if let ColumnConstraint::Collate(collation) =
8820                                &col.constraints[collate_idx]
8821                            {
8822                                self.write_space();
8823                                self.write_keyword("COLLATE");
8824                                self.write_space();
8825                                self.generate_identifier(collation)?;
8826                                collate_idx += 1;
8827                                break;
8828                            }
8829                            collate_idx += 1;
8830                        }
8831                    }
8832                    ConstraintType::Comment => {
8833                        // Find next Comment constraint
8834                        while comment_idx < col.constraints.len() {
8835                            if let ColumnConstraint::Comment(comment) =
8836                                &col.constraints[comment_idx]
8837                            {
8838                                self.write_space();
8839                                self.write_keyword("COMMENT");
8840                                self.write_space();
8841                                self.generate_string_literal(comment)?;
8842                                comment_idx += 1;
8843                                break;
8844                            }
8845                            comment_idx += 1;
8846                        }
8847                    }
8848                    ConstraintType::Tags => {
8849                        // Find next Tags constraint (Snowflake)
8850                        for constraint in &col.constraints {
8851                            if let ColumnConstraint::Tags(tags) = constraint {
8852                                self.write_space();
8853                                self.write_keyword("TAG");
8854                                self.write(" (");
8855                                for (i, expr) in tags.expressions.iter().enumerate() {
8856                                    if i > 0 {
8857                                        self.write(", ");
8858                                    }
8859                                    self.generate_expression(expr)?;
8860                                }
8861                                self.write(")");
8862                                break;
8863                            }
8864                        }
8865                    }
8866                    ConstraintType::ComputedColumn => {
8867                        // Find next ComputedColumn constraint
8868                        for constraint in &col.constraints {
8869                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
8870                                self.write_space();
8871                                self.generate_computed_column_inline(cc)?;
8872                                break;
8873                            }
8874                        }
8875                    }
8876                    ConstraintType::GeneratedAsRow => {
8877                        // Find next GeneratedAsRow constraint
8878                        for constraint in &col.constraints {
8879                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
8880                                self.write_space();
8881                                self.generate_generated_as_row_inline(gar)?;
8882                                break;
8883                            }
8884                        }
8885                    }
8886                    ConstraintType::OnUpdate => {
8887                        if let Some(ref expr) = col.on_update {
8888                            self.write_space();
8889                            self.write_keyword("ON UPDATE");
8890                            self.write_space();
8891                            self.generate_expression(expr)?;
8892                        }
8893                    }
8894                    ConstraintType::Encode => {
8895                        if let Some(ref encoding) = col.encoding {
8896                            self.write_space();
8897                            self.write_keyword("ENCODE");
8898                            self.write_space();
8899                            self.write(encoding);
8900                        }
8901                    }
8902                    ConstraintType::Path => {
8903                        // Find next Path constraint
8904                        for constraint in &col.constraints {
8905                            if let ColumnConstraint::Path(path_expr) = constraint {
8906                                self.write_space();
8907                                self.write_keyword("PATH");
8908                                self.write_space();
8909                                self.generate_expression(path_expr)?;
8910                                break;
8911                            }
8912                        }
8913                    }
8914                }
8915            }
8916            if pending_not_null_after_identity {
8917                self.write_space();
8918                self.write_keyword("NOT NULL");
8919            }
8920        } else {
8921            // Legacy fixed order for backward compatibility
8922            if col.primary_key {
8923                self.write_space();
8924                self.write_keyword("PRIMARY KEY");
8925                if let Some(ref order) = col.primary_key_order {
8926                    self.write_space();
8927                    match order {
8928                        SortOrder::Asc => self.write_keyword("ASC"),
8929                        SortOrder::Desc => self.write_keyword("DESC"),
8930                    }
8931                }
8932            }
8933
8934            if col.unique {
8935                self.write_space();
8936                self.write_keyword("UNIQUE");
8937                // PostgreSQL 15+: NULLS NOT DISTINCT
8938                if col.unique_nulls_not_distinct {
8939                    self.write(" NULLS NOT DISTINCT");
8940                }
8941            }
8942
8943            match col.nullable {
8944                Some(false) => {
8945                    self.write_space();
8946                    self.write_keyword("NOT NULL");
8947                }
8948                Some(true) => {
8949                    self.write_space();
8950                    self.write_keyword("NULL");
8951                }
8952                None => {}
8953            }
8954
8955            if let Some(ref default) = col.default {
8956                self.write_space();
8957                self.write_keyword("DEFAULT");
8958                self.write_space();
8959                self.generate_expression(default)?;
8960            }
8961
8962            if col.auto_increment {
8963                self.write_space();
8964                self.generate_auto_increment_keyword(col)?;
8965            }
8966
8967            // Column-level constraints from Vec
8968            for constraint in &col.constraints {
8969                match constraint {
8970                    ColumnConstraint::References(fk_ref) => {
8971                        self.write_space();
8972                        if fk_ref.has_foreign_key_keywords {
8973                            self.write_keyword("FOREIGN KEY");
8974                            self.write_space();
8975                        }
8976                        self.write_keyword("REFERENCES");
8977                        self.write_space();
8978                        self.generate_table(&fk_ref.table)?;
8979                        if !fk_ref.columns.is_empty() {
8980                            self.write(" (");
8981                            for (i, c) in fk_ref.columns.iter().enumerate() {
8982                                if i > 0 {
8983                                    self.write(", ");
8984                                }
8985                                self.generate_identifier(c)?;
8986                            }
8987                            self.write(")");
8988                        }
8989                        self.generate_referential_actions(fk_ref)?;
8990                    }
8991                    ColumnConstraint::Check(expr) => {
8992                        self.write_space();
8993                        self.write_keyword("CHECK");
8994                        self.write(" (");
8995                        self.generate_expression(expr)?;
8996                        self.write(")");
8997                    }
8998                    ColumnConstraint::GeneratedAsIdentity(gen) => {
8999                        self.write_space();
9000                        // Redshift uses IDENTITY(start, increment) syntax
9001                        if matches!(
9002                            self.config.dialect,
9003                            Some(crate::dialects::DialectType::Redshift)
9004                        ) {
9005                            self.write_keyword("IDENTITY");
9006                            self.write("(");
9007                            if let Some(ref start) = gen.start {
9008                                self.generate_expression(start)?;
9009                            } else {
9010                                self.write("0");
9011                            }
9012                            self.write(", ");
9013                            if let Some(ref incr) = gen.increment {
9014                                self.generate_expression(incr)?;
9015                            } else {
9016                                self.write("1");
9017                            }
9018                            self.write(")");
9019                        } else {
9020                            self.write_keyword("GENERATED");
9021                            if gen.always {
9022                                self.write_space();
9023                                self.write_keyword("ALWAYS");
9024                            } else {
9025                                self.write_space();
9026                                self.write_keyword("BY DEFAULT");
9027                                if gen.on_null {
9028                                    self.write_space();
9029                                    self.write_keyword("ON NULL");
9030                                }
9031                            }
9032                            self.write_space();
9033                            self.write_keyword("AS IDENTITY");
9034
9035                            let has_options = gen.start.is_some()
9036                                || gen.increment.is_some()
9037                                || gen.minvalue.is_some()
9038                                || gen.maxvalue.is_some()
9039                                || gen.cycle.is_some();
9040                            if has_options {
9041                                self.write(" (");
9042                                let mut first = true;
9043                                if let Some(ref start) = gen.start {
9044                                    if !first {
9045                                        self.write(" ");
9046                                    }
9047                                    first = false;
9048                                    self.write_keyword("START WITH");
9049                                    self.write_space();
9050                                    self.generate_expression(start)?;
9051                                }
9052                                if let Some(ref incr) = gen.increment {
9053                                    if !first {
9054                                        self.write(" ");
9055                                    }
9056                                    first = false;
9057                                    self.write_keyword("INCREMENT BY");
9058                                    self.write_space();
9059                                    self.generate_expression(incr)?;
9060                                }
9061                                if let Some(ref minv) = gen.minvalue {
9062                                    if !first {
9063                                        self.write(" ");
9064                                    }
9065                                    first = false;
9066                                    self.write_keyword("MINVALUE");
9067                                    self.write_space();
9068                                    self.generate_expression(minv)?;
9069                                }
9070                                if let Some(ref maxv) = gen.maxvalue {
9071                                    if !first {
9072                                        self.write(" ");
9073                                    }
9074                                    first = false;
9075                                    self.write_keyword("MAXVALUE");
9076                                    self.write_space();
9077                                    self.generate_expression(maxv)?;
9078                                }
9079                                if let Some(cycle) = gen.cycle {
9080                                    if !first {
9081                                        self.write(" ");
9082                                    }
9083                                    if cycle {
9084                                        self.write_keyword("CYCLE");
9085                                    } else {
9086                                        self.write_keyword("NO CYCLE");
9087                                    }
9088                                }
9089                                self.write(")");
9090                            }
9091                        }
9092                    }
9093                    ColumnConstraint::Collate(collation) => {
9094                        self.write_space();
9095                        self.write_keyword("COLLATE");
9096                        self.write_space();
9097                        self.generate_identifier(collation)?;
9098                    }
9099                    ColumnConstraint::Comment(comment) => {
9100                        self.write_space();
9101                        self.write_keyword("COMMENT");
9102                        self.write_space();
9103                        self.generate_string_literal(comment)?;
9104                    }
9105                    ColumnConstraint::Path(path_expr) => {
9106                        self.write_space();
9107                        self.write_keyword("PATH");
9108                        self.write_space();
9109                        self.generate_expression(path_expr)?;
9110                    }
9111                    _ => {} // Other constraints handled above
9112                }
9113            }
9114
9115            // Redshift: ENCODE encoding_type (legacy path)
9116            if let Some(ref encoding) = col.encoding {
9117                self.write_space();
9118                self.write_keyword("ENCODE");
9119                self.write_space();
9120                self.write(encoding);
9121            }
9122        }
9123
9124        // ClickHouse: CODEC(...)
9125        if let Some(ref codec) = col.codec {
9126            self.write_space();
9127            self.write_keyword("CODEC");
9128            self.write("(");
9129            self.write(codec);
9130            self.write(")");
9131        }
9132
9133        // ClickHouse: EPHEMERAL [expr]
9134        if let Some(ref ephemeral) = col.ephemeral {
9135            self.write_space();
9136            self.write_keyword("EPHEMERAL");
9137            if let Some(ref expr) = ephemeral {
9138                self.write_space();
9139                self.generate_expression(expr)?;
9140            }
9141        }
9142
9143        // ClickHouse: MATERIALIZED expr
9144        if let Some(ref mat_expr) = col.materialized_expr {
9145            self.write_space();
9146            self.write_keyword("MATERIALIZED");
9147            self.write_space();
9148            self.generate_expression(mat_expr)?;
9149        }
9150
9151        // ClickHouse: ALIAS expr
9152        if let Some(ref alias_expr) = col.alias_expr {
9153            self.write_space();
9154            self.write_keyword("ALIAS");
9155            self.write_space();
9156            self.generate_expression(alias_expr)?;
9157        }
9158
9159        // ClickHouse: TTL expr
9160        if let Some(ref ttl_expr) = col.ttl_expr {
9161            self.write_space();
9162            self.write_keyword("TTL");
9163            self.write_space();
9164            self.generate_expression(ttl_expr)?;
9165        }
9166
9167        // TSQL: NOT FOR REPLICATION
9168        if col.not_for_replication
9169            && matches!(
9170                self.config.dialect,
9171                Some(crate::dialects::DialectType::TSQL)
9172                    | Some(crate::dialects::DialectType::Fabric)
9173            )
9174        {
9175            self.write_space();
9176            self.write_keyword("NOT FOR REPLICATION");
9177        }
9178
9179        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
9180        if !col.options.is_empty() {
9181            self.write_space();
9182            self.generate_options_clause(&col.options)?;
9183        }
9184
9185        // SQLite: Inline PRIMARY KEY from table constraint
9186        // This comes at the end, after all existing column constraints
9187        if !col.primary_key
9188            && self
9189                .sqlite_inline_pk_columns
9190                .contains(&col.name.name.to_ascii_lowercase())
9191        {
9192            self.write_space();
9193            self.write_keyword("PRIMARY KEY");
9194        }
9195
9196        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
9197        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
9198        if serial_expansion.is_some() {
9199            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
9200                self.write_space();
9201                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
9202            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
9203                self.write_space();
9204                self.write_keyword("NOT NULL");
9205            }
9206        }
9207
9208        Ok(())
9209    }
9210
9211    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
9212        match constraint {
9213            TableConstraint::PrimaryKey {
9214                name,
9215                columns,
9216                include_columns,
9217                modifiers,
9218                has_constraint_keyword,
9219            } => {
9220                if let Some(ref n) = name {
9221                    if *has_constraint_keyword {
9222                        self.write_keyword("CONSTRAINT");
9223                        self.write_space();
9224                        self.generate_identifier(n)?;
9225                        self.write_space();
9226                    }
9227                }
9228                self.write_keyword("PRIMARY KEY");
9229                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9230                if let Some(ref clustered) = modifiers.clustered {
9231                    self.write_space();
9232                    self.write_keyword(clustered);
9233                }
9234                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
9235                if let Some(ref n) = name {
9236                    if !*has_constraint_keyword {
9237                        self.write_space();
9238                        self.generate_identifier(n)?;
9239                    }
9240                }
9241                self.write(" (");
9242                for (i, col) in columns.iter().enumerate() {
9243                    if i > 0 {
9244                        self.write(", ");
9245                    }
9246                    self.generate_identifier(col)?;
9247                }
9248                self.write(")");
9249                if !include_columns.is_empty() {
9250                    self.write_space();
9251                    self.write_keyword("INCLUDE");
9252                    self.write(" (");
9253                    for (i, col) in include_columns.iter().enumerate() {
9254                        if i > 0 {
9255                            self.write(", ");
9256                        }
9257                        self.generate_identifier(col)?;
9258                    }
9259                    self.write(")");
9260                }
9261                self.generate_constraint_modifiers(modifiers);
9262            }
9263            TableConstraint::Unique {
9264                name,
9265                columns,
9266                columns_parenthesized,
9267                modifiers,
9268                has_constraint_keyword,
9269                nulls_not_distinct,
9270            } => {
9271                if let Some(ref n) = name {
9272                    if *has_constraint_keyword {
9273                        self.write_keyword("CONSTRAINT");
9274                        self.write_space();
9275                        self.generate_identifier(n)?;
9276                        self.write_space();
9277                    }
9278                }
9279                self.write_keyword("UNIQUE");
9280                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9281                if let Some(ref clustered) = modifiers.clustered {
9282                    self.write_space();
9283                    self.write_keyword(clustered);
9284                }
9285                // PostgreSQL 15+: NULLS NOT DISTINCT
9286                if *nulls_not_distinct {
9287                    self.write(" NULLS NOT DISTINCT");
9288                }
9289                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
9290                if let Some(ref n) = name {
9291                    if !*has_constraint_keyword {
9292                        self.write_space();
9293                        self.generate_identifier(n)?;
9294                    }
9295                }
9296                if *columns_parenthesized {
9297                    self.write(" (");
9298                    for (i, col) in columns.iter().enumerate() {
9299                        if i > 0 {
9300                            self.write(", ");
9301                        }
9302                        self.generate_identifier(col)?;
9303                    }
9304                    self.write(")");
9305                } else {
9306                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
9307                    for col in columns.iter() {
9308                        self.write_space();
9309                        self.generate_identifier(col)?;
9310                    }
9311                }
9312                self.generate_constraint_modifiers(modifiers);
9313            }
9314            TableConstraint::ForeignKey {
9315                name,
9316                columns,
9317                references,
9318                on_delete,
9319                on_update,
9320                modifiers,
9321            } => {
9322                if let Some(ref n) = name {
9323                    self.write_keyword("CONSTRAINT");
9324                    self.write_space();
9325                    self.generate_identifier(n)?;
9326                    self.write_space();
9327                }
9328                self.write_keyword("FOREIGN KEY");
9329                self.write(" (");
9330                for (i, col) in columns.iter().enumerate() {
9331                    if i > 0 {
9332                        self.write(", ");
9333                    }
9334                    self.generate_identifier(col)?;
9335                }
9336                self.write(")");
9337                if let Some(ref refs) = references {
9338                    self.write(" ");
9339                    self.write_keyword("REFERENCES");
9340                    self.write_space();
9341                    self.generate_table(&refs.table)?;
9342                    if !refs.columns.is_empty() {
9343                        if self.config.pretty {
9344                            self.write(" (");
9345                            self.write_newline();
9346                            self.indent_level += 1;
9347                            for (i, col) in refs.columns.iter().enumerate() {
9348                                if i > 0 {
9349                                    self.write(",");
9350                                    self.write_newline();
9351                                }
9352                                self.write_indent();
9353                                self.generate_identifier(col)?;
9354                            }
9355                            self.indent_level -= 1;
9356                            self.write_newline();
9357                            self.write_indent();
9358                            self.write(")");
9359                        } else {
9360                            self.write(" (");
9361                            for (i, col) in refs.columns.iter().enumerate() {
9362                                if i > 0 {
9363                                    self.write(", ");
9364                                }
9365                                self.generate_identifier(col)?;
9366                            }
9367                            self.write(")");
9368                        }
9369                    }
9370                    self.generate_referential_actions(refs)?;
9371                } else {
9372                    // No REFERENCES - output ON DELETE/ON UPDATE directly
9373                    if let Some(ref action) = on_delete {
9374                        self.write_space();
9375                        self.write_keyword("ON DELETE");
9376                        self.write_space();
9377                        self.generate_referential_action(action);
9378                    }
9379                    if let Some(ref action) = on_update {
9380                        self.write_space();
9381                        self.write_keyword("ON UPDATE");
9382                        self.write_space();
9383                        self.generate_referential_action(action);
9384                    }
9385                }
9386                self.generate_constraint_modifiers(modifiers);
9387            }
9388            TableConstraint::Check {
9389                name,
9390                expression,
9391                modifiers,
9392            } => {
9393                if let Some(ref n) = name {
9394                    self.write_keyword("CONSTRAINT");
9395                    self.write_space();
9396                    self.generate_identifier(n)?;
9397                    self.write_space();
9398                }
9399                self.write_keyword("CHECK");
9400                self.write(" (");
9401                self.generate_expression(expression)?;
9402                self.write(")");
9403                self.generate_constraint_modifiers(modifiers);
9404            }
9405            TableConstraint::Assume { name, expression } => {
9406                if let Some(ref n) = name {
9407                    self.write_keyword("CONSTRAINT");
9408                    self.write_space();
9409                    self.generate_identifier(n)?;
9410                    self.write_space();
9411                }
9412                self.write_keyword("ASSUME");
9413                self.write(" (");
9414                self.generate_expression(expression)?;
9415                self.write(")");
9416            }
9417            TableConstraint::Default {
9418                name,
9419                expression,
9420                column,
9421            } => {
9422                if let Some(ref n) = name {
9423                    self.write_keyword("CONSTRAINT");
9424                    self.write_space();
9425                    self.generate_identifier(n)?;
9426                    self.write_space();
9427                }
9428                self.write_keyword("DEFAULT");
9429                self.write_space();
9430                self.generate_expression(expression)?;
9431                self.write_space();
9432                self.write_keyword("FOR");
9433                self.write_space();
9434                self.generate_identifier(column)?;
9435            }
9436            TableConstraint::Index {
9437                name,
9438                columns,
9439                kind,
9440                modifiers,
9441                use_key_keyword,
9442                expression,
9443                index_type,
9444                granularity,
9445            } => {
9446                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
9447                if expression.is_some() {
9448                    self.write_keyword("INDEX");
9449                    if let Some(ref n) = name {
9450                        self.write_space();
9451                        self.generate_identifier(n)?;
9452                    }
9453                    if let Some(ref expr) = expression {
9454                        self.write_space();
9455                        self.generate_expression(expr)?;
9456                    }
9457                    if let Some(ref idx_type) = index_type {
9458                        self.write_space();
9459                        self.write_keyword("TYPE");
9460                        self.write_space();
9461                        self.generate_expression(idx_type)?;
9462                    }
9463                    if let Some(ref gran) = granularity {
9464                        self.write_space();
9465                        self.write_keyword("GRANULARITY");
9466                        self.write_space();
9467                        self.generate_expression(gran)?;
9468                    }
9469                } else {
9470                    // Standard INDEX syntax
9471                    // Determine the index keyword to use
9472                    // MySQL normalizes KEY to INDEX
9473                    use crate::dialects::DialectType;
9474                    let index_keyword = if *use_key_keyword
9475                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
9476                    {
9477                        "KEY"
9478                    } else {
9479                        "INDEX"
9480                    };
9481
9482                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
9483                    if let Some(ref k) = kind {
9484                        self.write_keyword(k);
9485                        // For UNIQUE, don't add INDEX/KEY keyword
9486                        if k != "UNIQUE" {
9487                            self.write_space();
9488                            self.write_keyword(index_keyword);
9489                        }
9490                    } else {
9491                        self.write_keyword(index_keyword);
9492                    }
9493
9494                    // Output USING before name if using_before_columns is true and there's no name
9495                    if modifiers.using_before_columns && name.is_none() {
9496                        if let Some(ref using) = modifiers.using {
9497                            self.write_space();
9498                            self.write_keyword("USING");
9499                            self.write_space();
9500                            self.write_keyword(using);
9501                        }
9502                    }
9503
9504                    // Output index name if present
9505                    if let Some(ref n) = name {
9506                        self.write_space();
9507                        self.generate_identifier(n)?;
9508                    }
9509
9510                    // Output USING after name but before columns if using_before_columns and there's a name
9511                    if modifiers.using_before_columns && name.is_some() {
9512                        if let Some(ref using) = modifiers.using {
9513                            self.write_space();
9514                            self.write_keyword("USING");
9515                            self.write_space();
9516                            self.write_keyword(using);
9517                        }
9518                    }
9519
9520                    // Output columns
9521                    self.write(" (");
9522                    for (i, col) in columns.iter().enumerate() {
9523                        if i > 0 {
9524                            self.write(", ");
9525                        }
9526                        self.generate_identifier(col)?;
9527                    }
9528                    self.write(")");
9529
9530                    // Output USING after columns if not using_before_columns
9531                    if !modifiers.using_before_columns {
9532                        if let Some(ref using) = modifiers.using {
9533                            self.write_space();
9534                            self.write_keyword("USING");
9535                            self.write_space();
9536                            self.write_keyword(using);
9537                        }
9538                    }
9539
9540                    // Output other constraint modifiers (but skip USING since we already handled it)
9541                    self.generate_constraint_modifiers_without_using(modifiers);
9542                }
9543            }
9544            TableConstraint::Projection { name, expression } => {
9545                // ClickHouse: PROJECTION name (SELECT ...)
9546                self.write_keyword("PROJECTION");
9547                self.write_space();
9548                self.generate_identifier(name)?;
9549                self.write(" (");
9550                self.generate_expression(expression)?;
9551                self.write(")");
9552            }
9553            TableConstraint::Like { source, options } => {
9554                self.write_keyword("LIKE");
9555                self.write_space();
9556                self.generate_table(source)?;
9557                for (action, prop) in options {
9558                    self.write_space();
9559                    match action {
9560                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
9561                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
9562                    }
9563                    self.write_space();
9564                    self.write_keyword(prop);
9565                }
9566            }
9567            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
9568                self.write_keyword("PERIOD FOR SYSTEM_TIME");
9569                self.write(" (");
9570                self.generate_identifier(start_col)?;
9571                self.write(", ");
9572                self.generate_identifier(end_col)?;
9573                self.write(")");
9574            }
9575            TableConstraint::Exclude {
9576                name,
9577                using,
9578                elements,
9579                include_columns,
9580                where_clause,
9581                with_params,
9582                using_index_tablespace,
9583                modifiers: _,
9584            } => {
9585                if let Some(ref n) = name {
9586                    self.write_keyword("CONSTRAINT");
9587                    self.write_space();
9588                    self.generate_identifier(n)?;
9589                    self.write_space();
9590                }
9591                self.write_keyword("EXCLUDE");
9592                if let Some(ref method) = using {
9593                    self.write_space();
9594                    self.write_keyword("USING");
9595                    self.write_space();
9596                    self.write(method);
9597                    self.write("(");
9598                } else {
9599                    self.write(" (");
9600                }
9601                for (i, elem) in elements.iter().enumerate() {
9602                    if i > 0 {
9603                        self.write(", ");
9604                    }
9605                    self.write(&elem.expression);
9606                    self.write_space();
9607                    self.write_keyword("WITH");
9608                    self.write_space();
9609                    self.write(&elem.operator);
9610                }
9611                self.write(")");
9612                if !include_columns.is_empty() {
9613                    self.write_space();
9614                    self.write_keyword("INCLUDE");
9615                    self.write(" (");
9616                    for (i, col) in include_columns.iter().enumerate() {
9617                        if i > 0 {
9618                            self.write(", ");
9619                        }
9620                        self.generate_identifier(col)?;
9621                    }
9622                    self.write(")");
9623                }
9624                if !with_params.is_empty() {
9625                    self.write_space();
9626                    self.write_keyword("WITH");
9627                    self.write(" (");
9628                    for (i, (key, val)) in with_params.iter().enumerate() {
9629                        if i > 0 {
9630                            self.write(", ");
9631                        }
9632                        self.write(key);
9633                        self.write("=");
9634                        self.write(val);
9635                    }
9636                    self.write(")");
9637                }
9638                if let Some(ref tablespace) = using_index_tablespace {
9639                    self.write_space();
9640                    self.write_keyword("USING INDEX TABLESPACE");
9641                    self.write_space();
9642                    self.write(tablespace);
9643                }
9644                if let Some(ref where_expr) = where_clause {
9645                    self.write_space();
9646                    self.write_keyword("WHERE");
9647                    self.write(" (");
9648                    self.generate_expression(where_expr)?;
9649                    self.write(")");
9650                }
9651            }
9652            TableConstraint::Tags(tags) => {
9653                self.write_keyword("TAG");
9654                self.write(" (");
9655                for (i, expr) in tags.expressions.iter().enumerate() {
9656                    if i > 0 {
9657                        self.write(", ");
9658                    }
9659                    self.generate_expression(expr)?;
9660                }
9661                self.write(")");
9662            }
9663            TableConstraint::InitiallyDeferred { deferred } => {
9664                self.write_keyword("INITIALLY");
9665                self.write_space();
9666                if *deferred {
9667                    self.write_keyword("DEFERRED");
9668                } else {
9669                    self.write_keyword("IMMEDIATE");
9670                }
9671            }
9672        }
9673        Ok(())
9674    }
9675
9676    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9677        // Output USING BTREE/HASH (MySQL) - comes first
9678        if let Some(using) = &modifiers.using {
9679            self.write_space();
9680            self.write_keyword("USING");
9681            self.write_space();
9682            self.write_keyword(using);
9683        }
9684        // Output ENFORCED/NOT ENFORCED
9685        if let Some(enforced) = modifiers.enforced {
9686            self.write_space();
9687            if enforced {
9688                self.write_keyword("ENFORCED");
9689            } else {
9690                self.write_keyword("NOT ENFORCED");
9691            }
9692        }
9693        // Output DEFERRABLE/NOT DEFERRABLE
9694        if let Some(deferrable) = modifiers.deferrable {
9695            self.write_space();
9696            if deferrable {
9697                self.write_keyword("DEFERRABLE");
9698            } else {
9699                self.write_keyword("NOT DEFERRABLE");
9700            }
9701        }
9702        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9703        if let Some(initially_deferred) = modifiers.initially_deferred {
9704            self.write_space();
9705            if initially_deferred {
9706                self.write_keyword("INITIALLY DEFERRED");
9707            } else {
9708                self.write_keyword("INITIALLY IMMEDIATE");
9709            }
9710        }
9711        // Output NORELY
9712        if modifiers.norely {
9713            self.write_space();
9714            self.write_keyword("NORELY");
9715        }
9716        // Output RELY
9717        if modifiers.rely {
9718            self.write_space();
9719            self.write_keyword("RELY");
9720        }
9721        // Output NOT VALID (PostgreSQL)
9722        if modifiers.not_valid {
9723            self.write_space();
9724            self.write_keyword("NOT VALID");
9725        }
9726        // Output ON CONFLICT (SQLite)
9727        if let Some(on_conflict) = &modifiers.on_conflict {
9728            self.write_space();
9729            self.write_keyword("ON CONFLICT");
9730            self.write_space();
9731            self.write_keyword(on_conflict);
9732        }
9733        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
9734        if !modifiers.with_options.is_empty() {
9735            self.write_space();
9736            self.write_keyword("WITH");
9737            self.write(" (");
9738            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
9739                if i > 0 {
9740                    self.write(", ");
9741                }
9742                self.write(key);
9743                self.write("=");
9744                self.write(value);
9745            }
9746            self.write(")");
9747        }
9748        // Output TSQL ON filegroup
9749        if let Some(ref fg) = modifiers.on_filegroup {
9750            self.write_space();
9751            self.write_keyword("ON");
9752            self.write_space();
9753            let _ = self.generate_identifier(fg);
9754        }
9755    }
9756
9757    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
9758    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
9759        // Output ENFORCED/NOT ENFORCED
9760        if let Some(enforced) = modifiers.enforced {
9761            self.write_space();
9762            if enforced {
9763                self.write_keyword("ENFORCED");
9764            } else {
9765                self.write_keyword("NOT ENFORCED");
9766            }
9767        }
9768        // Output DEFERRABLE/NOT DEFERRABLE
9769        if let Some(deferrable) = modifiers.deferrable {
9770            self.write_space();
9771            if deferrable {
9772                self.write_keyword("DEFERRABLE");
9773            } else {
9774                self.write_keyword("NOT DEFERRABLE");
9775            }
9776        }
9777        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
9778        if let Some(initially_deferred) = modifiers.initially_deferred {
9779            self.write_space();
9780            if initially_deferred {
9781                self.write_keyword("INITIALLY DEFERRED");
9782            } else {
9783                self.write_keyword("INITIALLY IMMEDIATE");
9784            }
9785        }
9786        // Output NORELY
9787        if modifiers.norely {
9788            self.write_space();
9789            self.write_keyword("NORELY");
9790        }
9791        // Output RELY
9792        if modifiers.rely {
9793            self.write_space();
9794            self.write_keyword("RELY");
9795        }
9796        // Output NOT VALID (PostgreSQL)
9797        if modifiers.not_valid {
9798            self.write_space();
9799            self.write_keyword("NOT VALID");
9800        }
9801        // Output ON CONFLICT (SQLite)
9802        if let Some(on_conflict) = &modifiers.on_conflict {
9803            self.write_space();
9804            self.write_keyword("ON CONFLICT");
9805            self.write_space();
9806            self.write_keyword(on_conflict);
9807        }
9808        // Output MySQL index-specific modifiers
9809        self.generate_index_specific_modifiers(modifiers);
9810    }
9811
9812    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
9813    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
9814        if let Some(ref comment) = modifiers.comment {
9815            self.write_space();
9816            self.write_keyword("COMMENT");
9817            self.write(" '");
9818            self.write(comment);
9819            self.write("'");
9820        }
9821        if let Some(visible) = modifiers.visible {
9822            self.write_space();
9823            if visible {
9824                self.write_keyword("VISIBLE");
9825            } else {
9826                self.write_keyword("INVISIBLE");
9827            }
9828        }
9829        if let Some(ref attr) = modifiers.engine_attribute {
9830            self.write_space();
9831            self.write_keyword("ENGINE_ATTRIBUTE");
9832            self.write(" = '");
9833            self.write(attr);
9834            self.write("'");
9835        }
9836        if let Some(ref parser) = modifiers.with_parser {
9837            self.write_space();
9838            self.write_keyword("WITH PARSER");
9839            self.write_space();
9840            self.write(parser);
9841        }
9842    }
9843
9844    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
9845        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
9846        if !fk_ref.match_after_actions {
9847            if let Some(ref match_type) = fk_ref.match_type {
9848                self.write_space();
9849                self.write_keyword("MATCH");
9850                self.write_space();
9851                match match_type {
9852                    MatchType::Full => self.write_keyword("FULL"),
9853                    MatchType::Partial => self.write_keyword("PARTIAL"),
9854                    MatchType::Simple => self.write_keyword("SIMPLE"),
9855                }
9856            }
9857        }
9858
9859        // Output ON UPDATE and ON DELETE in the original order
9860        if fk_ref.on_update_first {
9861            if let Some(ref action) = fk_ref.on_update {
9862                self.write_space();
9863                self.write_keyword("ON UPDATE");
9864                self.write_space();
9865                self.generate_referential_action(action);
9866            }
9867            if let Some(ref action) = fk_ref.on_delete {
9868                self.write_space();
9869                self.write_keyword("ON DELETE");
9870                self.write_space();
9871                self.generate_referential_action(action);
9872            }
9873        } else {
9874            if let Some(ref action) = fk_ref.on_delete {
9875                self.write_space();
9876                self.write_keyword("ON DELETE");
9877                self.write_space();
9878                self.generate_referential_action(action);
9879            }
9880            if let Some(ref action) = fk_ref.on_update {
9881                self.write_space();
9882                self.write_keyword("ON UPDATE");
9883                self.write_space();
9884                self.generate_referential_action(action);
9885            }
9886        }
9887
9888        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
9889        if fk_ref.match_after_actions {
9890            if let Some(ref match_type) = fk_ref.match_type {
9891                self.write_space();
9892                self.write_keyword("MATCH");
9893                self.write_space();
9894                match match_type {
9895                    MatchType::Full => self.write_keyword("FULL"),
9896                    MatchType::Partial => self.write_keyword("PARTIAL"),
9897                    MatchType::Simple => self.write_keyword("SIMPLE"),
9898                }
9899            }
9900        }
9901
9902        // DEFERRABLE / NOT DEFERRABLE
9903        if let Some(deferrable) = fk_ref.deferrable {
9904            self.write_space();
9905            if deferrable {
9906                self.write_keyword("DEFERRABLE");
9907            } else {
9908                self.write_keyword("NOT DEFERRABLE");
9909            }
9910        }
9911
9912        Ok(())
9913    }
9914
9915    fn generate_referential_action(&mut self, action: &ReferentialAction) {
9916        match action {
9917            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
9918            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
9919            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
9920            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
9921            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
9922        }
9923    }
9924
9925    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
9926        // TSQL: IF NOT OBJECT_ID(...) IS NULL BEGIN DROP TABLE ...; END
9927        if let Some(ref object_id_args) = dt.object_id_args {
9928            if matches!(
9929                self.config.dialect,
9930                Some(crate::dialects::DialectType::TSQL)
9931                    | Some(crate::dialects::DialectType::Fabric)
9932            ) {
9933                self.write_keyword("IF NOT OBJECT_ID");
9934                self.write("(");
9935                self.write(object_id_args);
9936                self.write(")");
9937                self.write_space();
9938                self.write_keyword("IS NULL BEGIN DROP TABLE");
9939                self.write_space();
9940                for (i, table) in dt.names.iter().enumerate() {
9941                    if i > 0 {
9942                        self.write(", ");
9943                    }
9944                    self.generate_table(table)?;
9945                }
9946                self.write("; ");
9947                self.write_keyword("END");
9948                return Ok(());
9949            }
9950        }
9951
9952        // Athena: DROP TABLE uses Hive engine (backticks)
9953        let saved_athena_hive_context = self.athena_hive_context;
9954        if matches!(
9955            self.config.dialect,
9956            Some(crate::dialects::DialectType::Athena)
9957        ) {
9958            self.athena_hive_context = true;
9959        }
9960
9961        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
9962        for comment in &dt.leading_comments {
9963            self.write_formatted_comment(comment);
9964            self.write_space();
9965        }
9966        if dt.iceberg {
9967            self.write_keyword("DROP ICEBERG TABLE");
9968        } else {
9969            self.write_keyword("DROP TABLE");
9970        }
9971
9972        if dt.if_exists {
9973            self.write_space();
9974            self.write_keyword("IF EXISTS");
9975        }
9976
9977        self.write_space();
9978        for (i, table) in dt.names.iter().enumerate() {
9979            if i > 0 {
9980                self.write(", ");
9981            }
9982            self.generate_table(table)?;
9983        }
9984
9985        if dt.cascade_constraints {
9986            self.write_space();
9987            self.write_keyword("CASCADE CONSTRAINTS");
9988        } else if dt.cascade {
9989            self.write_space();
9990            self.write_keyword("CASCADE");
9991        }
9992
9993        if dt.restrict {
9994            self.write_space();
9995            self.write_keyword("RESTRICT");
9996        }
9997
9998        if dt.purge {
9999            self.write_space();
10000            self.write_keyword("PURGE");
10001        }
10002
10003        if dt.sync {
10004            self.write_space();
10005            self.write_keyword("SYNC");
10006        }
10007
10008        // Restore Athena Hive context
10009        self.athena_hive_context = saved_athena_hive_context;
10010
10011        Ok(())
10012    }
10013
10014    fn generate_undrop(&mut self, u: &Undrop) -> Result<()> {
10015        self.write_keyword("UNDROP");
10016        self.write_space();
10017        self.write_keyword(&u.kind);
10018        if u.if_exists {
10019            self.write_space();
10020            self.write_keyword("IF EXISTS");
10021        }
10022        self.write_space();
10023        self.generate_table(&u.name)?;
10024        Ok(())
10025    }
10026
10027    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
10028        // Athena: ALTER TABLE uses Hive engine (backticks)
10029        let saved_athena_hive_context = self.athena_hive_context;
10030        if matches!(
10031            self.config.dialect,
10032            Some(crate::dialects::DialectType::Athena)
10033        ) {
10034            self.athena_hive_context = true;
10035        }
10036
10037        self.write_keyword("ALTER");
10038        // Write table modifier (e.g., ICEBERG) unless target is DuckDB
10039        if let Some(ref modifier) = at.table_modifier {
10040            if !matches!(
10041                self.config.dialect,
10042                Some(crate::dialects::DialectType::DuckDB)
10043            ) {
10044                self.write_space();
10045                self.write_keyword(modifier);
10046            }
10047        }
10048        self.write(" ");
10049        self.write_keyword("TABLE");
10050        if at.if_exists {
10051            self.write_space();
10052            self.write_keyword("IF EXISTS");
10053        }
10054        self.write_space();
10055        self.generate_table(&at.name)?;
10056
10057        // ClickHouse: ON CLUSTER clause
10058        if let Some(ref on_cluster) = at.on_cluster {
10059            self.write_space();
10060            self.generate_on_cluster(on_cluster)?;
10061        }
10062
10063        // Hive: PARTITION(key=value, ...) clause
10064        if let Some(ref partition) = at.partition {
10065            self.write_space();
10066            self.write_keyword("PARTITION");
10067            self.write("(");
10068            for (i, (key, value)) in partition.iter().enumerate() {
10069                if i > 0 {
10070                    self.write(", ");
10071                }
10072                self.generate_identifier(key)?;
10073                self.write(" = ");
10074                self.generate_expression(value)?;
10075            }
10076            self.write(")");
10077        }
10078
10079        // TSQL: WITH CHECK / WITH NOCHECK modifier
10080        if let Some(ref with_check) = at.with_check {
10081            self.write_space();
10082            self.write_keyword(with_check);
10083        }
10084
10085        if self.config.pretty {
10086            // In pretty mode, format actions with newlines and indentation
10087            self.write_newline();
10088            self.indent_level += 1;
10089            for (i, action) in at.actions.iter().enumerate() {
10090                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
10091                let is_continuation = i > 0
10092                    && matches!(
10093                        (&at.actions[i - 1], action),
10094                        (
10095                            AlterTableAction::AddColumn { .. },
10096                            AlterTableAction::AddColumn { .. }
10097                        ) | (
10098                            AlterTableAction::AddConstraint(_),
10099                            AlterTableAction::AddConstraint(_)
10100                        )
10101                    );
10102                if i > 0 {
10103                    self.write(",");
10104                    self.write_newline();
10105                }
10106                self.write_indent();
10107                self.generate_alter_action_with_continuation(action, is_continuation)?;
10108            }
10109            self.indent_level -= 1;
10110        } else {
10111            for (i, action) in at.actions.iter().enumerate() {
10112                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
10113                let is_continuation = i > 0
10114                    && matches!(
10115                        (&at.actions[i - 1], action),
10116                        (
10117                            AlterTableAction::AddColumn { .. },
10118                            AlterTableAction::AddColumn { .. }
10119                        ) | (
10120                            AlterTableAction::AddConstraint(_),
10121                            AlterTableAction::AddConstraint(_)
10122                        )
10123                    );
10124                if i > 0 {
10125                    self.write(",");
10126                }
10127                self.write_space();
10128                self.generate_alter_action_with_continuation(action, is_continuation)?;
10129            }
10130        }
10131
10132        // MySQL ALTER TABLE trailing options
10133        if let Some(ref algorithm) = at.algorithm {
10134            self.write(", ");
10135            self.write_keyword("ALGORITHM");
10136            self.write("=");
10137            self.write_keyword(algorithm);
10138        }
10139        if let Some(ref lock) = at.lock {
10140            self.write(", ");
10141            self.write_keyword("LOCK");
10142            self.write("=");
10143            self.write_keyword(lock);
10144        }
10145
10146        // Restore Athena Hive context
10147        self.athena_hive_context = saved_athena_hive_context;
10148
10149        Ok(())
10150    }
10151
10152    fn generate_alter_action_with_continuation(
10153        &mut self,
10154        action: &AlterTableAction,
10155        is_continuation: bool,
10156    ) -> Result<()> {
10157        match action {
10158            AlterTableAction::AddColumn {
10159                column,
10160                if_not_exists,
10161                position,
10162            } => {
10163                use crate::dialects::DialectType;
10164                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
10165                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
10166                // For other dialects, repeat ADD COLUMN for each
10167                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
10168                let is_tsql_like = matches!(
10169                    self.config.dialect,
10170                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
10171                );
10172                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
10173                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
10174
10175                if is_continuation && (is_snowflake || is_tsql_like) {
10176                    // Don't write ADD keyword for continuation in Snowflake/TSQL
10177                } else if is_snowflake {
10178                    self.write_keyword("ADD");
10179                    self.write_space();
10180                } else if is_athena {
10181                    // Athena uses ADD COLUMNS (col_def) syntax
10182                    self.write_keyword("ADD COLUMNS");
10183                    self.write(" (");
10184                } else if self.config.alter_table_include_column_keyword {
10185                    self.write_keyword("ADD COLUMN");
10186                    self.write_space();
10187                } else {
10188                    // Dialects like Oracle and TSQL don't use COLUMN keyword
10189                    self.write_keyword("ADD");
10190                    self.write_space();
10191                }
10192
10193                if *if_not_exists {
10194                    self.write_keyword("IF NOT EXISTS");
10195                    self.write_space();
10196                }
10197                self.generate_column_def(column)?;
10198
10199                // Close parenthesis for Athena
10200                if is_athena {
10201                    self.write(")");
10202                }
10203
10204                // Column position (FIRST or AFTER)
10205                if let Some(pos) = position {
10206                    self.write_space();
10207                    match pos {
10208                        ColumnPosition::First => self.write_keyword("FIRST"),
10209                        ColumnPosition::After(col_name) => {
10210                            self.write_keyword("AFTER");
10211                            self.write_space();
10212                            self.generate_identifier(col_name)?;
10213                        }
10214                    }
10215                }
10216            }
10217            AlterTableAction::DropColumn {
10218                name,
10219                if_exists,
10220                cascade,
10221            } => {
10222                self.write_keyword("DROP COLUMN");
10223                if *if_exists {
10224                    self.write_space();
10225                    self.write_keyword("IF EXISTS");
10226                }
10227                self.write_space();
10228                self.generate_identifier(name)?;
10229                if *cascade {
10230                    self.write_space();
10231                    self.write_keyword("CASCADE");
10232                }
10233            }
10234            AlterTableAction::DropColumns { names } => {
10235                self.write_keyword("DROP COLUMNS");
10236                self.write(" (");
10237                for (i, name) in names.iter().enumerate() {
10238                    if i > 0 {
10239                        self.write(", ");
10240                    }
10241                    self.generate_identifier(name)?;
10242                }
10243                self.write(")");
10244            }
10245            AlterTableAction::RenameColumn {
10246                old_name,
10247                new_name,
10248                if_exists,
10249            } => {
10250                self.write_keyword("RENAME COLUMN");
10251                if *if_exists {
10252                    self.write_space();
10253                    self.write_keyword("IF EXISTS");
10254                }
10255                self.write_space();
10256                self.generate_identifier(old_name)?;
10257                self.write_space();
10258                self.write_keyword("TO");
10259                self.write_space();
10260                self.generate_identifier(new_name)?;
10261            }
10262            AlterTableAction::AlterColumn {
10263                name,
10264                action,
10265                use_modify_keyword,
10266            } => {
10267                use crate::dialects::DialectType;
10268                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
10269                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
10270                let use_modify = *use_modify_keyword
10271                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
10272                        && matches!(action, AlterColumnAction::SetDataType { .. }));
10273                if use_modify {
10274                    self.write_keyword("MODIFY COLUMN");
10275                    self.write_space();
10276                    self.generate_identifier(name)?;
10277                    // For MODIFY COLUMN, output the type directly
10278                    if let AlterColumnAction::SetDataType {
10279                        data_type,
10280                        using: _,
10281                        collate,
10282                    } = action
10283                    {
10284                        self.write_space();
10285                        self.generate_data_type(data_type)?;
10286                        // Output COLLATE clause if present
10287                        if let Some(collate_name) = collate {
10288                            self.write_space();
10289                            self.write_keyword("COLLATE");
10290                            self.write_space();
10291                            // Output as single-quoted string
10292                            self.write(&format!("'{}'", collate_name));
10293                        }
10294                    } else {
10295                        self.write_space();
10296                        self.generate_alter_column_action(action)?;
10297                    }
10298                } else if matches!(self.config.dialect, Some(DialectType::Hive))
10299                    && matches!(action, AlterColumnAction::SetDataType { .. })
10300                {
10301                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
10302                    self.write_keyword("CHANGE COLUMN");
10303                    self.write_space();
10304                    self.generate_identifier(name)?;
10305                    self.write_space();
10306                    self.generate_identifier(name)?;
10307                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
10308                        self.write_space();
10309                        self.generate_data_type(data_type)?;
10310                    }
10311                } else {
10312                    self.write_keyword("ALTER COLUMN");
10313                    self.write_space();
10314                    self.generate_identifier(name)?;
10315                    self.write_space();
10316                    self.generate_alter_column_action(action)?;
10317                }
10318            }
10319            AlterTableAction::RenameTable(new_name) => {
10320                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
10321                let mysql_like = matches!(
10322                    self.config.dialect,
10323                    Some(DialectType::MySQL)
10324                        | Some(DialectType::Doris)
10325                        | Some(DialectType::StarRocks)
10326                        | Some(DialectType::SingleStore)
10327                );
10328                if mysql_like {
10329                    self.write_keyword("RENAME");
10330                } else {
10331                    self.write_keyword("RENAME TO");
10332                }
10333                self.write_space();
10334                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
10335                let rename_table_with_db = !matches!(
10336                    self.config.dialect,
10337                    Some(DialectType::Doris)
10338                        | Some(DialectType::DuckDB)
10339                        | Some(DialectType::BigQuery)
10340                        | Some(DialectType::PostgreSQL)
10341                );
10342                if !rename_table_with_db {
10343                    let mut stripped = new_name.clone();
10344                    stripped.schema = None;
10345                    stripped.catalog = None;
10346                    self.generate_table(&stripped)?;
10347                } else {
10348                    self.generate_table(new_name)?;
10349                }
10350            }
10351            AlterTableAction::AddConstraint(constraint) => {
10352                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
10353                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
10354                if !is_continuation {
10355                    self.write_keyword("ADD");
10356                    self.write_space();
10357                }
10358                self.generate_table_constraint(constraint)?;
10359            }
10360            AlterTableAction::DropConstraint { name, if_exists } => {
10361                self.write_keyword("DROP CONSTRAINT");
10362                if *if_exists {
10363                    self.write_space();
10364                    self.write_keyword("IF EXISTS");
10365                }
10366                self.write_space();
10367                self.generate_identifier(name)?;
10368            }
10369            AlterTableAction::DropForeignKey { name } => {
10370                self.write_keyword("DROP FOREIGN KEY");
10371                self.write_space();
10372                self.generate_identifier(name)?;
10373            }
10374            AlterTableAction::DropPartition {
10375                partitions,
10376                if_exists,
10377            } => {
10378                self.write_keyword("DROP");
10379                if *if_exists {
10380                    self.write_space();
10381                    self.write_keyword("IF EXISTS");
10382                }
10383                for (i, partition) in partitions.iter().enumerate() {
10384                    if i > 0 {
10385                        self.write(",");
10386                    }
10387                    self.write_space();
10388                    self.write_keyword("PARTITION");
10389                    // Check for special ClickHouse partition formats
10390                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
10391                        // ClickHouse: PARTITION <expression>
10392                        self.write_space();
10393                        self.generate_expression(&partition[0].1)?;
10394                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
10395                        // ClickHouse: PARTITION ALL
10396                        self.write_space();
10397                        self.write_keyword("ALL");
10398                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
10399                        // ClickHouse: PARTITION ID 'string'
10400                        self.write_space();
10401                        self.write_keyword("ID");
10402                        self.write_space();
10403                        self.generate_expression(&partition[0].1)?;
10404                    } else {
10405                        // Standard SQL: PARTITION(key=value, ...)
10406                        self.write("(");
10407                        for (j, (key, value)) in partition.iter().enumerate() {
10408                            if j > 0 {
10409                                self.write(", ");
10410                            }
10411                            self.generate_identifier(key)?;
10412                            self.write(" = ");
10413                            self.generate_expression(value)?;
10414                        }
10415                        self.write(")");
10416                    }
10417                }
10418            }
10419            AlterTableAction::Delete { where_clause } => {
10420                self.write_keyword("DELETE");
10421                self.write_space();
10422                self.write_keyword("WHERE");
10423                self.write_space();
10424                self.generate_expression(where_clause)?;
10425            }
10426            AlterTableAction::SwapWith(target) => {
10427                self.write_keyword("SWAP WITH");
10428                self.write_space();
10429                self.generate_table(target)?;
10430            }
10431            AlterTableAction::SetProperty { properties } => {
10432                use crate::dialects::DialectType;
10433                self.write_keyword("SET");
10434                // Trino/Presto use SET PROPERTIES syntax with spaces around =
10435                let is_trino_presto = matches!(
10436                    self.config.dialect,
10437                    Some(DialectType::Trino) | Some(DialectType::Presto)
10438                );
10439                if is_trino_presto {
10440                    self.write_space();
10441                    self.write_keyword("PROPERTIES");
10442                }
10443                let eq = if is_trino_presto { " = " } else { "=" };
10444                for (i, (key, value)) in properties.iter().enumerate() {
10445                    if i > 0 {
10446                        self.write(",");
10447                    }
10448                    self.write_space();
10449                    // Handle quoted property names for Trino
10450                    if key.contains(' ') {
10451                        self.generate_string_literal(key)?;
10452                    } else {
10453                        self.write(key);
10454                    }
10455                    self.write(eq);
10456                    self.generate_expression(value)?;
10457                }
10458            }
10459            AlterTableAction::UnsetProperty { properties } => {
10460                self.write_keyword("UNSET");
10461                for (i, name) in properties.iter().enumerate() {
10462                    if i > 0 {
10463                        self.write(",");
10464                    }
10465                    self.write_space();
10466                    self.write(name);
10467                }
10468            }
10469            AlterTableAction::ClusterBy { expressions } => {
10470                self.write_keyword("CLUSTER BY");
10471                self.write(" (");
10472                for (i, expr) in expressions.iter().enumerate() {
10473                    if i > 0 {
10474                        self.write(", ");
10475                    }
10476                    self.generate_expression(expr)?;
10477                }
10478                self.write(")");
10479            }
10480            AlterTableAction::SetTag { expressions } => {
10481                self.write_keyword("SET TAG");
10482                for (i, (key, value)) in expressions.iter().enumerate() {
10483                    if i > 0 {
10484                        self.write(",");
10485                    }
10486                    self.write_space();
10487                    self.write(key);
10488                    self.write(" = ");
10489                    self.generate_expression(value)?;
10490                }
10491            }
10492            AlterTableAction::UnsetTag { names } => {
10493                self.write_keyword("UNSET TAG");
10494                for (i, name) in names.iter().enumerate() {
10495                    if i > 0 {
10496                        self.write(",");
10497                    }
10498                    self.write_space();
10499                    self.write(name);
10500                }
10501            }
10502            AlterTableAction::SetOptions { expressions } => {
10503                self.write_keyword("SET");
10504                self.write(" (");
10505                for (i, expr) in expressions.iter().enumerate() {
10506                    if i > 0 {
10507                        self.write(", ");
10508                    }
10509                    self.generate_expression(expr)?;
10510                }
10511                self.write(")");
10512            }
10513            AlterTableAction::AlterIndex { name, visible } => {
10514                self.write_keyword("ALTER INDEX");
10515                self.write_space();
10516                self.generate_identifier(name)?;
10517                self.write_space();
10518                if *visible {
10519                    self.write_keyword("VISIBLE");
10520                } else {
10521                    self.write_keyword("INVISIBLE");
10522                }
10523            }
10524            AlterTableAction::SetAttribute { attribute } => {
10525                self.write_keyword("SET");
10526                self.write_space();
10527                self.write_keyword(attribute);
10528            }
10529            AlterTableAction::SetStageFileFormat { options } => {
10530                self.write_keyword("SET");
10531                self.write_space();
10532                self.write_keyword("STAGE_FILE_FORMAT");
10533                self.write(" = (");
10534                if let Some(opts) = options {
10535                    self.generate_space_separated_properties(opts)?;
10536                }
10537                self.write(")");
10538            }
10539            AlterTableAction::SetStageCopyOptions { options } => {
10540                self.write_keyword("SET");
10541                self.write_space();
10542                self.write_keyword("STAGE_COPY_OPTIONS");
10543                self.write(" = (");
10544                if let Some(opts) = options {
10545                    self.generate_space_separated_properties(opts)?;
10546                }
10547                self.write(")");
10548            }
10549            AlterTableAction::AddColumns { columns, cascade } => {
10550                // Oracle uses ADD (...) without COLUMNS keyword
10551                // Hive/Spark uses ADD COLUMNS (...)
10552                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
10553                if is_oracle {
10554                    self.write_keyword("ADD");
10555                } else {
10556                    self.write_keyword("ADD COLUMNS");
10557                }
10558                self.write(" (");
10559                for (i, col) in columns.iter().enumerate() {
10560                    if i > 0 {
10561                        self.write(", ");
10562                    }
10563                    self.generate_column_def(col)?;
10564                }
10565                self.write(")");
10566                if *cascade {
10567                    self.write_space();
10568                    self.write_keyword("CASCADE");
10569                }
10570            }
10571            AlterTableAction::ChangeColumn {
10572                old_name,
10573                new_name,
10574                data_type,
10575                comment,
10576                cascade,
10577            } => {
10578                use crate::dialects::DialectType;
10579                let is_spark = matches!(
10580                    self.config.dialect,
10581                    Some(DialectType::Spark) | Some(DialectType::Databricks)
10582                );
10583                let is_rename = old_name.name != new_name.name;
10584
10585                if is_spark {
10586                    if is_rename {
10587                        // Spark: RENAME COLUMN old TO new
10588                        self.write_keyword("RENAME COLUMN");
10589                        self.write_space();
10590                        self.generate_identifier(old_name)?;
10591                        self.write_space();
10592                        self.write_keyword("TO");
10593                        self.write_space();
10594                        self.generate_identifier(new_name)?;
10595                    } else if comment.is_some() {
10596                        // Spark: ALTER COLUMN old COMMENT 'comment'
10597                        self.write_keyword("ALTER COLUMN");
10598                        self.write_space();
10599                        self.generate_identifier(old_name)?;
10600                        self.write_space();
10601                        self.write_keyword("COMMENT");
10602                        self.write_space();
10603                        self.write("'");
10604                        self.write(comment.as_ref().unwrap());
10605                        self.write("'");
10606                    } else if data_type.is_some() {
10607                        // Spark: ALTER COLUMN old TYPE data_type
10608                        self.write_keyword("ALTER COLUMN");
10609                        self.write_space();
10610                        self.generate_identifier(old_name)?;
10611                        self.write_space();
10612                        self.write_keyword("TYPE");
10613                        self.write_space();
10614                        self.generate_data_type(data_type.as_ref().unwrap())?;
10615                    } else {
10616                        // Fallback to CHANGE COLUMN
10617                        self.write_keyword("CHANGE COLUMN");
10618                        self.write_space();
10619                        self.generate_identifier(old_name)?;
10620                        self.write_space();
10621                        self.generate_identifier(new_name)?;
10622                    }
10623                } else {
10624                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
10625                    if data_type.is_some() {
10626                        self.write_keyword("CHANGE COLUMN");
10627                    } else {
10628                        self.write_keyword("CHANGE");
10629                    }
10630                    self.write_space();
10631                    self.generate_identifier(old_name)?;
10632                    self.write_space();
10633                    self.generate_identifier(new_name)?;
10634                    if let Some(ref dt) = data_type {
10635                        self.write_space();
10636                        self.generate_data_type(dt)?;
10637                    }
10638                    if let Some(ref c) = comment {
10639                        self.write_space();
10640                        self.write_keyword("COMMENT");
10641                        self.write_space();
10642                        self.write("'");
10643                        self.write(c);
10644                        self.write("'");
10645                    }
10646                    if *cascade {
10647                        self.write_space();
10648                        self.write_keyword("CASCADE");
10649                    }
10650                }
10651            }
10652            AlterTableAction::AddPartition {
10653                partition,
10654                if_not_exists,
10655                location,
10656            } => {
10657                self.write_keyword("ADD");
10658                self.write_space();
10659                if *if_not_exists {
10660                    self.write_keyword("IF NOT EXISTS");
10661                    self.write_space();
10662                }
10663                self.generate_expression(partition)?;
10664                if let Some(ref loc) = location {
10665                    self.write_space();
10666                    self.write_keyword("LOCATION");
10667                    self.write_space();
10668                    self.generate_expression(loc)?;
10669                }
10670            }
10671            AlterTableAction::AlterSortKey {
10672                this,
10673                expressions,
10674                compound,
10675            } => {
10676                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
10677                self.write_keyword("ALTER");
10678                if *compound {
10679                    self.write_space();
10680                    self.write_keyword("COMPOUND");
10681                }
10682                self.write_space();
10683                self.write_keyword("SORTKEY");
10684                self.write_space();
10685                if let Some(style) = this {
10686                    self.write_keyword(style);
10687                } else if !expressions.is_empty() {
10688                    self.write("(");
10689                    for (i, expr) in expressions.iter().enumerate() {
10690                        if i > 0 {
10691                            self.write(", ");
10692                        }
10693                        self.generate_expression(expr)?;
10694                    }
10695                    self.write(")");
10696                }
10697            }
10698            AlterTableAction::AlterDistStyle { style, distkey } => {
10699                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
10700                self.write_keyword("ALTER");
10701                self.write_space();
10702                self.write_keyword("DISTSTYLE");
10703                self.write_space();
10704                self.write_keyword(style);
10705                if let Some(col) = distkey {
10706                    self.write_space();
10707                    self.write_keyword("DISTKEY");
10708                    self.write_space();
10709                    self.generate_identifier(col)?;
10710                }
10711            }
10712            AlterTableAction::SetTableProperties { properties } => {
10713                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
10714                self.write_keyword("SET TABLE PROPERTIES");
10715                self.write(" (");
10716                for (i, (key, value)) in properties.iter().enumerate() {
10717                    if i > 0 {
10718                        self.write(", ");
10719                    }
10720                    self.generate_expression(key)?;
10721                    self.write(" = ");
10722                    self.generate_expression(value)?;
10723                }
10724                self.write(")");
10725            }
10726            AlterTableAction::SetLocation { location } => {
10727                // Redshift: SET LOCATION 's3://bucket/folder/'
10728                self.write_keyword("SET LOCATION");
10729                self.write_space();
10730                self.write("'");
10731                self.write(location);
10732                self.write("'");
10733            }
10734            AlterTableAction::SetFileFormat { format } => {
10735                // Redshift: SET FILE FORMAT AVRO
10736                self.write_keyword("SET FILE FORMAT");
10737                self.write_space();
10738                self.write_keyword(format);
10739            }
10740            AlterTableAction::ReplacePartition { partition, source } => {
10741                // ClickHouse: REPLACE PARTITION expr FROM source
10742                self.write_keyword("REPLACE PARTITION");
10743                self.write_space();
10744                self.generate_expression(partition)?;
10745                if let Some(src) = source {
10746                    self.write_space();
10747                    self.write_keyword("FROM");
10748                    self.write_space();
10749                    self.generate_expression(src)?;
10750                }
10751            }
10752            AlterTableAction::Raw { sql } => {
10753                self.write(sql);
10754            }
10755        }
10756        Ok(())
10757    }
10758
10759    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
10760        match action {
10761            AlterColumnAction::SetDataType {
10762                data_type,
10763                using,
10764                collate,
10765            } => {
10766                use crate::dialects::DialectType;
10767                // Dialect-specific type change syntax:
10768                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
10769                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
10770                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
10771                let is_no_prefix = matches!(
10772                    self.config.dialect,
10773                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
10774                );
10775                let is_type_only = matches!(
10776                    self.config.dialect,
10777                    Some(DialectType::Redshift)
10778                        | Some(DialectType::Spark)
10779                        | Some(DialectType::Databricks)
10780                );
10781                if is_type_only {
10782                    self.write_keyword("TYPE");
10783                    self.write_space();
10784                } else if !is_no_prefix {
10785                    self.write_keyword("SET DATA TYPE");
10786                    self.write_space();
10787                }
10788                self.generate_data_type(data_type)?;
10789                if let Some(ref collation) = collate {
10790                    self.write_space();
10791                    self.write_keyword("COLLATE");
10792                    self.write_space();
10793                    self.write(collation);
10794                }
10795                if let Some(ref using_expr) = using {
10796                    self.write_space();
10797                    self.write_keyword("USING");
10798                    self.write_space();
10799                    self.generate_expression(using_expr)?;
10800                }
10801            }
10802            AlterColumnAction::SetDefault(expr) => {
10803                self.write_keyword("SET DEFAULT");
10804                self.write_space();
10805                self.generate_expression(expr)?;
10806            }
10807            AlterColumnAction::DropDefault => {
10808                self.write_keyword("DROP DEFAULT");
10809            }
10810            AlterColumnAction::SetNotNull => {
10811                self.write_keyword("SET NOT NULL");
10812            }
10813            AlterColumnAction::DropNotNull => {
10814                self.write_keyword("DROP NOT NULL");
10815            }
10816            AlterColumnAction::Comment(comment) => {
10817                self.write_keyword("COMMENT");
10818                self.write_space();
10819                self.generate_string_literal(comment)?;
10820            }
10821            AlterColumnAction::SetVisible => {
10822                self.write_keyword("SET VISIBLE");
10823            }
10824            AlterColumnAction::SetInvisible => {
10825                self.write_keyword("SET INVISIBLE");
10826            }
10827        }
10828        Ok(())
10829    }
10830
10831    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
10832        self.write_keyword("CREATE");
10833
10834        if ci.unique {
10835            self.write_space();
10836            self.write_keyword("UNIQUE");
10837        }
10838
10839        // TSQL CLUSTERED/NONCLUSTERED modifier
10840        if let Some(ref clustered) = ci.clustered {
10841            self.write_space();
10842            self.write_keyword(clustered);
10843        }
10844
10845        self.write_space();
10846        self.write_keyword("INDEX");
10847
10848        // PostgreSQL CONCURRENTLY modifier
10849        if ci.concurrently {
10850            self.write_space();
10851            self.write_keyword("CONCURRENTLY");
10852        }
10853
10854        if ci.if_not_exists {
10855            self.write_space();
10856            self.write_keyword("IF NOT EXISTS");
10857        }
10858
10859        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
10860        if !ci.name.name.is_empty() {
10861            self.write_space();
10862            self.generate_identifier(&ci.name)?;
10863        }
10864        self.write_space();
10865        self.write_keyword("ON");
10866        // Hive uses ON TABLE
10867        if matches!(self.config.dialect, Some(DialectType::Hive)) {
10868            self.write_space();
10869            self.write_keyword("TABLE");
10870        }
10871        self.write_space();
10872        self.generate_table(&ci.table)?;
10873
10874        // Column list (optional for COLUMNSTORE indexes)
10875        // Standard SQL convention: ON t(a) without space before paren
10876        if !ci.columns.is_empty() || ci.using.is_some() {
10877            let space_before_paren = false;
10878
10879            if let Some(ref using) = ci.using {
10880                self.write_space();
10881                self.write_keyword("USING");
10882                self.write_space();
10883                self.write(using);
10884                if space_before_paren {
10885                    self.write(" (");
10886                } else {
10887                    self.write("(");
10888                }
10889            } else {
10890                if space_before_paren {
10891                    self.write(" (");
10892                } else {
10893                    self.write("(");
10894                }
10895            }
10896            for (i, col) in ci.columns.iter().enumerate() {
10897                if i > 0 {
10898                    self.write(", ");
10899                }
10900                self.generate_identifier(&col.column)?;
10901                if let Some(ref opclass) = col.opclass {
10902                    self.write_space();
10903                    self.write(opclass);
10904                }
10905                if col.desc {
10906                    self.write_space();
10907                    self.write_keyword("DESC");
10908                } else if col.asc {
10909                    self.write_space();
10910                    self.write_keyword("ASC");
10911                }
10912                if let Some(nulls_first) = col.nulls_first {
10913                    self.write_space();
10914                    self.write_keyword("NULLS");
10915                    self.write_space();
10916                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
10917                }
10918            }
10919            self.write(")");
10920        }
10921
10922        // PostgreSQL INCLUDE (col1, col2) clause
10923        if !ci.include_columns.is_empty() {
10924            self.write_space();
10925            self.write_keyword("INCLUDE");
10926            self.write(" (");
10927            for (i, col) in ci.include_columns.iter().enumerate() {
10928                if i > 0 {
10929                    self.write(", ");
10930                }
10931                self.generate_identifier(col)?;
10932            }
10933            self.write(")");
10934        }
10935
10936        // TSQL: WITH (option=value, ...) clause
10937        if !ci.with_options.is_empty() {
10938            self.write_space();
10939            self.write_keyword("WITH");
10940            self.write(" (");
10941            for (i, (key, value)) in ci.with_options.iter().enumerate() {
10942                if i > 0 {
10943                    self.write(", ");
10944                }
10945                self.write(key);
10946                self.write("=");
10947                self.write(value);
10948            }
10949            self.write(")");
10950        }
10951
10952        // PostgreSQL WHERE clause for partial indexes
10953        if let Some(ref where_clause) = ci.where_clause {
10954            self.write_space();
10955            self.write_keyword("WHERE");
10956            self.write_space();
10957            self.generate_expression(where_clause)?;
10958        }
10959
10960        // TSQL: ON filegroup or partition scheme clause
10961        if let Some(ref on_fg) = ci.on_filegroup {
10962            self.write_space();
10963            self.write_keyword("ON");
10964            self.write_space();
10965            self.write(on_fg);
10966        }
10967
10968        Ok(())
10969    }
10970
10971    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
10972        self.write_keyword("DROP INDEX");
10973
10974        if di.concurrently {
10975            self.write_space();
10976            self.write_keyword("CONCURRENTLY");
10977        }
10978
10979        if di.if_exists {
10980            self.write_space();
10981            self.write_keyword("IF EXISTS");
10982        }
10983
10984        self.write_space();
10985        self.generate_identifier(&di.name)?;
10986
10987        if let Some(ref table) = di.table {
10988            self.write_space();
10989            self.write_keyword("ON");
10990            self.write_space();
10991            self.generate_table(table)?;
10992        }
10993
10994        Ok(())
10995    }
10996
10997    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
10998        self.write_keyword("CREATE");
10999
11000        // MySQL: ALGORITHM=...
11001        if let Some(ref algorithm) = cv.algorithm {
11002            self.write_space();
11003            self.write_keyword("ALGORITHM");
11004            self.write("=");
11005            self.write_keyword(algorithm);
11006        }
11007
11008        // MySQL: DEFINER=...
11009        if let Some(ref definer) = cv.definer {
11010            self.write_space();
11011            self.write_keyword("DEFINER");
11012            self.write("=");
11013            self.write(definer);
11014        }
11015
11016        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword, unless it appeared after view name)
11017        if cv.security_sql_style && !cv.security_after_name {
11018            if let Some(ref security) = cv.security {
11019                self.write_space();
11020                self.write_keyword("SQL SECURITY");
11021                self.write_space();
11022                match security {
11023                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
11024                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
11025                    FunctionSecurity::None => self.write_keyword("NONE"),
11026                }
11027            }
11028        }
11029
11030        if cv.or_alter {
11031            self.write_space();
11032            self.write_keyword("OR ALTER");
11033        } else if cv.or_replace {
11034            self.write_space();
11035            self.write_keyword("OR REPLACE");
11036        }
11037
11038        if cv.temporary {
11039            self.write_space();
11040            self.write_keyword("TEMPORARY");
11041        }
11042
11043        if cv.materialized {
11044            self.write_space();
11045            self.write_keyword("MATERIALIZED");
11046        }
11047
11048        // Snowflake: SECURE VIEW
11049        if cv.secure {
11050            self.write_space();
11051            self.write_keyword("SECURE");
11052        }
11053
11054        self.write_space();
11055        self.write_keyword("VIEW");
11056
11057        if cv.if_not_exists {
11058            self.write_space();
11059            self.write_keyword("IF NOT EXISTS");
11060        }
11061
11062        self.write_space();
11063        self.generate_table(&cv.name)?;
11064
11065        // ClickHouse: ON CLUSTER clause
11066        if let Some(ref on_cluster) = cv.on_cluster {
11067            self.write_space();
11068            self.generate_on_cluster(on_cluster)?;
11069        }
11070
11071        // ClickHouse: TO destination_table
11072        if let Some(ref to_table) = cv.to_table {
11073            self.write_space();
11074            self.write_keyword("TO");
11075            self.write_space();
11076            self.generate_table(to_table)?;
11077        }
11078
11079        // For regular VIEW: columns come before COPY GRANTS
11080        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
11081        if !cv.materialized {
11082            // Regular VIEW: columns first
11083            if !cv.columns.is_empty() {
11084                self.write(" (");
11085                for (i, col) in cv.columns.iter().enumerate() {
11086                    if i > 0 {
11087                        self.write(", ");
11088                    }
11089                    self.generate_identifier(&col.name)?;
11090                    // BigQuery: OPTIONS (key=value, ...) on view column
11091                    if !col.options.is_empty() {
11092                        self.write_space();
11093                        self.generate_options_clause(&col.options)?;
11094                    }
11095                    if let Some(ref comment) = col.comment {
11096                        self.write_space();
11097                        self.write_keyword("COMMENT");
11098                        self.write_space();
11099                        self.generate_string_literal(comment)?;
11100                    }
11101                }
11102                self.write(")");
11103            }
11104
11105            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
11106            // Also handles SQL SECURITY after view name (security_after_name)
11107            if !cv.security_sql_style || cv.security_after_name {
11108                if let Some(ref security) = cv.security {
11109                    self.write_space();
11110                    if cv.security_sql_style {
11111                        self.write_keyword("SQL SECURITY");
11112                    } else {
11113                        self.write_keyword("SECURITY");
11114                    }
11115                    self.write_space();
11116                    match security {
11117                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
11118                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
11119                        FunctionSecurity::None => self.write_keyword("NONE"),
11120                    }
11121                }
11122            }
11123
11124            // Snowflake: COPY GRANTS
11125            if cv.copy_grants {
11126                self.write_space();
11127                self.write_keyword("COPY GRANTS");
11128            }
11129        } else {
11130            // MATERIALIZED VIEW: COPY GRANTS first
11131            if cv.copy_grants {
11132                self.write_space();
11133                self.write_keyword("COPY GRANTS");
11134            }
11135
11136            // Doris: If we have a schema (typed columns), generate that instead
11137            if let Some(ref schema) = cv.schema {
11138                self.write(" (");
11139                for (i, expr) in schema.expressions.iter().enumerate() {
11140                    if i > 0 {
11141                        self.write(", ");
11142                    }
11143                    self.generate_expression(expr)?;
11144                }
11145                self.write(")");
11146            } else if !cv.columns.is_empty() {
11147                // Then columns (simple column names without types)
11148                self.write(" (");
11149                for (i, col) in cv.columns.iter().enumerate() {
11150                    if i > 0 {
11151                        self.write(", ");
11152                    }
11153                    self.generate_identifier(&col.name)?;
11154                    // BigQuery: OPTIONS (key=value, ...) on view column
11155                    if !col.options.is_empty() {
11156                        self.write_space();
11157                        self.generate_options_clause(&col.options)?;
11158                    }
11159                    if let Some(ref comment) = col.comment {
11160                        self.write_space();
11161                        self.write_keyword("COMMENT");
11162                        self.write_space();
11163                        self.generate_string_literal(comment)?;
11164                    }
11165                }
11166                self.write(")");
11167            }
11168
11169            // Doris: KEY (columns) for materialized views
11170            if let Some(ref unique_key) = cv.unique_key {
11171                self.write_space();
11172                self.write_keyword("KEY");
11173                self.write(" (");
11174                for (i, expr) in unique_key.expressions.iter().enumerate() {
11175                    if i > 0 {
11176                        self.write(", ");
11177                    }
11178                    self.generate_expression(expr)?;
11179                }
11180                self.write(")");
11181            }
11182        }
11183
11184        // Snowflake: COMMENT = 'text'
11185        if let Some(ref comment) = cv.comment {
11186            self.write_space();
11187            self.write_keyword("COMMENT");
11188            self.write("=");
11189            self.generate_string_literal(comment)?;
11190        }
11191
11192        // Snowflake: TAG (name='value', ...)
11193        if !cv.tags.is_empty() {
11194            self.write_space();
11195            self.write_keyword("TAG");
11196            self.write(" (");
11197            for (i, (name, value)) in cv.tags.iter().enumerate() {
11198                if i > 0 {
11199                    self.write(", ");
11200                }
11201                self.write(name);
11202                self.write("='");
11203                self.write(value);
11204                self.write("'");
11205            }
11206            self.write(")");
11207        }
11208
11209        // BigQuery: OPTIONS (key=value, ...)
11210        if !cv.options.is_empty() {
11211            self.write_space();
11212            self.generate_options_clause(&cv.options)?;
11213        }
11214
11215        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
11216        if let Some(ref build) = cv.build {
11217            self.write_space();
11218            self.write_keyword("BUILD");
11219            self.write_space();
11220            self.write_keyword(build);
11221        }
11222
11223        // Doris: REFRESH clause for materialized views
11224        if let Some(ref refresh) = cv.refresh {
11225            self.write_space();
11226            self.generate_refresh_trigger_property(refresh)?;
11227        }
11228
11229        // Redshift: AUTO REFRESH YES|NO for materialized views
11230        if let Some(auto_refresh) = cv.auto_refresh {
11231            self.write_space();
11232            self.write_keyword("AUTO REFRESH");
11233            self.write_space();
11234            if auto_refresh {
11235                self.write_keyword("YES");
11236            } else {
11237                self.write_keyword("NO");
11238            }
11239        }
11240
11241        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
11242        for prop in &cv.table_properties {
11243            self.write_space();
11244            self.generate_expression(prop)?;
11245        }
11246
11247        // Only output AS clause if there's a real query (not just NULL placeholder)
11248        if !matches!(&cv.query, Expression::Null(_)) {
11249            self.write_space();
11250            self.write_keyword("AS");
11251            self.write_space();
11252
11253            // Teradata: LOCKING clause (between AS and query)
11254            if let Some(ref mode) = cv.locking_mode {
11255                self.write_keyword("LOCKING");
11256                self.write_space();
11257                self.write_keyword(mode);
11258                if let Some(ref access) = cv.locking_access {
11259                    self.write_space();
11260                    self.write_keyword("FOR");
11261                    self.write_space();
11262                    self.write_keyword(access);
11263                }
11264                self.write_space();
11265            }
11266
11267            if cv.query_parenthesized {
11268                self.write("(");
11269            }
11270            self.generate_expression(&cv.query)?;
11271            if cv.query_parenthesized {
11272                self.write(")");
11273            }
11274        }
11275
11276        // Redshift: WITH NO SCHEMA BINDING (after query)
11277        if cv.no_schema_binding {
11278            self.write_space();
11279            self.write_keyword("WITH NO SCHEMA BINDING");
11280        }
11281
11282        Ok(())
11283    }
11284
11285    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
11286        self.write_keyword("DROP");
11287
11288        if dv.materialized {
11289            self.write_space();
11290            self.write_keyword("MATERIALIZED");
11291        }
11292
11293        self.write_space();
11294        self.write_keyword("VIEW");
11295
11296        if dv.if_exists {
11297            self.write_space();
11298            self.write_keyword("IF EXISTS");
11299        }
11300
11301        self.write_space();
11302        self.generate_table(&dv.name)?;
11303
11304        Ok(())
11305    }
11306
11307    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
11308        match tr.target {
11309            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
11310            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
11311        }
11312        if tr.if_exists {
11313            self.write_space();
11314            self.write_keyword("IF EXISTS");
11315        }
11316        self.write_space();
11317        self.generate_table(&tr.table)?;
11318
11319        // ClickHouse: ON CLUSTER clause
11320        if let Some(ref on_cluster) = tr.on_cluster {
11321            self.write_space();
11322            self.generate_on_cluster(on_cluster)?;
11323        }
11324
11325        // Check if first table has a * (multi-table with star)
11326        if !tr.extra_tables.is_empty() {
11327            // Check if the first entry matches the main table (star case)
11328            let skip_first = if let Some(first) = tr.extra_tables.first() {
11329                first.table.name == tr.table.name && first.star
11330            } else {
11331                false
11332            };
11333
11334            // PostgreSQL normalizes away the * suffix (it's the default behavior)
11335            let strip_star = matches!(
11336                self.config.dialect,
11337                Some(crate::dialects::DialectType::PostgreSQL)
11338                    | Some(crate::dialects::DialectType::Redshift)
11339            );
11340            if skip_first && !strip_star {
11341                self.write("*");
11342            }
11343
11344            // Generate additional tables
11345            for (i, entry) in tr.extra_tables.iter().enumerate() {
11346                if i == 0 && skip_first {
11347                    continue; // Already handled the star for first table
11348                }
11349                self.write(", ");
11350                self.generate_table(&entry.table)?;
11351                if entry.star && !strip_star {
11352                    self.write("*");
11353                }
11354            }
11355        }
11356
11357        // RESTART/CONTINUE IDENTITY
11358        if let Some(identity) = &tr.identity {
11359            self.write_space();
11360            match identity {
11361                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
11362                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
11363            }
11364        }
11365
11366        if tr.cascade {
11367            self.write_space();
11368            self.write_keyword("CASCADE");
11369        }
11370
11371        if tr.restrict {
11372            self.write_space();
11373            self.write_keyword("RESTRICT");
11374        }
11375
11376        // Output Hive PARTITION clause
11377        if let Some(ref partition) = tr.partition {
11378            self.write_space();
11379            self.generate_expression(partition)?;
11380        }
11381
11382        Ok(())
11383    }
11384
11385    fn generate_use(&mut self, u: &Use) -> Result<()> {
11386        // Teradata uses "DATABASE <name>" instead of "USE <name>"
11387        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
11388            self.write_keyword("DATABASE");
11389            self.write_space();
11390            self.generate_identifier(&u.this)?;
11391            return Ok(());
11392        }
11393
11394        self.write_keyword("USE");
11395
11396        if let Some(kind) = &u.kind {
11397            self.write_space();
11398            match kind {
11399                UseKind::Database => self.write_keyword("DATABASE"),
11400                UseKind::Schema => self.write_keyword("SCHEMA"),
11401                UseKind::Role => self.write_keyword("ROLE"),
11402                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
11403                UseKind::Catalog => self.write_keyword("CATALOG"),
11404                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
11405            }
11406        }
11407
11408        self.write_space();
11409        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
11410        // without quoting, since these are keywords not identifiers
11411        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
11412            self.write(&u.this.name);
11413        } else {
11414            self.generate_identifier(&u.this)?;
11415        }
11416        Ok(())
11417    }
11418
11419    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
11420        self.write_keyword("CACHE");
11421        if c.lazy {
11422            self.write_space();
11423            self.write_keyword("LAZY");
11424        }
11425        self.write_space();
11426        self.write_keyword("TABLE");
11427        self.write_space();
11428        self.generate_identifier(&c.table)?;
11429
11430        // OPTIONS clause
11431        if !c.options.is_empty() {
11432            self.write_space();
11433            self.write_keyword("OPTIONS");
11434            self.write("(");
11435            for (i, (key, value)) in c.options.iter().enumerate() {
11436                if i > 0 {
11437                    self.write(", ");
11438                }
11439                self.generate_expression(key)?;
11440                self.write(" = ");
11441                self.generate_expression(value)?;
11442            }
11443            self.write(")");
11444        }
11445
11446        // AS query
11447        if let Some(query) = &c.query {
11448            self.write_space();
11449            self.write_keyword("AS");
11450            self.write_space();
11451            self.generate_expression(query)?;
11452        }
11453
11454        Ok(())
11455    }
11456
11457    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
11458        self.write_keyword("UNCACHE TABLE");
11459        if u.if_exists {
11460            self.write_space();
11461            self.write_keyword("IF EXISTS");
11462        }
11463        self.write_space();
11464        self.generate_identifier(&u.table)?;
11465        Ok(())
11466    }
11467
11468    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
11469        self.write_keyword("LOAD DATA");
11470        if l.local {
11471            self.write_space();
11472            self.write_keyword("LOCAL");
11473        }
11474        self.write_space();
11475        self.write_keyword("INPATH");
11476        self.write_space();
11477        self.write("'");
11478        self.write(&l.inpath);
11479        self.write("'");
11480
11481        if l.overwrite {
11482            self.write_space();
11483            self.write_keyword("OVERWRITE");
11484        }
11485
11486        self.write_space();
11487        self.write_keyword("INTO TABLE");
11488        self.write_space();
11489        self.generate_expression(&l.table)?;
11490
11491        // PARTITION clause
11492        if !l.partition.is_empty() {
11493            self.write_space();
11494            self.write_keyword("PARTITION");
11495            self.write("(");
11496            for (i, (col, val)) in l.partition.iter().enumerate() {
11497                if i > 0 {
11498                    self.write(", ");
11499                }
11500                self.generate_identifier(col)?;
11501                self.write(" = ");
11502                self.generate_expression(val)?;
11503            }
11504            self.write(")");
11505        }
11506
11507        // INPUTFORMAT clause
11508        if let Some(fmt) = &l.input_format {
11509            self.write_space();
11510            self.write_keyword("INPUTFORMAT");
11511            self.write_space();
11512            self.write("'");
11513            self.write(fmt);
11514            self.write("'");
11515        }
11516
11517        // SERDE clause
11518        if let Some(serde) = &l.serde {
11519            self.write_space();
11520            self.write_keyword("SERDE");
11521            self.write_space();
11522            self.write("'");
11523            self.write(serde);
11524            self.write("'");
11525        }
11526
11527        Ok(())
11528    }
11529
11530    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
11531        self.write_keyword("PRAGMA");
11532        self.write_space();
11533
11534        // Schema prefix if present
11535        if let Some(schema) = &p.schema {
11536            self.generate_identifier(schema)?;
11537            self.write(".");
11538        }
11539
11540        // Pragma name
11541        self.generate_identifier(&p.name)?;
11542
11543        // Value assignment or function call
11544        if let Some(value) = &p.value {
11545            self.write(" = ");
11546            self.generate_expression(value)?;
11547        } else if !p.args.is_empty() {
11548            self.write("(");
11549            for (i, arg) in p.args.iter().enumerate() {
11550                if i > 0 {
11551                    self.write(", ");
11552                }
11553                self.generate_expression(arg)?;
11554            }
11555            self.write(")");
11556        }
11557
11558        Ok(())
11559    }
11560
11561    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
11562        self.write_keyword("GRANT");
11563        self.write_space();
11564
11565        // Privileges (with optional column lists)
11566        for (i, privilege) in g.privileges.iter().enumerate() {
11567            if i > 0 {
11568                self.write(", ");
11569            }
11570            self.write_keyword(&privilege.name);
11571            // Output column list if present: SELECT(col1, col2)
11572            if !privilege.columns.is_empty() {
11573                self.write("(");
11574                for (j, col) in privilege.columns.iter().enumerate() {
11575                    if j > 0 {
11576                        self.write(", ");
11577                    }
11578                    self.write(col);
11579                }
11580                self.write(")");
11581            }
11582        }
11583
11584        self.write_space();
11585        self.write_keyword("ON");
11586        self.write_space();
11587
11588        // Object kind (TABLE, SCHEMA, etc.)
11589        if let Some(kind) = &g.kind {
11590            self.write_keyword(kind);
11591            self.write_space();
11592        }
11593
11594        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11595        {
11596            use crate::dialects::DialectType;
11597            let should_upper = matches!(
11598                self.config.dialect,
11599                Some(DialectType::PostgreSQL)
11600                    | Some(DialectType::CockroachDB)
11601                    | Some(DialectType::Materialize)
11602                    | Some(DialectType::RisingWave)
11603            ) && (g.kind.as_deref() == Some("FUNCTION")
11604                || g.kind.as_deref() == Some("PROCEDURE"));
11605            if should_upper {
11606                use crate::expressions::Identifier;
11607                let upper_id = Identifier {
11608                    name: g.securable.name.to_ascii_uppercase(),
11609                    quoted: g.securable.quoted,
11610                    ..g.securable.clone()
11611                };
11612                self.generate_identifier(&upper_id)?;
11613            } else {
11614                self.generate_identifier(&g.securable)?;
11615            }
11616        }
11617
11618        // Function parameter types (if present)
11619        if !g.function_params.is_empty() {
11620            self.write("(");
11621            for (i, param) in g.function_params.iter().enumerate() {
11622                if i > 0 {
11623                    self.write(", ");
11624                }
11625                self.write(param);
11626            }
11627            self.write(")");
11628        }
11629
11630        self.write_space();
11631        self.write_keyword("TO");
11632        self.write_space();
11633
11634        // Principals
11635        for (i, principal) in g.principals.iter().enumerate() {
11636            if i > 0 {
11637                self.write(", ");
11638            }
11639            if principal.is_role {
11640                self.write_keyword("ROLE");
11641                self.write_space();
11642            } else if principal.is_group {
11643                self.write_keyword("GROUP");
11644                self.write_space();
11645            } else if principal.is_share {
11646                self.write_keyword("SHARE");
11647                self.write_space();
11648            }
11649            self.generate_identifier(&principal.name)?;
11650        }
11651
11652        // WITH GRANT OPTION
11653        if g.grant_option {
11654            self.write_space();
11655            self.write_keyword("WITH GRANT OPTION");
11656        }
11657
11658        // TSQL: AS principal
11659        if let Some(ref principal) = g.as_principal {
11660            self.write_space();
11661            self.write_keyword("AS");
11662            self.write_space();
11663            self.generate_identifier(principal)?;
11664        }
11665
11666        Ok(())
11667    }
11668
11669    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
11670        self.write_keyword("REVOKE");
11671        self.write_space();
11672
11673        // GRANT OPTION FOR
11674        if r.grant_option {
11675            self.write_keyword("GRANT OPTION FOR");
11676            self.write_space();
11677        }
11678
11679        // Privileges (with optional column lists)
11680        for (i, privilege) in r.privileges.iter().enumerate() {
11681            if i > 0 {
11682                self.write(", ");
11683            }
11684            self.write_keyword(&privilege.name);
11685            // Output column list if present: SELECT(col1, col2)
11686            if !privilege.columns.is_empty() {
11687                self.write("(");
11688                for (j, col) in privilege.columns.iter().enumerate() {
11689                    if j > 0 {
11690                        self.write(", ");
11691                    }
11692                    self.write(col);
11693                }
11694                self.write(")");
11695            }
11696        }
11697
11698        self.write_space();
11699        self.write_keyword("ON");
11700        self.write_space();
11701
11702        // Object kind
11703        if let Some(kind) = &r.kind {
11704            self.write_keyword(kind);
11705            self.write_space();
11706        }
11707
11708        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11709        {
11710            use crate::dialects::DialectType;
11711            let should_upper = matches!(
11712                self.config.dialect,
11713                Some(DialectType::PostgreSQL)
11714                    | Some(DialectType::CockroachDB)
11715                    | Some(DialectType::Materialize)
11716                    | Some(DialectType::RisingWave)
11717            ) && (r.kind.as_deref() == Some("FUNCTION")
11718                || r.kind.as_deref() == Some("PROCEDURE"));
11719            if should_upper {
11720                use crate::expressions::Identifier;
11721                let upper_id = Identifier {
11722                    name: r.securable.name.to_ascii_uppercase(),
11723                    quoted: r.securable.quoted,
11724                    ..r.securable.clone()
11725                };
11726                self.generate_identifier(&upper_id)?;
11727            } else {
11728                self.generate_identifier(&r.securable)?;
11729            }
11730        }
11731
11732        // Function parameter types (if present)
11733        if !r.function_params.is_empty() {
11734            self.write("(");
11735            for (i, param) in r.function_params.iter().enumerate() {
11736                if i > 0 {
11737                    self.write(", ");
11738                }
11739                self.write(param);
11740            }
11741            self.write(")");
11742        }
11743
11744        self.write_space();
11745        self.write_keyword("FROM");
11746        self.write_space();
11747
11748        // Principals
11749        for (i, principal) in r.principals.iter().enumerate() {
11750            if i > 0 {
11751                self.write(", ");
11752            }
11753            if principal.is_role {
11754                self.write_keyword("ROLE");
11755                self.write_space();
11756            } else if principal.is_group {
11757                self.write_keyword("GROUP");
11758                self.write_space();
11759            } else if principal.is_share {
11760                self.write_keyword("SHARE");
11761                self.write_space();
11762            }
11763            self.generate_identifier(&principal.name)?;
11764        }
11765
11766        // CASCADE or RESTRICT
11767        if r.cascade {
11768            self.write_space();
11769            self.write_keyword("CASCADE");
11770        } else if r.restrict {
11771            self.write_space();
11772            self.write_keyword("RESTRICT");
11773        }
11774
11775        Ok(())
11776    }
11777
11778    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
11779        self.write_keyword("COMMENT");
11780
11781        // IF EXISTS
11782        if c.exists {
11783            self.write_space();
11784            self.write_keyword("IF EXISTS");
11785        }
11786
11787        self.write_space();
11788        self.write_keyword("ON");
11789
11790        // MATERIALIZED
11791        if c.materialized {
11792            self.write_space();
11793            self.write_keyword("MATERIALIZED");
11794        }
11795
11796        self.write_space();
11797        self.write_keyword(&c.kind);
11798        self.write_space();
11799
11800        // Object name
11801        self.generate_expression(&c.this)?;
11802
11803        self.write_space();
11804        self.write_keyword("IS");
11805        self.write_space();
11806
11807        // Comment expression
11808        self.generate_expression(&c.expression)?;
11809
11810        Ok(())
11811    }
11812
11813    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
11814        self.write_keyword("SET");
11815
11816        for (i, item) in s.items.iter().enumerate() {
11817            if i > 0 {
11818                self.write(",");
11819            }
11820            self.write_space();
11821
11822            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY, VARIABLE)
11823            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
11824            if let Some(ref kind) = item.kind {
11825                // For VARIABLE kind, only output the keyword for dialects that require it
11826                // (Spark, Databricks, DuckDB) - matching Python sqlglot's
11827                // SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD flag
11828                if has_variable_kind {
11829                    if matches!(
11830                        self.config.dialect,
11831                        Some(DialectType::Spark | DialectType::Databricks | DialectType::DuckDB)
11832                    ) {
11833                        self.write_keyword("VARIABLE");
11834                        self.write_space();
11835                    }
11836                } else {
11837                    self.write_keyword(kind);
11838                    self.write_space();
11839                }
11840            }
11841
11842            // Check for special SET forms by name
11843            let name_str = match &item.name {
11844                Expression::Identifier(id) => Some(id.name.as_str()),
11845                _ => None,
11846            };
11847
11848            let is_transaction = name_str == Some("TRANSACTION");
11849            let is_character_set = name_str == Some("CHARACTER SET");
11850            let is_names = name_str == Some("NAMES");
11851            let is_collate = name_str == Some("COLLATE");
11852            let is_value_only =
11853                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
11854
11855            if is_transaction {
11856                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
11857                self.write_keyword("TRANSACTION");
11858                if let Expression::Identifier(id) = &item.value {
11859                    if !id.name.is_empty() {
11860                        self.write_space();
11861                        self.write(&id.name);
11862                    }
11863                }
11864            } else if is_character_set {
11865                // Output: SET CHARACTER SET <charset>
11866                self.write_keyword("CHARACTER SET");
11867                self.write_space();
11868                self.generate_set_value(&item.value)?;
11869            } else if is_names {
11870                // Output: SET NAMES <charset>
11871                self.write_keyword("NAMES");
11872                self.write_space();
11873                self.generate_set_value(&item.value)?;
11874            } else if is_collate {
11875                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
11876                self.write_keyword("COLLATE");
11877                self.write_space();
11878                self.generate_set_value(&item.value)?;
11879            } else if has_variable_kind {
11880                // Output: SET [VARIABLE] <name> = <value>
11881                // VARIABLE keyword already written above if dialect requires it
11882                if let Some(ns) = name_str {
11883                    self.write(ns);
11884                } else {
11885                    self.generate_expression(&item.name)?;
11886                }
11887                self.write(" = ");
11888                self.generate_set_value(&item.value)?;
11889            } else if is_value_only {
11890                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
11891                self.generate_expression(&item.name)?;
11892            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
11893                // SET key value without = (TSQL style)
11894                self.generate_expression(&item.name)?;
11895                self.write_space();
11896                self.generate_set_value(&item.value)?;
11897            } else {
11898                // Standard: variable = value
11899                // SET item names should not be quoted (they are config parameter names, not column refs)
11900                match &item.name {
11901                    Expression::Identifier(id) => {
11902                        self.write(&id.name);
11903                    }
11904                    _ => {
11905                        self.generate_expression(&item.name)?;
11906                    }
11907                }
11908                self.write(" = ");
11909                self.generate_set_value(&item.value)?;
11910            }
11911        }
11912
11913        Ok(())
11914    }
11915
11916    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
11917    /// directly to avoid reserved keyword quoting.
11918    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
11919        if let Expression::Identifier(id) = value {
11920            match id.name.as_str() {
11921                "DEFAULT" | "ON" | "OFF" => {
11922                    self.write_keyword(&id.name);
11923                    return Ok(());
11924                }
11925                _ => {}
11926            }
11927        }
11928        self.generate_expression(value)
11929    }
11930
11931    // ==================== Phase 4: Additional DDL Generation ====================
11932
11933    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
11934        self.write_keyword("ALTER");
11935        // MySQL modifiers before VIEW
11936        if let Some(ref algorithm) = av.algorithm {
11937            self.write_space();
11938            self.write_keyword("ALGORITHM");
11939            self.write(" = ");
11940            self.write_keyword(algorithm);
11941        }
11942        if let Some(ref definer) = av.definer {
11943            self.write_space();
11944            self.write_keyword("DEFINER");
11945            self.write(" = ");
11946            self.write(definer);
11947        }
11948        if let Some(ref sql_security) = av.sql_security {
11949            self.write_space();
11950            self.write_keyword("SQL SECURITY");
11951            self.write(" = ");
11952            self.write_keyword(sql_security);
11953        }
11954        self.write_space();
11955        self.write_keyword("VIEW");
11956        self.write_space();
11957        self.generate_table(&av.name)?;
11958
11959        // Hive: Column aliases with optional COMMENT
11960        if !av.columns.is_empty() {
11961            self.write(" (");
11962            for (i, col) in av.columns.iter().enumerate() {
11963                if i > 0 {
11964                    self.write(", ");
11965                }
11966                self.generate_identifier(&col.name)?;
11967                if let Some(ref comment) = col.comment {
11968                    self.write_space();
11969                    self.write_keyword("COMMENT");
11970                    self.write(" ");
11971                    self.generate_string_literal(comment)?;
11972                }
11973            }
11974            self.write(")");
11975        }
11976
11977        // TSQL: WITH option before actions
11978        if let Some(ref opt) = av.with_option {
11979            self.write_space();
11980            self.write_keyword("WITH");
11981            self.write_space();
11982            self.write_keyword(opt);
11983        }
11984
11985        for action in &av.actions {
11986            self.write_space();
11987            match action {
11988                AlterViewAction::Rename(new_name) => {
11989                    self.write_keyword("RENAME TO");
11990                    self.write_space();
11991                    self.generate_table(new_name)?;
11992                }
11993                AlterViewAction::OwnerTo(owner) => {
11994                    self.write_keyword("OWNER TO");
11995                    self.write_space();
11996                    self.generate_identifier(owner)?;
11997                }
11998                AlterViewAction::SetSchema(schema) => {
11999                    self.write_keyword("SET SCHEMA");
12000                    self.write_space();
12001                    self.generate_identifier(schema)?;
12002                }
12003                AlterViewAction::SetAuthorization(auth) => {
12004                    self.write_keyword("SET AUTHORIZATION");
12005                    self.write_space();
12006                    self.write(auth);
12007                }
12008                AlterViewAction::AlterColumn { name, action } => {
12009                    self.write_keyword("ALTER COLUMN");
12010                    self.write_space();
12011                    self.generate_identifier(name)?;
12012                    self.write_space();
12013                    self.generate_alter_column_action(action)?;
12014                }
12015                AlterViewAction::AsSelect(query) => {
12016                    self.write_keyword("AS");
12017                    self.write_space();
12018                    self.generate_expression(query)?;
12019                }
12020                AlterViewAction::SetTblproperties(props) => {
12021                    self.write_keyword("SET TBLPROPERTIES");
12022                    self.write(" (");
12023                    for (i, (key, value)) in props.iter().enumerate() {
12024                        if i > 0 {
12025                            self.write(", ");
12026                        }
12027                        self.generate_string_literal(key)?;
12028                        self.write("=");
12029                        self.generate_string_literal(value)?;
12030                    }
12031                    self.write(")");
12032                }
12033                AlterViewAction::UnsetTblproperties(keys) => {
12034                    self.write_keyword("UNSET TBLPROPERTIES");
12035                    self.write(" (");
12036                    for (i, key) in keys.iter().enumerate() {
12037                        if i > 0 {
12038                            self.write(", ");
12039                        }
12040                        self.generate_string_literal(key)?;
12041                    }
12042                    self.write(")");
12043                }
12044            }
12045        }
12046
12047        Ok(())
12048    }
12049
12050    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
12051        self.write_keyword("ALTER INDEX");
12052        self.write_space();
12053        self.generate_identifier(&ai.name)?;
12054
12055        if let Some(table) = &ai.table {
12056            self.write_space();
12057            self.write_keyword("ON");
12058            self.write_space();
12059            self.generate_table(table)?;
12060        }
12061
12062        for action in &ai.actions {
12063            self.write_space();
12064            match action {
12065                AlterIndexAction::Rename(new_name) => {
12066                    self.write_keyword("RENAME TO");
12067                    self.write_space();
12068                    self.generate_identifier(new_name)?;
12069                }
12070                AlterIndexAction::SetTablespace(tablespace) => {
12071                    self.write_keyword("SET TABLESPACE");
12072                    self.write_space();
12073                    self.generate_identifier(tablespace)?;
12074                }
12075                AlterIndexAction::Visible(visible) => {
12076                    if *visible {
12077                        self.write_keyword("VISIBLE");
12078                    } else {
12079                        self.write_keyword("INVISIBLE");
12080                    }
12081                }
12082            }
12083        }
12084
12085        Ok(())
12086    }
12087
12088    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
12089        // Output leading comments
12090        for comment in &cs.leading_comments {
12091            self.write_formatted_comment(comment);
12092            self.write_space();
12093        }
12094
12095        // Athena: CREATE SCHEMA uses Hive engine (backticks)
12096        let saved_athena_hive_context = self.athena_hive_context;
12097        if matches!(
12098            self.config.dialect,
12099            Some(crate::dialects::DialectType::Athena)
12100        ) {
12101            self.athena_hive_context = true;
12102        }
12103
12104        self.write_keyword("CREATE SCHEMA");
12105
12106        if cs.if_not_exists {
12107            self.write_space();
12108            self.write_keyword("IF NOT EXISTS");
12109        }
12110
12111        self.write_space();
12112        for (i, part) in cs.name.iter().enumerate() {
12113            if i > 0 {
12114                self.write(".");
12115            }
12116            self.generate_identifier(part)?;
12117        }
12118
12119        if let Some(ref clone_parts) = cs.clone_from {
12120            self.write_keyword(" CLONE ");
12121            for (i, part) in clone_parts.iter().enumerate() {
12122                if i > 0 {
12123                    self.write(".");
12124                }
12125                self.generate_identifier(part)?;
12126            }
12127        }
12128
12129        if let Some(ref at_clause) = cs.at_clause {
12130            self.write_space();
12131            self.generate_expression(at_clause)?;
12132        }
12133
12134        if let Some(auth) = &cs.authorization {
12135            self.write_space();
12136            self.write_keyword("AUTHORIZATION");
12137            self.write_space();
12138            self.generate_identifier(auth)?;
12139        }
12140
12141        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
12142        // Separate WITH properties from other properties
12143        let with_properties: Vec<_> = cs
12144            .properties
12145            .iter()
12146            .filter(|p| matches!(p, Expression::Property(_)))
12147            .collect();
12148        let other_properties: Vec<_> = cs
12149            .properties
12150            .iter()
12151            .filter(|p| !matches!(p, Expression::Property(_)))
12152            .collect();
12153
12154        // Generate WITH (props) if we have Property expressions
12155        if !with_properties.is_empty() {
12156            self.write_space();
12157            self.write_keyword("WITH");
12158            self.write(" (");
12159            for (i, prop) in with_properties.iter().enumerate() {
12160                if i > 0 {
12161                    self.write(", ");
12162                }
12163                self.generate_expression(prop)?;
12164            }
12165            self.write(")");
12166        }
12167
12168        // Generate other properties (like DEFAULT COLLATE)
12169        for prop in other_properties {
12170            self.write_space();
12171            self.generate_expression(prop)?;
12172        }
12173
12174        // Restore Athena Hive context
12175        self.athena_hive_context = saved_athena_hive_context;
12176
12177        Ok(())
12178    }
12179
12180    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
12181        self.write_keyword("DROP SCHEMA");
12182
12183        if ds.if_exists {
12184            self.write_space();
12185            self.write_keyword("IF EXISTS");
12186        }
12187
12188        self.write_space();
12189        self.generate_identifier(&ds.name)?;
12190
12191        if ds.cascade {
12192            self.write_space();
12193            self.write_keyword("CASCADE");
12194        }
12195
12196        Ok(())
12197    }
12198
12199    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
12200        self.write_keyword("DROP NAMESPACE");
12201
12202        if dn.if_exists {
12203            self.write_space();
12204            self.write_keyword("IF EXISTS");
12205        }
12206
12207        self.write_space();
12208        self.generate_identifier(&dn.name)?;
12209
12210        if dn.cascade {
12211            self.write_space();
12212            self.write_keyword("CASCADE");
12213        }
12214
12215        Ok(())
12216    }
12217
12218    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
12219        self.write_keyword("CREATE DATABASE");
12220
12221        if cd.if_not_exists {
12222            self.write_space();
12223            self.write_keyword("IF NOT EXISTS");
12224        }
12225
12226        self.write_space();
12227        self.generate_identifier(&cd.name)?;
12228
12229        if let Some(ref clone_src) = cd.clone_from {
12230            self.write_keyword(" CLONE ");
12231            self.generate_identifier(clone_src)?;
12232        }
12233
12234        // AT/BEFORE clause for time travel (Snowflake)
12235        if let Some(ref at_clause) = cd.at_clause {
12236            self.write_space();
12237            self.generate_expression(at_clause)?;
12238        }
12239
12240        for option in &cd.options {
12241            self.write_space();
12242            match option {
12243                DatabaseOption::CharacterSet(charset) => {
12244                    self.write_keyword("CHARACTER SET");
12245                    self.write(" = ");
12246                    self.write(&format!("'{}'", charset));
12247                }
12248                DatabaseOption::Collate(collate) => {
12249                    self.write_keyword("COLLATE");
12250                    self.write(" = ");
12251                    self.write(&format!("'{}'", collate));
12252                }
12253                DatabaseOption::Owner(owner) => {
12254                    self.write_keyword("OWNER");
12255                    self.write(" = ");
12256                    self.generate_identifier(owner)?;
12257                }
12258                DatabaseOption::Template(template) => {
12259                    self.write_keyword("TEMPLATE");
12260                    self.write(" = ");
12261                    self.generate_identifier(template)?;
12262                }
12263                DatabaseOption::Encoding(encoding) => {
12264                    self.write_keyword("ENCODING");
12265                    self.write(" = ");
12266                    self.write(&format!("'{}'", encoding));
12267                }
12268                DatabaseOption::Location(location) => {
12269                    self.write_keyword("LOCATION");
12270                    self.write(" = ");
12271                    self.write(&format!("'{}'", location));
12272                }
12273            }
12274        }
12275
12276        Ok(())
12277    }
12278
12279    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
12280        self.write_keyword("DROP DATABASE");
12281
12282        if dd.if_exists {
12283            self.write_space();
12284            self.write_keyword("IF EXISTS");
12285        }
12286
12287        self.write_space();
12288        self.generate_identifier(&dd.name)?;
12289
12290        if dd.sync {
12291            self.write_space();
12292            self.write_keyword("SYNC");
12293        }
12294
12295        Ok(())
12296    }
12297
12298    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
12299        self.write_keyword("CREATE");
12300
12301        if cf.or_alter {
12302            self.write_space();
12303            self.write_keyword("OR ALTER");
12304        } else if cf.or_replace {
12305            self.write_space();
12306            self.write_keyword("OR REPLACE");
12307        }
12308
12309        if cf.temporary {
12310            self.write_space();
12311            self.write_keyword("TEMPORARY");
12312        }
12313
12314        self.write_space();
12315        if cf.is_table_function {
12316            self.write_keyword("TABLE FUNCTION");
12317        } else {
12318            self.write_keyword("FUNCTION");
12319        }
12320
12321        if cf.if_not_exists {
12322            self.write_space();
12323            self.write_keyword("IF NOT EXISTS");
12324        }
12325
12326        self.write_space();
12327        self.generate_table(&cf.name)?;
12328        if cf.has_parens {
12329            let func_multiline = self.config.pretty
12330                && matches!(
12331                    self.config.dialect,
12332                    Some(crate::dialects::DialectType::TSQL)
12333                        | Some(crate::dialects::DialectType::Fabric)
12334                )
12335                && !cf.parameters.is_empty();
12336            if func_multiline {
12337                self.write("(\n");
12338                self.indent_level += 2;
12339                self.write_indent();
12340                self.generate_function_parameters(&cf.parameters)?;
12341                self.write("\n");
12342                self.indent_level -= 2;
12343                self.write(")");
12344            } else {
12345                self.write("(");
12346                self.generate_function_parameters(&cf.parameters)?;
12347                self.write(")");
12348            }
12349        }
12350
12351        // Output RETURNS clause (always comes first after parameters)
12352        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
12353        let use_multiline = self.config.pretty
12354            && matches!(
12355                self.config.dialect,
12356                Some(crate::dialects::DialectType::BigQuery)
12357                    | Some(crate::dialects::DialectType::TSQL)
12358                    | Some(crate::dialects::DialectType::Fabric)
12359            );
12360
12361        if cf.language_first {
12362            // LANGUAGE first, then SQL data access, then RETURNS
12363            if let Some(lang) = &cf.language {
12364                if use_multiline {
12365                    self.write_newline();
12366                } else {
12367                    self.write_space();
12368                }
12369                self.write_keyword("LANGUAGE");
12370                self.write_space();
12371                self.write(lang);
12372            }
12373
12374            // SQL data access comes after LANGUAGE in this case
12375            if let Some(sql_data) = &cf.sql_data_access {
12376                self.write_space();
12377                match sql_data {
12378                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12379                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12380                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12381                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12382                }
12383            }
12384
12385            if let Some(ref rtb) = cf.returns_table_body {
12386                if use_multiline {
12387                    self.write_newline();
12388                } else {
12389                    self.write_space();
12390                }
12391                self.write_keyword("RETURNS");
12392                self.write_space();
12393                self.write(rtb);
12394            } else if let Some(return_type) = &cf.return_type {
12395                if use_multiline {
12396                    self.write_newline();
12397                } else {
12398                    self.write_space();
12399                }
12400                self.write_keyword("RETURNS");
12401                self.write_space();
12402                self.generate_data_type(return_type)?;
12403            }
12404        } else {
12405            // RETURNS first (default)
12406            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
12407            let is_duckdb = matches!(
12408                self.config.dialect,
12409                Some(crate::dialects::DialectType::DuckDB)
12410            );
12411            if let Some(ref rtb) = cf.returns_table_body {
12412                if !(is_duckdb && rtb.is_empty()) {
12413                    if use_multiline {
12414                        self.write_newline();
12415                    } else {
12416                        self.write_space();
12417                    }
12418                    self.write_keyword("RETURNS");
12419                    self.write_space();
12420                    self.write(rtb);
12421                }
12422            } else if let Some(return_type) = &cf.return_type {
12423                // DuckDB: skip all RETURNS (DuckDB macros don't use RETURNS clause)
12424                if !is_duckdb {
12425                    let is_table_return = matches!(return_type, crate::expressions::DataType::Custom { ref name } if name.eq_ignore_ascii_case("TABLE"));
12426                    if use_multiline {
12427                        self.write_newline();
12428                    } else {
12429                        self.write_space();
12430                    }
12431                    self.write_keyword("RETURNS");
12432                    self.write_space();
12433                    if is_table_return {
12434                        self.write_keyword("TABLE");
12435                    } else {
12436                        self.generate_data_type(return_type)?;
12437                    }
12438                }
12439            }
12440        }
12441
12442        // If we have property_order, use it to output properties in original order
12443        if !cf.property_order.is_empty() {
12444            // For BigQuery, OPTIONS must come before AS - reorder if needed
12445            let is_bigquery = matches!(
12446                self.config.dialect,
12447                Some(crate::dialects::DialectType::BigQuery)
12448            );
12449            let property_order = if is_bigquery {
12450                // Move Options before As if both are present
12451                let mut reordered = Vec::new();
12452                let mut has_as = false;
12453                let mut has_options = false;
12454                for prop in &cf.property_order {
12455                    match prop {
12456                        FunctionPropertyKind::As => has_as = true,
12457                        FunctionPropertyKind::Options => has_options = true,
12458                        _ => {}
12459                    }
12460                }
12461                if has_as && has_options {
12462                    // Output all props except As and Options, then Options, then As
12463                    for prop in &cf.property_order {
12464                        if *prop != FunctionPropertyKind::As
12465                            && *prop != FunctionPropertyKind::Options
12466                        {
12467                            reordered.push(*prop);
12468                        }
12469                    }
12470                    reordered.push(FunctionPropertyKind::Options);
12471                    reordered.push(FunctionPropertyKind::As);
12472                    reordered
12473                } else {
12474                    cf.property_order.clone()
12475                }
12476            } else {
12477                cf.property_order.clone()
12478            };
12479
12480            for prop in &property_order {
12481                match prop {
12482                    FunctionPropertyKind::Set => {
12483                        self.generate_function_set_options(cf)?;
12484                    }
12485                    FunctionPropertyKind::As => {
12486                        self.generate_function_body(cf)?;
12487                    }
12488                    FunctionPropertyKind::Language => {
12489                        if !cf.language_first {
12490                            // Only output here if not already output above
12491                            if let Some(lang) = &cf.language {
12492                                // Only BigQuery uses multiline formatting
12493                                let use_multiline = self.config.pretty
12494                                    && matches!(
12495                                        self.config.dialect,
12496                                        Some(crate::dialects::DialectType::BigQuery)
12497                                    );
12498                                if use_multiline {
12499                                    self.write_newline();
12500                                } else {
12501                                    self.write_space();
12502                                }
12503                                self.write_keyword("LANGUAGE");
12504                                self.write_space();
12505                                self.write(lang);
12506                            }
12507                        }
12508                    }
12509                    FunctionPropertyKind::Determinism => {
12510                        self.generate_function_determinism(cf)?;
12511                    }
12512                    FunctionPropertyKind::NullInput => {
12513                        self.generate_function_null_input(cf)?;
12514                    }
12515                    FunctionPropertyKind::Security => {
12516                        self.generate_function_security(cf)?;
12517                    }
12518                    FunctionPropertyKind::SqlDataAccess => {
12519                        if !cf.language_first {
12520                            // Only output here if not already output above
12521                            self.generate_function_sql_data_access(cf)?;
12522                        }
12523                    }
12524                    FunctionPropertyKind::Options => {
12525                        if !cf.options.is_empty() {
12526                            self.write_space();
12527                            self.generate_options_clause(&cf.options)?;
12528                        }
12529                    }
12530                    FunctionPropertyKind::Environment => {
12531                        if !cf.environment.is_empty() {
12532                            self.write_space();
12533                            self.generate_environment_clause(&cf.environment)?;
12534                        }
12535                    }
12536                    FunctionPropertyKind::Handler => {
12537                        if let Some(ref h) = cf.handler {
12538                            self.write_space();
12539                            self.write_keyword("HANDLER");
12540                            self.write_space();
12541                            self.write("'");
12542                            self.write(h);
12543                            self.write("'");
12544                        }
12545                    }
12546                    FunctionPropertyKind::ParameterStyle => {
12547                        if let Some(ref ps) = cf.parameter_style {
12548                            self.write_space();
12549                            self.write_keyword("PARAMETER STYLE");
12550                            self.write_space();
12551                            self.write_keyword(ps);
12552                        }
12553                    }
12554                }
12555            }
12556
12557            // Output OPTIONS if not tracked in property_order (legacy)
12558            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
12559            {
12560                self.write_space();
12561                self.generate_options_clause(&cf.options)?;
12562            }
12563
12564            // Output ENVIRONMENT if not tracked in property_order (legacy)
12565            if !cf.environment.is_empty()
12566                && !cf
12567                    .property_order
12568                    .contains(&FunctionPropertyKind::Environment)
12569            {
12570                self.write_space();
12571                self.generate_environment_clause(&cf.environment)?;
12572            }
12573        } else {
12574            // Legacy behavior when property_order is empty
12575            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
12576            if matches!(
12577                self.config.dialect,
12578                Some(crate::dialects::DialectType::BigQuery)
12579            ) {
12580                self.generate_function_determinism(cf)?;
12581            }
12582
12583            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
12584            let use_multiline = self.config.pretty
12585                && matches!(
12586                    self.config.dialect,
12587                    Some(crate::dialects::DialectType::BigQuery)
12588                );
12589
12590            if !cf.language_first {
12591                if let Some(lang) = &cf.language {
12592                    if use_multiline {
12593                        self.write_newline();
12594                    } else {
12595                        self.write_space();
12596                    }
12597                    self.write_keyword("LANGUAGE");
12598                    self.write_space();
12599                    self.write(lang);
12600                }
12601
12602                // SQL data access characteristic comes after LANGUAGE
12603                self.generate_function_sql_data_access(cf)?;
12604            }
12605
12606            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
12607            if !matches!(
12608                self.config.dialect,
12609                Some(crate::dialects::DialectType::BigQuery)
12610            ) {
12611                self.generate_function_determinism(cf)?;
12612            }
12613
12614            self.generate_function_null_input(cf)?;
12615            self.generate_function_security(cf)?;
12616            self.generate_function_set_options(cf)?;
12617
12618            // BigQuery: OPTIONS (key=value, ...) - comes before AS
12619            if !cf.options.is_empty() {
12620                self.write_space();
12621                self.generate_options_clause(&cf.options)?;
12622            }
12623
12624            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
12625            if !cf.environment.is_empty() {
12626                self.write_space();
12627                self.generate_environment_clause(&cf.environment)?;
12628            }
12629
12630            self.generate_function_body(cf)?;
12631        }
12632
12633        Ok(())
12634    }
12635
12636    /// Generate SET options for CREATE FUNCTION
12637    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
12638        for opt in &cf.set_options {
12639            self.write_space();
12640            self.write_keyword("SET");
12641            self.write_space();
12642            self.write(&opt.name);
12643            match &opt.value {
12644                FunctionSetValue::Value { value, use_to } => {
12645                    if *use_to {
12646                        self.write(" TO ");
12647                    } else {
12648                        self.write(" = ");
12649                    }
12650                    self.write(value);
12651                }
12652                FunctionSetValue::FromCurrent => {
12653                    self.write_space();
12654                    self.write_keyword("FROM CURRENT");
12655                }
12656            }
12657        }
12658        Ok(())
12659    }
12660
12661    /// Generate function body (AS clause)
12662    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
12663        if let Some(body) = &cf.body {
12664            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
12665            self.write_space();
12666            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
12667            let use_multiline = self.config.pretty
12668                && matches!(
12669                    self.config.dialect,
12670                    Some(crate::dialects::DialectType::BigQuery)
12671                );
12672            match body {
12673                FunctionBody::Block(block) => {
12674                    self.write_keyword("AS");
12675                    if matches!(
12676                        self.config.dialect,
12677                        Some(crate::dialects::DialectType::TSQL)
12678                    ) {
12679                        self.write(" BEGIN ");
12680                        self.write(block);
12681                        self.write(" END");
12682                    } else if matches!(
12683                        self.config.dialect,
12684                        Some(crate::dialects::DialectType::PostgreSQL)
12685                    ) {
12686                        self.write(" $$");
12687                        self.write(block);
12688                        self.write("$$");
12689                    } else {
12690                        // Escape content for single-quoted output
12691                        let escaped = self.escape_block_for_single_quote(block);
12692                        // In BigQuery pretty mode, body content goes on new line
12693                        if use_multiline {
12694                            self.write_newline();
12695                        } else {
12696                            self.write(" ");
12697                        }
12698                        self.write("'");
12699                        self.write(&escaped);
12700                        self.write("'");
12701                    }
12702                }
12703                FunctionBody::StringLiteral(s) => {
12704                    self.write_keyword("AS");
12705                    // In BigQuery pretty mode, body content goes on new line
12706                    if use_multiline {
12707                        self.write_newline();
12708                    } else {
12709                        self.write(" ");
12710                    }
12711                    self.write("'");
12712                    self.write(s);
12713                    self.write("'");
12714                }
12715                FunctionBody::Expression(expr) => {
12716                    self.write_keyword("AS");
12717                    self.write_space();
12718                    self.generate_expression(expr)?;
12719                }
12720                FunctionBody::External(name) => {
12721                    self.write_keyword("EXTERNAL NAME");
12722                    self.write(" '");
12723                    self.write(name);
12724                    self.write("'");
12725                }
12726                FunctionBody::Return(expr) => {
12727                    if matches!(
12728                        self.config.dialect,
12729                        Some(crate::dialects::DialectType::DuckDB)
12730                    ) {
12731                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
12732                        self.write_keyword("AS");
12733                        self.write_space();
12734                        // Check both returns_table_body marker and return_type = Custom "TABLE"
12735                        let is_table_return = cf.returns_table_body.is_some()
12736                            || matches!(&cf.return_type, Some(crate::expressions::DataType::Custom { ref name }) if name.eq_ignore_ascii_case("TABLE"));
12737                        if is_table_return {
12738                            self.write_keyword("TABLE");
12739                            self.write_space();
12740                        }
12741                        self.generate_expression(expr)?;
12742                    } else {
12743                        if self.config.create_function_return_as {
12744                            self.write_keyword("AS");
12745                            // TSQL pretty: newline between AS and RETURN
12746                            if self.config.pretty
12747                                && matches!(
12748                                    self.config.dialect,
12749                                    Some(crate::dialects::DialectType::TSQL)
12750                                        | Some(crate::dialects::DialectType::Fabric)
12751                                )
12752                            {
12753                                self.write_newline();
12754                            } else {
12755                                self.write_space();
12756                            }
12757                        }
12758                        self.write_keyword("RETURN");
12759                        self.write_space();
12760                        self.generate_expression(expr)?;
12761                    }
12762                }
12763                FunctionBody::Statements(stmts) => {
12764                    self.write_keyword("AS");
12765                    self.write(" BEGIN ");
12766                    for (i, stmt) in stmts.iter().enumerate() {
12767                        if i > 0 {
12768                            self.write(" ");
12769                        }
12770                        self.generate_expression(stmt)?;
12771                        self.write(";");
12772                    }
12773                    self.write(" END");
12774                }
12775                FunctionBody::RawBlock(text) => {
12776                    self.write_newline();
12777                    self.write(text);
12778                }
12779                FunctionBody::DollarQuoted { content, tag } => {
12780                    self.write_keyword("AS");
12781                    self.write(" ");
12782                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
12783                    let supports_dollar_quoting = matches!(
12784                        self.config.dialect,
12785                        Some(crate::dialects::DialectType::PostgreSQL)
12786                            | Some(crate::dialects::DialectType::Databricks)
12787                            | Some(crate::dialects::DialectType::Redshift)
12788                            | Some(crate::dialects::DialectType::DuckDB)
12789                    );
12790                    if supports_dollar_quoting {
12791                        // Output in dollar-quoted format
12792                        self.write("$");
12793                        if let Some(t) = tag {
12794                            self.write(t);
12795                        }
12796                        self.write("$");
12797                        self.write(content);
12798                        self.write("$");
12799                        if let Some(t) = tag {
12800                            self.write(t);
12801                        }
12802                        self.write("$");
12803                    } else {
12804                        // Convert to single-quoted string for other dialects
12805                        let escaped = self.escape_block_for_single_quote(content);
12806                        self.write("'");
12807                        self.write(&escaped);
12808                        self.write("'");
12809                    }
12810                }
12811            }
12812        }
12813        Ok(())
12814    }
12815
12816    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
12817    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
12818        if let Some(det) = cf.deterministic {
12819            self.write_space();
12820            if matches!(
12821                self.config.dialect,
12822                Some(crate::dialects::DialectType::BigQuery)
12823            ) {
12824                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
12825                if det {
12826                    self.write_keyword("DETERMINISTIC");
12827                } else {
12828                    self.write_keyword("NOT DETERMINISTIC");
12829                }
12830            } else {
12831                // PostgreSQL and others use IMMUTABLE/VOLATILE
12832                if det {
12833                    self.write_keyword("IMMUTABLE");
12834                } else {
12835                    self.write_keyword("VOLATILE");
12836                }
12837            }
12838        }
12839        Ok(())
12840    }
12841
12842    /// Generate null input handling clause
12843    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
12844        if let Some(returns_null) = cf.returns_null_on_null_input {
12845            self.write_space();
12846            if returns_null {
12847                if cf.strict {
12848                    self.write_keyword("STRICT");
12849                } else {
12850                    self.write_keyword("RETURNS NULL ON NULL INPUT");
12851                }
12852            } else {
12853                self.write_keyword("CALLED ON NULL INPUT");
12854            }
12855        }
12856        Ok(())
12857    }
12858
12859    /// Generate security clause
12860    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
12861        if let Some(security) = &cf.security {
12862            self.write_space();
12863            // MySQL uses SQL SECURITY prefix
12864            if matches!(
12865                self.config.dialect,
12866                Some(crate::dialects::DialectType::MySQL)
12867            ) {
12868                self.write_keyword("SQL SECURITY");
12869            } else {
12870                self.write_keyword("SECURITY");
12871            }
12872            self.write_space();
12873            match security {
12874                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12875                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12876                FunctionSecurity::None => self.write_keyword("NONE"),
12877            }
12878        }
12879        Ok(())
12880    }
12881
12882    /// Generate SQL data access clause
12883    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
12884        if let Some(sql_data) = &cf.sql_data_access {
12885            self.write_space();
12886            match sql_data {
12887                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12888                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12889                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12890                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12891            }
12892        }
12893        Ok(())
12894    }
12895
12896    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
12897        for (i, param) in params.iter().enumerate() {
12898            if i > 0 {
12899                self.write(", ");
12900            }
12901
12902            if let Some(mode) = &param.mode {
12903                if let Some(text) = &param.mode_text {
12904                    self.write(text);
12905                } else {
12906                    match mode {
12907                        ParameterMode::In => self.write_keyword("IN"),
12908                        ParameterMode::Out => self.write_keyword("OUT"),
12909                        ParameterMode::InOut => self.write_keyword("INOUT"),
12910                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
12911                    }
12912                }
12913                self.write_space();
12914            }
12915
12916            if let Some(name) = &param.name {
12917                self.generate_identifier(name)?;
12918                // Skip space and type for empty Custom types (e.g., DuckDB macros)
12919                let skip_type =
12920                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
12921                if !skip_type {
12922                    self.write_space();
12923                    self.generate_data_type(&param.data_type)?;
12924                }
12925            } else {
12926                self.generate_data_type(&param.data_type)?;
12927            }
12928
12929            if let Some(default) = &param.default {
12930                if self.config.parameter_default_equals {
12931                    self.write(" = ");
12932                } else {
12933                    self.write(" DEFAULT ");
12934                }
12935                self.generate_expression(default)?;
12936            }
12937        }
12938
12939        Ok(())
12940    }
12941
12942    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
12943        self.write_keyword("DROP FUNCTION");
12944
12945        if df.if_exists {
12946            self.write_space();
12947            self.write_keyword("IF EXISTS");
12948        }
12949
12950        self.write_space();
12951        self.generate_table(&df.name)?;
12952
12953        if let Some(params) = &df.parameters {
12954            self.write(" (");
12955            for (i, dt) in params.iter().enumerate() {
12956                if i > 0 {
12957                    self.write(", ");
12958                }
12959                self.generate_data_type(dt)?;
12960            }
12961            self.write(")");
12962        }
12963
12964        if df.cascade {
12965            self.write_space();
12966            self.write_keyword("CASCADE");
12967        }
12968
12969        Ok(())
12970    }
12971
12972    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
12973        self.write_keyword("CREATE");
12974
12975        if cp.or_alter {
12976            self.write_space();
12977            self.write_keyword("OR ALTER");
12978        } else if cp.or_replace {
12979            self.write_space();
12980            self.write_keyword("OR REPLACE");
12981        }
12982
12983        self.write_space();
12984        if cp.use_proc_keyword {
12985            self.write_keyword("PROC");
12986        } else {
12987            self.write_keyword("PROCEDURE");
12988        }
12989
12990        if cp.if_not_exists {
12991            self.write_space();
12992            self.write_keyword("IF NOT EXISTS");
12993        }
12994
12995        self.write_space();
12996        self.generate_table(&cp.name)?;
12997        if cp.has_parens {
12998            self.write("(");
12999            self.generate_function_parameters(&cp.parameters)?;
13000            self.write(")");
13001        } else if !cp.parameters.is_empty() {
13002            // TSQL: unparenthesized parameters
13003            self.write_space();
13004            self.generate_function_parameters(&cp.parameters)?;
13005        }
13006
13007        // RETURNS clause (Snowflake)
13008        if let Some(return_type) = &cp.return_type {
13009            self.write_space();
13010            self.write_keyword("RETURNS");
13011            self.write_space();
13012            self.generate_data_type(return_type)?;
13013        }
13014
13015        // EXECUTE AS clause (Snowflake)
13016        if let Some(execute_as) = &cp.execute_as {
13017            self.write_space();
13018            self.write_keyword("EXECUTE AS");
13019            self.write_space();
13020            self.write_keyword(execute_as);
13021        }
13022
13023        if let Some(lang) = &cp.language {
13024            self.write_space();
13025            self.write_keyword("LANGUAGE");
13026            self.write_space();
13027            self.write(lang);
13028        }
13029
13030        if let Some(security) = &cp.security {
13031            self.write_space();
13032            self.write_keyword("SECURITY");
13033            self.write_space();
13034            match security {
13035                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
13036                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
13037                FunctionSecurity::None => self.write_keyword("NONE"),
13038            }
13039        }
13040
13041        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
13042        if !cp.with_options.is_empty() {
13043            self.write_space();
13044            self.write_keyword("WITH");
13045            self.write_space();
13046            for (i, opt) in cp.with_options.iter().enumerate() {
13047                if i > 0 {
13048                    self.write(", ");
13049                }
13050                self.write(opt);
13051            }
13052        }
13053
13054        if let Some(body) = &cp.body {
13055            self.write_space();
13056            match body {
13057                FunctionBody::Block(block) => {
13058                    self.write_keyword("AS");
13059                    if matches!(
13060                        self.config.dialect,
13061                        Some(crate::dialects::DialectType::TSQL)
13062                    ) {
13063                        self.write(" BEGIN ");
13064                        self.write(block);
13065                        self.write(" END");
13066                    } else if matches!(
13067                        self.config.dialect,
13068                        Some(crate::dialects::DialectType::PostgreSQL)
13069                    ) {
13070                        self.write(" $$");
13071                        self.write(block);
13072                        self.write("$$");
13073                    } else {
13074                        // Escape content for single-quoted output
13075                        let escaped = self.escape_block_for_single_quote(block);
13076                        self.write(" '");
13077                        self.write(&escaped);
13078                        self.write("'");
13079                    }
13080                }
13081                FunctionBody::StringLiteral(s) => {
13082                    self.write_keyword("AS");
13083                    self.write(" '");
13084                    self.write(s);
13085                    self.write("'");
13086                }
13087                FunctionBody::Expression(expr) => {
13088                    self.write_keyword("AS");
13089                    self.write_space();
13090                    self.generate_expression(expr)?;
13091                }
13092                FunctionBody::External(name) => {
13093                    self.write_keyword("EXTERNAL NAME");
13094                    self.write(" '");
13095                    self.write(name);
13096                    self.write("'");
13097                }
13098                FunctionBody::Return(expr) => {
13099                    self.write_keyword("RETURN");
13100                    self.write_space();
13101                    self.generate_expression(expr)?;
13102                }
13103                FunctionBody::Statements(stmts) => {
13104                    self.write_keyword("AS");
13105                    self.write(" BEGIN ");
13106                    for (i, stmt) in stmts.iter().enumerate() {
13107                        if i > 0 {
13108                            self.write(" ");
13109                        }
13110                        self.generate_expression(stmt)?;
13111                        self.write(";");
13112                    }
13113                    self.write(" END");
13114                }
13115                FunctionBody::RawBlock(text) => {
13116                    self.write_newline();
13117                    self.write(text);
13118                }
13119                FunctionBody::DollarQuoted { content, tag } => {
13120                    self.write_keyword("AS");
13121                    self.write(" ");
13122                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
13123                    let supports_dollar_quoting = matches!(
13124                        self.config.dialect,
13125                        Some(crate::dialects::DialectType::PostgreSQL)
13126                            | Some(crate::dialects::DialectType::Databricks)
13127                            | Some(crate::dialects::DialectType::Redshift)
13128                            | Some(crate::dialects::DialectType::DuckDB)
13129                    );
13130                    if supports_dollar_quoting {
13131                        // Output in dollar-quoted format
13132                        self.write("$");
13133                        if let Some(t) = tag {
13134                            self.write(t);
13135                        }
13136                        self.write("$");
13137                        self.write(content);
13138                        self.write("$");
13139                        if let Some(t) = tag {
13140                            self.write(t);
13141                        }
13142                        self.write("$");
13143                    } else {
13144                        // Convert to single-quoted string for other dialects
13145                        let escaped = self.escape_block_for_single_quote(content);
13146                        self.write("'");
13147                        self.write(&escaped);
13148                        self.write("'");
13149                    }
13150                }
13151            }
13152        }
13153
13154        Ok(())
13155    }
13156
13157    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
13158        self.write_keyword("DROP PROCEDURE");
13159
13160        if dp.if_exists {
13161            self.write_space();
13162            self.write_keyword("IF EXISTS");
13163        }
13164
13165        self.write_space();
13166        self.generate_table(&dp.name)?;
13167
13168        if let Some(params) = &dp.parameters {
13169            self.write(" (");
13170            for (i, dt) in params.iter().enumerate() {
13171                if i > 0 {
13172                    self.write(", ");
13173                }
13174                self.generate_data_type(dt)?;
13175            }
13176            self.write(")");
13177        }
13178
13179        if dp.cascade {
13180            self.write_space();
13181            self.write_keyword("CASCADE");
13182        }
13183
13184        Ok(())
13185    }
13186
13187    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
13188        self.write_keyword("CREATE");
13189
13190        if cs.or_replace {
13191            self.write_space();
13192            self.write_keyword("OR REPLACE");
13193        }
13194
13195        if cs.temporary {
13196            self.write_space();
13197            self.write_keyword("TEMPORARY");
13198        }
13199
13200        self.write_space();
13201        self.write_keyword("SEQUENCE");
13202
13203        if cs.if_not_exists {
13204            self.write_space();
13205            self.write_keyword("IF NOT EXISTS");
13206        }
13207
13208        self.write_space();
13209        self.generate_table(&cs.name)?;
13210
13211        // Output AS <type> if present
13212        if let Some(as_type) = &cs.as_type {
13213            self.write_space();
13214            self.write_keyword("AS");
13215            self.write_space();
13216            self.generate_data_type(as_type)?;
13217        }
13218
13219        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
13220        if let Some(comment) = &cs.comment {
13221            self.write_space();
13222            self.write_keyword("COMMENT");
13223            self.write("=");
13224            self.generate_string_literal(comment)?;
13225        }
13226
13227        // If property_order is available, use it to preserve original order
13228        if !cs.property_order.is_empty() {
13229            for prop in &cs.property_order {
13230                match prop {
13231                    SeqPropKind::Start => {
13232                        if let Some(start) = cs.start {
13233                            self.write_space();
13234                            self.write_keyword("START WITH");
13235                            self.write(&format!(" {}", start));
13236                        }
13237                    }
13238                    SeqPropKind::Increment => {
13239                        if let Some(inc) = cs.increment {
13240                            self.write_space();
13241                            self.write_keyword("INCREMENT BY");
13242                            self.write(&format!(" {}", inc));
13243                        }
13244                    }
13245                    SeqPropKind::Minvalue => {
13246                        if let Some(min) = &cs.minvalue {
13247                            self.write_space();
13248                            match min {
13249                                SequenceBound::Value(v) => {
13250                                    self.write_keyword("MINVALUE");
13251                                    self.write(&format!(" {}", v));
13252                                }
13253                                SequenceBound::None => {
13254                                    self.write_keyword("NO MINVALUE");
13255                                }
13256                            }
13257                        }
13258                    }
13259                    SeqPropKind::Maxvalue => {
13260                        if let Some(max) = &cs.maxvalue {
13261                            self.write_space();
13262                            match max {
13263                                SequenceBound::Value(v) => {
13264                                    self.write_keyword("MAXVALUE");
13265                                    self.write(&format!(" {}", v));
13266                                }
13267                                SequenceBound::None => {
13268                                    self.write_keyword("NO MAXVALUE");
13269                                }
13270                            }
13271                        }
13272                    }
13273                    SeqPropKind::Cache => {
13274                        if let Some(cache) = cs.cache {
13275                            self.write_space();
13276                            self.write_keyword("CACHE");
13277                            self.write(&format!(" {}", cache));
13278                        }
13279                    }
13280                    SeqPropKind::NoCache => {
13281                        self.write_space();
13282                        self.write_keyword("NO CACHE");
13283                    }
13284                    SeqPropKind::NoCacheWord => {
13285                        self.write_space();
13286                        self.write_keyword("NOCACHE");
13287                    }
13288                    SeqPropKind::Cycle => {
13289                        self.write_space();
13290                        self.write_keyword("CYCLE");
13291                    }
13292                    SeqPropKind::NoCycle => {
13293                        self.write_space();
13294                        self.write_keyword("NO CYCLE");
13295                    }
13296                    SeqPropKind::NoCycleWord => {
13297                        self.write_space();
13298                        self.write_keyword("NOCYCLE");
13299                    }
13300                    SeqPropKind::OwnedBy => {
13301                        // Skip OWNED BY NONE (it's a no-op)
13302                        if !cs.owned_by_none {
13303                            if let Some(owned) = &cs.owned_by {
13304                                self.write_space();
13305                                self.write_keyword("OWNED BY");
13306                                self.write_space();
13307                                self.generate_table(owned)?;
13308                            }
13309                        }
13310                    }
13311                    SeqPropKind::Order => {
13312                        self.write_space();
13313                        self.write_keyword("ORDER");
13314                    }
13315                    SeqPropKind::NoOrder => {
13316                        self.write_space();
13317                        self.write_keyword("NOORDER");
13318                    }
13319                    SeqPropKind::Comment => {
13320                        // COMMENT is output above, before property_order iteration
13321                    }
13322                    SeqPropKind::Sharing => {
13323                        if let Some(val) = &cs.sharing {
13324                            self.write_space();
13325                            self.write(&format!("SHARING={}", val));
13326                        }
13327                    }
13328                    SeqPropKind::Keep => {
13329                        self.write_space();
13330                        self.write_keyword("KEEP");
13331                    }
13332                    SeqPropKind::NoKeep => {
13333                        self.write_space();
13334                        self.write_keyword("NOKEEP");
13335                    }
13336                    SeqPropKind::Scale => {
13337                        self.write_space();
13338                        self.write_keyword("SCALE");
13339                        if let Some(modifier) = &cs.scale_modifier {
13340                            if !modifier.is_empty() {
13341                                self.write_space();
13342                                self.write_keyword(modifier);
13343                            }
13344                        }
13345                    }
13346                    SeqPropKind::NoScale => {
13347                        self.write_space();
13348                        self.write_keyword("NOSCALE");
13349                    }
13350                    SeqPropKind::Shard => {
13351                        self.write_space();
13352                        self.write_keyword("SHARD");
13353                        if let Some(modifier) = &cs.shard_modifier {
13354                            if !modifier.is_empty() {
13355                                self.write_space();
13356                                self.write_keyword(modifier);
13357                            }
13358                        }
13359                    }
13360                    SeqPropKind::NoShard => {
13361                        self.write_space();
13362                        self.write_keyword("NOSHARD");
13363                    }
13364                    SeqPropKind::Session => {
13365                        self.write_space();
13366                        self.write_keyword("SESSION");
13367                    }
13368                    SeqPropKind::Global => {
13369                        self.write_space();
13370                        self.write_keyword("GLOBAL");
13371                    }
13372                    SeqPropKind::NoMinvalueWord => {
13373                        self.write_space();
13374                        self.write_keyword("NOMINVALUE");
13375                    }
13376                    SeqPropKind::NoMaxvalueWord => {
13377                        self.write_space();
13378                        self.write_keyword("NOMAXVALUE");
13379                    }
13380                }
13381            }
13382        } else {
13383            // Fallback: default order for backwards compatibility
13384            if let Some(inc) = cs.increment {
13385                self.write_space();
13386                self.write_keyword("INCREMENT BY");
13387                self.write(&format!(" {}", inc));
13388            }
13389
13390            if let Some(min) = &cs.minvalue {
13391                self.write_space();
13392                match min {
13393                    SequenceBound::Value(v) => {
13394                        self.write_keyword("MINVALUE");
13395                        self.write(&format!(" {}", v));
13396                    }
13397                    SequenceBound::None => {
13398                        self.write_keyword("NO MINVALUE");
13399                    }
13400                }
13401            }
13402
13403            if let Some(max) = &cs.maxvalue {
13404                self.write_space();
13405                match max {
13406                    SequenceBound::Value(v) => {
13407                        self.write_keyword("MAXVALUE");
13408                        self.write(&format!(" {}", v));
13409                    }
13410                    SequenceBound::None => {
13411                        self.write_keyword("NO MAXVALUE");
13412                    }
13413                }
13414            }
13415
13416            if let Some(start) = cs.start {
13417                self.write_space();
13418                self.write_keyword("START WITH");
13419                self.write(&format!(" {}", start));
13420            }
13421
13422            if let Some(cache) = cs.cache {
13423                self.write_space();
13424                self.write_keyword("CACHE");
13425                self.write(&format!(" {}", cache));
13426            }
13427
13428            if cs.cycle {
13429                self.write_space();
13430                self.write_keyword("CYCLE");
13431            }
13432
13433            if let Some(owned) = &cs.owned_by {
13434                self.write_space();
13435                self.write_keyword("OWNED BY");
13436                self.write_space();
13437                self.generate_table(owned)?;
13438            }
13439        }
13440
13441        Ok(())
13442    }
13443
13444    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
13445        self.write_keyword("DROP SEQUENCE");
13446
13447        if ds.if_exists {
13448            self.write_space();
13449            self.write_keyword("IF EXISTS");
13450        }
13451
13452        self.write_space();
13453        self.generate_table(&ds.name)?;
13454
13455        if ds.cascade {
13456            self.write_space();
13457            self.write_keyword("CASCADE");
13458        }
13459
13460        Ok(())
13461    }
13462
13463    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
13464        self.write_keyword("ALTER SEQUENCE");
13465
13466        if als.if_exists {
13467            self.write_space();
13468            self.write_keyword("IF EXISTS");
13469        }
13470
13471        self.write_space();
13472        self.generate_table(&als.name)?;
13473
13474        if let Some(inc) = als.increment {
13475            self.write_space();
13476            self.write_keyword("INCREMENT BY");
13477            self.write(&format!(" {}", inc));
13478        }
13479
13480        if let Some(min) = &als.minvalue {
13481            self.write_space();
13482            match min {
13483                SequenceBound::Value(v) => {
13484                    self.write_keyword("MINVALUE");
13485                    self.write(&format!(" {}", v));
13486                }
13487                SequenceBound::None => {
13488                    self.write_keyword("NO MINVALUE");
13489                }
13490            }
13491        }
13492
13493        if let Some(max) = &als.maxvalue {
13494            self.write_space();
13495            match max {
13496                SequenceBound::Value(v) => {
13497                    self.write_keyword("MAXVALUE");
13498                    self.write(&format!(" {}", v));
13499                }
13500                SequenceBound::None => {
13501                    self.write_keyword("NO MAXVALUE");
13502                }
13503            }
13504        }
13505
13506        if let Some(start) = als.start {
13507            self.write_space();
13508            self.write_keyword("START WITH");
13509            self.write(&format!(" {}", start));
13510        }
13511
13512        if let Some(restart) = &als.restart {
13513            self.write_space();
13514            self.write_keyword("RESTART");
13515            if let Some(val) = restart {
13516                self.write_keyword(" WITH");
13517                self.write(&format!(" {}", val));
13518            }
13519        }
13520
13521        if let Some(cache) = als.cache {
13522            self.write_space();
13523            self.write_keyword("CACHE");
13524            self.write(&format!(" {}", cache));
13525        }
13526
13527        if let Some(cycle) = als.cycle {
13528            self.write_space();
13529            if cycle {
13530                self.write_keyword("CYCLE");
13531            } else {
13532                self.write_keyword("NO CYCLE");
13533            }
13534        }
13535
13536        if let Some(owned) = &als.owned_by {
13537            self.write_space();
13538            self.write_keyword("OWNED BY");
13539            self.write_space();
13540            if let Some(table) = owned {
13541                self.generate_table(table)?;
13542            } else {
13543                self.write_keyword("NONE");
13544            }
13545        }
13546
13547        Ok(())
13548    }
13549
13550    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
13551        self.write_keyword("CREATE");
13552
13553        if ct.or_alter {
13554            self.write_space();
13555            self.write_keyword("OR ALTER");
13556        } else if ct.or_replace {
13557            self.write_space();
13558            self.write_keyword("OR REPLACE");
13559        }
13560
13561        if ct.constraint {
13562            self.write_space();
13563            self.write_keyword("CONSTRAINT");
13564        }
13565
13566        self.write_space();
13567        self.write_keyword("TRIGGER");
13568        self.write_space();
13569        self.generate_identifier(&ct.name)?;
13570
13571        self.write_space();
13572        match ct.timing {
13573            TriggerTiming::Before => self.write_keyword("BEFORE"),
13574            TriggerTiming::After => self.write_keyword("AFTER"),
13575            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
13576        }
13577
13578        // Events
13579        for (i, event) in ct.events.iter().enumerate() {
13580            if i > 0 {
13581                self.write_keyword(" OR");
13582            }
13583            self.write_space();
13584            match event {
13585                TriggerEvent::Insert => self.write_keyword("INSERT"),
13586                TriggerEvent::Update(cols) => {
13587                    self.write_keyword("UPDATE");
13588                    if let Some(cols) = cols {
13589                        self.write_space();
13590                        self.write_keyword("OF");
13591                        for (j, col) in cols.iter().enumerate() {
13592                            if j > 0 {
13593                                self.write(",");
13594                            }
13595                            self.write_space();
13596                            self.generate_identifier(col)?;
13597                        }
13598                    }
13599                }
13600                TriggerEvent::Delete => self.write_keyword("DELETE"),
13601                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
13602            }
13603        }
13604
13605        self.write_space();
13606        self.write_keyword("ON");
13607        self.write_space();
13608        self.generate_table(&ct.table)?;
13609
13610        // Referencing clause
13611        if let Some(ref_clause) = &ct.referencing {
13612            self.write_space();
13613            self.write_keyword("REFERENCING");
13614            if let Some(old_table) = &ref_clause.old_table {
13615                self.write_space();
13616                self.write_keyword("OLD TABLE AS");
13617                self.write_space();
13618                self.generate_identifier(old_table)?;
13619            }
13620            if let Some(new_table) = &ref_clause.new_table {
13621                self.write_space();
13622                self.write_keyword("NEW TABLE AS");
13623                self.write_space();
13624                self.generate_identifier(new_table)?;
13625            }
13626            if let Some(old_row) = &ref_clause.old_row {
13627                self.write_space();
13628                self.write_keyword("OLD ROW AS");
13629                self.write_space();
13630                self.generate_identifier(old_row)?;
13631            }
13632            if let Some(new_row) = &ref_clause.new_row {
13633                self.write_space();
13634                self.write_keyword("NEW ROW AS");
13635                self.write_space();
13636                self.generate_identifier(new_row)?;
13637            }
13638        }
13639
13640        // Deferrable options for constraint triggers (must come before FOR EACH)
13641        if let Some(deferrable) = ct.deferrable {
13642            self.write_space();
13643            if deferrable {
13644                self.write_keyword("DEFERRABLE");
13645            } else {
13646                self.write_keyword("NOT DEFERRABLE");
13647            }
13648        }
13649
13650        if let Some(initially) = ct.initially_deferred {
13651            self.write_space();
13652            self.write_keyword("INITIALLY");
13653            self.write_space();
13654            if initially {
13655                self.write_keyword("DEFERRED");
13656            } else {
13657                self.write_keyword("IMMEDIATE");
13658            }
13659        }
13660
13661        if let Some(for_each) = ct.for_each {
13662            self.write_space();
13663            self.write_keyword("FOR EACH");
13664            self.write_space();
13665            match for_each {
13666                TriggerForEach::Row => self.write_keyword("ROW"),
13667                TriggerForEach::Statement => self.write_keyword("STATEMENT"),
13668            }
13669        }
13670
13671        // When clause
13672        if let Some(when) = &ct.when {
13673            self.write_space();
13674            self.write_keyword("WHEN");
13675            if ct.when_paren {
13676                self.write(" (");
13677                self.generate_expression(when)?;
13678                self.write(")");
13679            } else {
13680                self.write_space();
13681                self.generate_expression(when)?;
13682            }
13683        }
13684
13685        // Body
13686        self.write_space();
13687        match &ct.body {
13688            TriggerBody::Execute { function, args } => {
13689                self.write_keyword("EXECUTE FUNCTION");
13690                self.write_space();
13691                self.generate_table(function)?;
13692                self.write("(");
13693                for (i, arg) in args.iter().enumerate() {
13694                    if i > 0 {
13695                        self.write(", ");
13696                    }
13697                    self.generate_expression(arg)?;
13698                }
13699                self.write(")");
13700            }
13701            TriggerBody::Block(block) => {
13702                self.write_keyword("BEGIN");
13703                self.write_space();
13704                self.write(block);
13705                self.write_space();
13706                self.write_keyword("END");
13707            }
13708        }
13709
13710        Ok(())
13711    }
13712
13713    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
13714        self.write_keyword("DROP TRIGGER");
13715
13716        if dt.if_exists {
13717            self.write_space();
13718            self.write_keyword("IF EXISTS");
13719        }
13720
13721        self.write_space();
13722        self.generate_identifier(&dt.name)?;
13723
13724        if let Some(table) = &dt.table {
13725            self.write_space();
13726            self.write_keyword("ON");
13727            self.write_space();
13728            self.generate_table(table)?;
13729        }
13730
13731        if dt.cascade {
13732            self.write_space();
13733            self.write_keyword("CASCADE");
13734        }
13735
13736        Ok(())
13737    }
13738
13739    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
13740        self.write_keyword("CREATE TYPE");
13741
13742        if ct.if_not_exists {
13743            self.write_space();
13744            self.write_keyword("IF NOT EXISTS");
13745        }
13746
13747        self.write_space();
13748        self.generate_table(&ct.name)?;
13749
13750        self.write_space();
13751        self.write_keyword("AS");
13752        self.write_space();
13753
13754        match &ct.definition {
13755            TypeDefinition::Enum(values) => {
13756                self.write_keyword("ENUM");
13757                self.write(" (");
13758                for (i, val) in values.iter().enumerate() {
13759                    if i > 0 {
13760                        self.write(", ");
13761                    }
13762                    self.write(&format!("'{}'", val));
13763                }
13764                self.write(")");
13765            }
13766            TypeDefinition::Composite(attrs) => {
13767                self.write("(");
13768                for (i, attr) in attrs.iter().enumerate() {
13769                    if i > 0 {
13770                        self.write(", ");
13771                    }
13772                    self.generate_identifier(&attr.name)?;
13773                    self.write_space();
13774                    self.generate_data_type(&attr.data_type)?;
13775                    if let Some(collate) = &attr.collate {
13776                        self.write_space();
13777                        self.write_keyword("COLLATE");
13778                        self.write_space();
13779                        self.generate_identifier(collate)?;
13780                    }
13781                }
13782                self.write(")");
13783            }
13784            TypeDefinition::Range {
13785                subtype,
13786                subtype_diff,
13787                canonical,
13788            } => {
13789                self.write_keyword("RANGE");
13790                self.write(" (");
13791                self.write_keyword("SUBTYPE");
13792                self.write(" = ");
13793                self.generate_data_type(subtype)?;
13794                if let Some(diff) = subtype_diff {
13795                    self.write(", ");
13796                    self.write_keyword("SUBTYPE_DIFF");
13797                    self.write(" = ");
13798                    self.write(diff);
13799                }
13800                if let Some(canon) = canonical {
13801                    self.write(", ");
13802                    self.write_keyword("CANONICAL");
13803                    self.write(" = ");
13804                    self.write(canon);
13805                }
13806                self.write(")");
13807            }
13808            TypeDefinition::Base {
13809                input,
13810                output,
13811                internallength,
13812            } => {
13813                self.write("(");
13814                self.write_keyword("INPUT");
13815                self.write(" = ");
13816                self.write(input);
13817                self.write(", ");
13818                self.write_keyword("OUTPUT");
13819                self.write(" = ");
13820                self.write(output);
13821                if let Some(len) = internallength {
13822                    self.write(", ");
13823                    self.write_keyword("INTERNALLENGTH");
13824                    self.write(" = ");
13825                    self.write(&len.to_string());
13826                }
13827                self.write(")");
13828            }
13829            TypeDefinition::Domain {
13830                base_type,
13831                default,
13832                constraints,
13833            } => {
13834                self.generate_data_type(base_type)?;
13835                if let Some(def) = default {
13836                    self.write_space();
13837                    self.write_keyword("DEFAULT");
13838                    self.write_space();
13839                    self.generate_expression(def)?;
13840                }
13841                for constr in constraints {
13842                    self.write_space();
13843                    if let Some(name) = &constr.name {
13844                        self.write_keyword("CONSTRAINT");
13845                        self.write_space();
13846                        self.generate_identifier(name)?;
13847                        self.write_space();
13848                    }
13849                    self.write_keyword("CHECK");
13850                    self.write(" (");
13851                    self.generate_expression(&constr.check)?;
13852                    self.write(")");
13853                }
13854            }
13855        }
13856
13857        Ok(())
13858    }
13859
13860    fn generate_create_task(&mut self, task: &crate::expressions::CreateTask) -> Result<()> {
13861        self.write_keyword("CREATE");
13862        if task.or_replace {
13863            self.write_space();
13864            self.write_keyword("OR REPLACE");
13865        }
13866        self.write_space();
13867        self.write_keyword("TASK");
13868        if task.if_not_exists {
13869            self.write_space();
13870            self.write_keyword("IF NOT EXISTS");
13871        }
13872        self.write_space();
13873        self.write(&task.name);
13874        if !task.properties.is_empty() {
13875            // Properties already include leading whitespace from tokens_to_sql
13876            if !task.properties.starts_with('\n') && !task.properties.starts_with(' ') {
13877                self.write_space();
13878            }
13879            self.write(&task.properties);
13880        }
13881        self.write_space();
13882        self.write_keyword("AS");
13883        self.write_space();
13884        self.generate_expression(&task.body)?;
13885        Ok(())
13886    }
13887
13888    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
13889        self.write_keyword("DROP TYPE");
13890
13891        if dt.if_exists {
13892            self.write_space();
13893            self.write_keyword("IF EXISTS");
13894        }
13895
13896        self.write_space();
13897        self.generate_table(&dt.name)?;
13898
13899        if dt.cascade {
13900            self.write_space();
13901            self.write_keyword("CASCADE");
13902        }
13903
13904        Ok(())
13905    }
13906
13907    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
13908        // Athena: DESCRIBE uses Hive engine (backticks)
13909        let saved_athena_hive_context = self.athena_hive_context;
13910        if matches!(
13911            self.config.dialect,
13912            Some(crate::dialects::DialectType::Athena)
13913        ) {
13914            self.athena_hive_context = true;
13915        }
13916
13917        // Output leading comments before DESCRIBE
13918        for comment in &d.leading_comments {
13919            self.write_formatted_comment(comment);
13920            self.write(" ");
13921        }
13922
13923        self.write_keyword("DESCRIBE");
13924
13925        if d.extended {
13926            self.write_space();
13927            self.write_keyword("EXTENDED");
13928        } else if d.formatted {
13929            self.write_space();
13930            self.write_keyword("FORMATTED");
13931        }
13932
13933        // Output style like ANALYZE, HISTORY
13934        if let Some(ref style) = d.style {
13935            self.write_space();
13936            self.write_keyword(style);
13937        }
13938
13939        // Handle object kind (TABLE, VIEW) based on dialect
13940        let should_output_kind = match self.config.dialect {
13941            // Spark doesn't use TABLE/VIEW after DESCRIBE
13942            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
13943                false
13944            }
13945            // Snowflake always includes TABLE
13946            Some(DialectType::Snowflake) => true,
13947            _ => d.kind.is_some(),
13948        };
13949        if should_output_kind {
13950            if let Some(ref kind) = d.kind {
13951                self.write_space();
13952                self.write_keyword(kind);
13953            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
13954                self.write_space();
13955                self.write_keyword("TABLE");
13956            }
13957        }
13958
13959        self.write_space();
13960        self.generate_expression(&d.target)?;
13961
13962        // Output parenthesized parameter types for PROCEDURE/FUNCTION
13963        if !d.params.is_empty() {
13964            self.write("(");
13965            for (i, param) in d.params.iter().enumerate() {
13966                if i > 0 {
13967                    self.write(", ");
13968                }
13969                self.write(param);
13970            }
13971            self.write(")");
13972        }
13973
13974        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
13975        if let Some(ref partition) = d.partition {
13976            self.write_space();
13977            self.generate_expression(partition)?;
13978        }
13979
13980        // Databricks: AS JSON
13981        if d.as_json {
13982            self.write_space();
13983            self.write_keyword("AS JSON");
13984        }
13985
13986        // Output properties like type=stage
13987        for (name, value) in &d.properties {
13988            self.write_space();
13989            self.write(name);
13990            self.write("=");
13991            self.write(value);
13992        }
13993
13994        // Restore Athena Hive context
13995        self.athena_hive_context = saved_athena_hive_context;
13996
13997        Ok(())
13998    }
13999
14000    /// Generate SHOW statement (Snowflake, MySQL, etc.)
14001    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
14002    fn generate_show(&mut self, s: &Show) -> Result<()> {
14003        self.write_keyword("SHOW");
14004        self.write_space();
14005
14006        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
14007        // where TERSE is syntactically valid but has no effect on output
14008        let show_terse = s.terse
14009            && !matches!(
14010                s.this.as_str(),
14011                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
14012            );
14013        if show_terse {
14014            self.write_keyword("TERSE");
14015            self.write_space();
14016        }
14017
14018        // Object type (USERS, TABLES, DATABASES, etc.)
14019        self.write_keyword(&s.this);
14020
14021        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
14022        if let Some(ref target_expr) = s.target {
14023            self.write_space();
14024            self.generate_expression(target_expr)?;
14025        }
14026
14027        // HISTORY keyword
14028        if s.history {
14029            self.write_space();
14030            self.write_keyword("HISTORY");
14031        }
14032
14033        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
14034        if let Some(ref for_target) = s.for_target {
14035            self.write_space();
14036            self.write_keyword("FOR");
14037            self.write_space();
14038            self.generate_expression(for_target)?;
14039        }
14040
14041        // Determine ordering based on dialect:
14042        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
14043        // - MySQL: IN, FROM, LIKE (when FROM is present)
14044        use crate::dialects::DialectType;
14045        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
14046
14047        if !is_snowflake && s.from.is_some() {
14048            // MySQL ordering: IN, FROM, LIKE
14049
14050            // IN scope_kind [scope]
14051            if let Some(ref scope_kind) = s.scope_kind {
14052                self.write_space();
14053                self.write_keyword("IN");
14054                self.write_space();
14055                self.write_keyword(scope_kind);
14056                if let Some(ref scope) = s.scope {
14057                    self.write_space();
14058                    self.generate_expression(scope)?;
14059                }
14060            } else if let Some(ref scope) = s.scope {
14061                self.write_space();
14062                self.write_keyword("IN");
14063                self.write_space();
14064                self.generate_expression(scope)?;
14065            }
14066
14067            // FROM clause
14068            if let Some(ref from) = s.from {
14069                self.write_space();
14070                self.write_keyword("FROM");
14071                self.write_space();
14072                self.generate_expression(from)?;
14073            }
14074
14075            // Second FROM clause (db name)
14076            if let Some(ref db) = s.db {
14077                self.write_space();
14078                self.write_keyword("FROM");
14079                self.write_space();
14080                self.generate_expression(db)?;
14081            }
14082
14083            // LIKE pattern
14084            if let Some(ref like) = s.like {
14085                self.write_space();
14086                self.write_keyword("LIKE");
14087                self.write_space();
14088                self.generate_expression(like)?;
14089            }
14090        } else {
14091            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
14092
14093            // LIKE pattern
14094            if let Some(ref like) = s.like {
14095                self.write_space();
14096                self.write_keyword("LIKE");
14097                self.write_space();
14098                self.generate_expression(like)?;
14099            }
14100
14101            // IN scope_kind [scope]
14102            if let Some(ref scope_kind) = s.scope_kind {
14103                self.write_space();
14104                self.write_keyword("IN");
14105                self.write_space();
14106                self.write_keyword(scope_kind);
14107                if let Some(ref scope) = s.scope {
14108                    self.write_space();
14109                    self.generate_expression(scope)?;
14110                }
14111            } else if let Some(ref scope) = s.scope {
14112                self.write_space();
14113                self.write_keyword("IN");
14114                self.write_space();
14115                self.generate_expression(scope)?;
14116            }
14117        }
14118
14119        // STARTS WITH pattern
14120        if let Some(ref starts_with) = s.starts_with {
14121            self.write_space();
14122            self.write_keyword("STARTS WITH");
14123            self.write_space();
14124            self.generate_expression(starts_with)?;
14125        }
14126
14127        // LIMIT clause
14128        if let Some(ref limit) = s.limit {
14129            self.write_space();
14130            self.generate_limit(limit)?;
14131        }
14132
14133        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
14134        if is_snowflake {
14135            if let Some(ref from) = s.from {
14136                self.write_space();
14137                self.write_keyword("FROM");
14138                self.write_space();
14139                self.generate_expression(from)?;
14140            }
14141        }
14142
14143        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
14144        if let Some(ref where_clause) = s.where_clause {
14145            self.write_space();
14146            self.write_keyword("WHERE");
14147            self.write_space();
14148            self.generate_expression(where_clause)?;
14149        }
14150
14151        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
14152        if let Some(is_mutex) = s.mutex {
14153            self.write_space();
14154            if is_mutex {
14155                self.write_keyword("MUTEX");
14156            } else {
14157                self.write_keyword("STATUS");
14158            }
14159        }
14160
14161        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
14162        if !s.privileges.is_empty() {
14163            self.write_space();
14164            self.write_keyword("WITH PRIVILEGES");
14165            self.write_space();
14166            for (i, priv_name) in s.privileges.iter().enumerate() {
14167                if i > 0 {
14168                    self.write(", ");
14169                }
14170                self.write_keyword(priv_name);
14171            }
14172        }
14173
14174        Ok(())
14175    }
14176
14177    // ==================== End DDL Generation ====================
14178
14179    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
14180        use crate::dialects::DialectType;
14181        match lit {
14182            Literal::String(s) => {
14183                self.generate_string_literal(s)?;
14184            }
14185            Literal::Number(n) => {
14186                if matches!(self.config.dialect, Some(DialectType::MySQL))
14187                    && n.len() > 2
14188                    && (n.starts_with("0x") || n.starts_with("0X"))
14189                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
14190                {
14191                    return self.generate_identifier(&Identifier {
14192                        name: n.clone(),
14193                        quoted: true,
14194                        trailing_comments: Vec::new(),
14195                        span: None,
14196                    });
14197                }
14198                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
14199                // for dialects that don't support them (MySQL interprets as identifier).
14200                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
14201                let n = if n.contains('_')
14202                    && !matches!(
14203                        self.config.dialect,
14204                        Some(DialectType::ClickHouse)
14205                            | Some(DialectType::DuckDB)
14206                            | Some(DialectType::PostgreSQL)
14207                            | Some(DialectType::Hive)
14208                            | Some(DialectType::Spark)
14209                            | Some(DialectType::Databricks)
14210                    ) {
14211                    std::borrow::Cow::Owned(n.replace('_', ""))
14212                } else {
14213                    std::borrow::Cow::Borrowed(n.as_str())
14214                };
14215                // Normalize numbers starting with decimal point to have leading zero
14216                // e.g., .25 -> 0.25 (matches sqlglot behavior)
14217                if n.starts_with('.') {
14218                    self.write("0");
14219                    self.write(&n);
14220                } else if n.starts_with("-.") {
14221                    // Handle negative numbers like -.25 -> -0.25
14222                    self.write("-0");
14223                    self.write(&n[1..]);
14224                } else {
14225                    self.write(&n);
14226                }
14227            }
14228            Literal::HexString(h) => {
14229                // Most dialects use lowercase x'...' for hex literals; Spark/Databricks/Teradata use uppercase X'...'
14230                match self.config.dialect {
14231                    Some(DialectType::Spark)
14232                    | Some(DialectType::Databricks)
14233                    | Some(DialectType::Teradata) => self.write("X'"),
14234                    _ => self.write("x'"),
14235                }
14236                self.write(h);
14237                self.write("'");
14238            }
14239            Literal::HexNumber(h) => {
14240                // Hex number (0xA) - integer in hex notation (from BigQuery)
14241                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
14242                // For other dialects, convert to decimal integer
14243                match self.config.dialect {
14244                    Some(DialectType::BigQuery)
14245                    | Some(DialectType::TSQL)
14246                    | Some(DialectType::Fabric) => {
14247                        self.write("0x");
14248                        self.write(h);
14249                    }
14250                    _ => {
14251                        // Convert hex to decimal
14252                        if let Ok(val) = u64::from_str_radix(h, 16) {
14253                            self.write(&val.to_string());
14254                        } else {
14255                            // Fallback: keep as 0x notation
14256                            self.write("0x");
14257                            self.write(h);
14258                        }
14259                    }
14260                }
14261            }
14262            Literal::BitString(b) => {
14263                // Bit string B'0101...'
14264                self.write("B'");
14265                self.write(b);
14266                self.write("'");
14267            }
14268            Literal::ByteString(b) => {
14269                // Byte string b'...' (BigQuery style)
14270                self.write("b'");
14271                // Escape special characters for output
14272                self.write_escaped_byte_string(b);
14273                self.write("'");
14274            }
14275            Literal::NationalString(s) => {
14276                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
14277                // Other dialects strip the N prefix and output as regular string
14278                let keep_n_prefix = matches!(
14279                    self.config.dialect,
14280                    Some(DialectType::TSQL)
14281                        | Some(DialectType::Oracle)
14282                        | Some(DialectType::MySQL)
14283                        | None
14284                );
14285                if keep_n_prefix {
14286                    self.write("N'");
14287                } else {
14288                    self.write("'");
14289                }
14290                self.write(s);
14291                self.write("'");
14292            }
14293            Literal::Date(d) => {
14294                self.generate_date_literal(d)?;
14295            }
14296            Literal::Time(t) => {
14297                self.generate_time_literal(t)?;
14298            }
14299            Literal::Timestamp(ts) => {
14300                self.generate_timestamp_literal(ts)?;
14301            }
14302            Literal::Datetime(dt) => {
14303                self.generate_datetime_literal(dt)?;
14304            }
14305            Literal::TripleQuotedString(s, _quote_char) => {
14306                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
14307                if matches!(
14308                    self.config.dialect,
14309                    Some(crate::dialects::DialectType::BigQuery)
14310                        | Some(crate::dialects::DialectType::DuckDB)
14311                        | Some(crate::dialects::DialectType::Snowflake)
14312                        | Some(crate::dialects::DialectType::Spark)
14313                        | Some(crate::dialects::DialectType::Hive)
14314                        | Some(crate::dialects::DialectType::Presto)
14315                        | Some(crate::dialects::DialectType::Trino)
14316                        | Some(crate::dialects::DialectType::PostgreSQL)
14317                        | Some(crate::dialects::DialectType::MySQL)
14318                        | Some(crate::dialects::DialectType::Redshift)
14319                        | Some(crate::dialects::DialectType::TSQL)
14320                        | Some(crate::dialects::DialectType::Oracle)
14321                        | Some(crate::dialects::DialectType::ClickHouse)
14322                        | Some(crate::dialects::DialectType::Databricks)
14323                        | Some(crate::dialects::DialectType::SQLite)
14324                ) {
14325                    self.generate_string_literal(s)?;
14326                } else {
14327                    // Preserve triple-quoted string syntax for generic/unknown dialects
14328                    let quotes = format!("{0}{0}{0}", _quote_char);
14329                    self.write(&quotes);
14330                    self.write(s);
14331                    self.write(&quotes);
14332                }
14333            }
14334            Literal::EscapeString(s) => {
14335                // PostgreSQL escape string: e'...' or E'...'
14336                // Token text format is "e:content" or "E:content"
14337                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
14338                use crate::dialects::DialectType;
14339                let content = if let Some(c) = s.strip_prefix("e:") {
14340                    c
14341                } else if let Some(c) = s.strip_prefix("E:") {
14342                    c
14343                } else {
14344                    s.as_str()
14345                };
14346
14347                // MySQL: output the content without quotes or prefix
14348                if matches!(
14349                    self.config.dialect,
14350                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
14351                ) {
14352                    self.write(content);
14353                } else {
14354                    // Some dialects use lowercase e' prefix
14355                    let prefix = if matches!(
14356                        self.config.dialect,
14357                        Some(DialectType::SingleStore)
14358                            | Some(DialectType::DuckDB)
14359                            | Some(DialectType::PostgreSQL)
14360                            | Some(DialectType::CockroachDB)
14361                            | Some(DialectType::Materialize)
14362                            | Some(DialectType::RisingWave)
14363                    ) {
14364                        "e'"
14365                    } else {
14366                        "E'"
14367                    };
14368
14369                    // Normalize \' to '' for output
14370                    let normalized = content.replace("\\'", "''");
14371                    self.write(prefix);
14372                    self.write(&normalized);
14373                    self.write("'");
14374                }
14375            }
14376            Literal::DollarString(s) => {
14377                // Convert dollar-quoted strings to single-quoted strings
14378                // (like Python sqlglot's rawstring_sql)
14379                use crate::dialects::DialectType;
14380                // Extract content from tag\x00content format
14381                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
14382                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
14383                let escape_backslash = matches!(self.config.dialect, Some(DialectType::Snowflake));
14384                // Step 2: Determine quote escaping style
14385                // Snowflake: ' -> \' (backslash escape)
14386                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
14387                let use_backslash_quote =
14388                    matches!(self.config.dialect, Some(DialectType::Snowflake));
14389
14390                let mut escaped = String::with_capacity(content.len() + 4);
14391                for ch in content.chars() {
14392                    if escape_backslash && ch == '\\' {
14393                        // Escape backslash first (before quote escaping)
14394                        escaped.push('\\');
14395                        escaped.push('\\');
14396                    } else if ch == '\'' {
14397                        if use_backslash_quote {
14398                            escaped.push('\\');
14399                            escaped.push('\'');
14400                        } else {
14401                            escaped.push('\'');
14402                            escaped.push('\'');
14403                        }
14404                    } else {
14405                        escaped.push(ch);
14406                    }
14407                }
14408                self.write("'");
14409                self.write(&escaped);
14410                self.write("'");
14411            }
14412            Literal::RawString(s) => {
14413                // Raw strings (r"..." or r'...') contain literal backslashes.
14414                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
14415                // 1. If \\ is in STRING_ESCAPES, double all backslashes
14416                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
14417                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
14418                use crate::dialects::DialectType;
14419
14420                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
14421                let escape_backslash = matches!(
14422                    self.config.dialect,
14423                    Some(DialectType::BigQuery)
14424                        | Some(DialectType::MySQL)
14425                        | Some(DialectType::SingleStore)
14426                        | Some(DialectType::TiDB)
14427                        | Some(DialectType::Hive)
14428                        | Some(DialectType::Spark)
14429                        | Some(DialectType::Databricks)
14430                        | Some(DialectType::Drill)
14431                        | Some(DialectType::Snowflake)
14432                        | Some(DialectType::Redshift)
14433                        | Some(DialectType::ClickHouse)
14434                );
14435
14436                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
14437                // These escape quotes as \' instead of ''
14438                let backslash_escapes_quote = matches!(
14439                    self.config.dialect,
14440                    Some(DialectType::BigQuery)
14441                        | Some(DialectType::Hive)
14442                        | Some(DialectType::Spark)
14443                        | Some(DialectType::Databricks)
14444                        | Some(DialectType::Drill)
14445                        | Some(DialectType::Snowflake)
14446                        | Some(DialectType::Redshift)
14447                );
14448
14449                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
14450                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
14451                let supports_escape_sequences = escape_backslash;
14452
14453                let mut escaped = String::with_capacity(s.len() + 4);
14454                for ch in s.chars() {
14455                    if escape_backslash && ch == '\\' {
14456                        // Double the backslash for the target dialect
14457                        escaped.push('\\');
14458                        escaped.push('\\');
14459                    } else if ch == '\'' {
14460                        if backslash_escapes_quote {
14461                            // Use backslash to escape the quote: \'
14462                            escaped.push('\\');
14463                            escaped.push('\'');
14464                        } else {
14465                            // Use SQL standard quote doubling: ''
14466                            escaped.push('\'');
14467                            escaped.push('\'');
14468                        }
14469                    } else if supports_escape_sequences {
14470                        // Apply ESCAPED_SEQUENCES mapping for special chars
14471                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
14472                        match ch {
14473                            '\n' => {
14474                                escaped.push('\\');
14475                                escaped.push('n');
14476                            }
14477                            '\r' => {
14478                                escaped.push('\\');
14479                                escaped.push('r');
14480                            }
14481                            '\t' => {
14482                                escaped.push('\\');
14483                                escaped.push('t');
14484                            }
14485                            '\x07' => {
14486                                escaped.push('\\');
14487                                escaped.push('a');
14488                            }
14489                            '\x08' => {
14490                                escaped.push('\\');
14491                                escaped.push('b');
14492                            }
14493                            '\x0C' => {
14494                                escaped.push('\\');
14495                                escaped.push('f');
14496                            }
14497                            '\x0B' => {
14498                                escaped.push('\\');
14499                                escaped.push('v');
14500                            }
14501                            _ => escaped.push(ch),
14502                        }
14503                    } else {
14504                        escaped.push(ch);
14505                    }
14506                }
14507                self.write("'");
14508                self.write(&escaped);
14509                self.write("'");
14510            }
14511        }
14512        Ok(())
14513    }
14514
14515    /// Generate a DATE literal with dialect-specific formatting
14516    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
14517        use crate::dialects::DialectType;
14518
14519        match self.config.dialect {
14520            // SQL Server uses CONVERT or CAST
14521            Some(DialectType::TSQL) => {
14522                self.write("CAST('");
14523                self.write(d);
14524                self.write("' AS DATE)");
14525            }
14526            // BigQuery uses CAST syntax for type literals
14527            // DATE 'value' -> CAST('value' AS DATE)
14528            Some(DialectType::BigQuery) => {
14529                self.write("CAST('");
14530                self.write(d);
14531                self.write("' AS DATE)");
14532            }
14533            // Exasol uses CAST syntax for DATE literals
14534            // DATE 'value' -> CAST('value' AS DATE)
14535            Some(DialectType::Exasol) => {
14536                self.write("CAST('");
14537                self.write(d);
14538                self.write("' AS DATE)");
14539            }
14540            // Snowflake uses CAST syntax for DATE literals
14541            // DATE 'value' -> CAST('value' AS DATE)
14542            Some(DialectType::Snowflake) => {
14543                self.write("CAST('");
14544                self.write(d);
14545                self.write("' AS DATE)");
14546            }
14547            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
14548            Some(DialectType::PostgreSQL)
14549            | Some(DialectType::MySQL)
14550            | Some(DialectType::SingleStore)
14551            | Some(DialectType::TiDB)
14552            | Some(DialectType::Redshift) => {
14553                self.write("CAST('");
14554                self.write(d);
14555                self.write("' AS DATE)");
14556            }
14557            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
14558            Some(DialectType::DuckDB)
14559            | Some(DialectType::Presto)
14560            | Some(DialectType::Trino)
14561            | Some(DialectType::Athena)
14562            | Some(DialectType::Spark)
14563            | Some(DialectType::Databricks)
14564            | Some(DialectType::Hive) => {
14565                self.write("CAST('");
14566                self.write(d);
14567                self.write("' AS DATE)");
14568            }
14569            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
14570            Some(DialectType::Oracle) => {
14571                self.write("TO_DATE('");
14572                self.write(d);
14573                self.write("', 'YYYY-MM-DD')");
14574            }
14575            // Standard SQL: DATE '...'
14576            _ => {
14577                self.write_keyword("DATE");
14578                self.write(" '");
14579                self.write(d);
14580                self.write("'");
14581            }
14582        }
14583        Ok(())
14584    }
14585
14586    /// Generate a TIME literal with dialect-specific formatting
14587    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
14588        use crate::dialects::DialectType;
14589
14590        match self.config.dialect {
14591            // SQL Server uses CONVERT or CAST
14592            Some(DialectType::TSQL) => {
14593                self.write("CAST('");
14594                self.write(t);
14595                self.write("' AS TIME)");
14596            }
14597            // Standard SQL: TIME '...'
14598            _ => {
14599                self.write_keyword("TIME");
14600                self.write(" '");
14601                self.write(t);
14602                self.write("'");
14603            }
14604        }
14605        Ok(())
14606    }
14607
14608    /// Generate a date expression for Dremio, converting DATE literals to CAST
14609    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
14610        use crate::expressions::Literal;
14611
14612        match expr {
14613            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
14614                let Literal::Date(d) = lit.as_ref() else {
14615                    unreachable!()
14616                };
14617                // DATE 'value' -> CAST('value' AS DATE)
14618                self.write("CAST('");
14619                self.write(d);
14620                self.write("' AS DATE)");
14621            }
14622            _ => {
14623                // For all other expressions, generate normally
14624                self.generate_expression(expr)?;
14625            }
14626        }
14627        Ok(())
14628    }
14629
14630    /// Generate a TIMESTAMP literal with dialect-specific formatting
14631    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
14632        use crate::dialects::DialectType;
14633
14634        match self.config.dialect {
14635            // SQL Server uses CONVERT or CAST
14636            Some(DialectType::TSQL) => {
14637                self.write("CAST('");
14638                self.write(ts);
14639                self.write("' AS DATETIME2)");
14640            }
14641            // BigQuery uses CAST syntax for type literals
14642            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14643            Some(DialectType::BigQuery) => {
14644                self.write("CAST('");
14645                self.write(ts);
14646                self.write("' AS TIMESTAMP)");
14647            }
14648            // Snowflake uses CAST syntax for TIMESTAMP literals
14649            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14650            Some(DialectType::Snowflake) => {
14651                self.write("CAST('");
14652                self.write(ts);
14653                self.write("' AS TIMESTAMP)");
14654            }
14655            // Dremio uses CAST syntax for TIMESTAMP literals
14656            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14657            Some(DialectType::Dremio) => {
14658                self.write("CAST('");
14659                self.write(ts);
14660                self.write("' AS TIMESTAMP)");
14661            }
14662            // Exasol uses CAST syntax for TIMESTAMP literals
14663            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
14664            Some(DialectType::Exasol) => {
14665                self.write("CAST('");
14666                self.write(ts);
14667                self.write("' AS TIMESTAMP)");
14668            }
14669            // Oracle prefers TO_TIMESTAMP function call
14670            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
14671            Some(DialectType::Oracle) => {
14672                self.write("TO_TIMESTAMP('");
14673                self.write(ts);
14674                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
14675            }
14676            // Presto/Trino: always use CAST for TIMESTAMP literals
14677            Some(DialectType::Presto) | Some(DialectType::Trino) => {
14678                if Self::timestamp_has_timezone(ts) {
14679                    self.write("CAST('");
14680                    self.write(ts);
14681                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
14682                } else {
14683                    self.write("CAST('");
14684                    self.write(ts);
14685                    self.write("' AS TIMESTAMP)");
14686                }
14687            }
14688            // ClickHouse: CAST('...' AS Nullable(DateTime))
14689            Some(DialectType::ClickHouse) => {
14690                self.write("CAST('");
14691                self.write(ts);
14692                self.write("' AS Nullable(DateTime))");
14693            }
14694            // Spark: CAST('...' AS TIMESTAMP)
14695            Some(DialectType::Spark) => {
14696                self.write("CAST('");
14697                self.write(ts);
14698                self.write("' AS TIMESTAMP)");
14699            }
14700            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
14701            // but TIMESTAMP '...' for special values like 'epoch'
14702            Some(DialectType::Redshift) => {
14703                if ts == "epoch" {
14704                    self.write_keyword("TIMESTAMP");
14705                    self.write(" '");
14706                    self.write(ts);
14707                    self.write("'");
14708                } else {
14709                    self.write("CAST('");
14710                    self.write(ts);
14711                    self.write("' AS TIMESTAMP)");
14712                }
14713            }
14714            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
14715            Some(DialectType::PostgreSQL)
14716            | Some(DialectType::Hive)
14717            | Some(DialectType::SQLite)
14718            | Some(DialectType::DuckDB)
14719            | Some(DialectType::Athena)
14720            | Some(DialectType::Drill)
14721            | Some(DialectType::Teradata) => {
14722                self.write("CAST('");
14723                self.write(ts);
14724                self.write("' AS TIMESTAMP)");
14725            }
14726            // MySQL/StarRocks: CAST('...' AS DATETIME)
14727            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
14728                self.write("CAST('");
14729                self.write(ts);
14730                self.write("' AS DATETIME)");
14731            }
14732            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
14733            Some(DialectType::Databricks) => {
14734                self.write("CAST('");
14735                self.write(ts);
14736                self.write("' AS TIMESTAMP_NTZ)");
14737            }
14738            // Standard SQL: TIMESTAMP '...'
14739            _ => {
14740                self.write_keyword("TIMESTAMP");
14741                self.write(" '");
14742                self.write(ts);
14743                self.write("'");
14744            }
14745        }
14746        Ok(())
14747    }
14748
14749    /// Check if a timestamp string contains a timezone identifier
14750    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
14751    fn timestamp_has_timezone(ts: &str) -> bool {
14752        // Check for common IANA timezone patterns: Continent/City format
14753        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
14754        // Also handles: UTC, GMT, Etc/GMT+0, etc.
14755        let ts_lower = ts.to_ascii_lowercase();
14756
14757        // Check for Continent/City pattern (most common)
14758        let continent_prefixes = [
14759            "africa/",
14760            "america/",
14761            "antarctica/",
14762            "arctic/",
14763            "asia/",
14764            "atlantic/",
14765            "australia/",
14766            "europe/",
14767            "indian/",
14768            "pacific/",
14769            "etc/",
14770            "brazil/",
14771            "canada/",
14772            "chile/",
14773            "mexico/",
14774            "us/",
14775        ];
14776
14777        for prefix in &continent_prefixes {
14778            if ts_lower.contains(prefix) {
14779                return true;
14780            }
14781        }
14782
14783        // Check for standalone timezone abbreviations at the end
14784        // These typically appear after the time portion
14785        let tz_abbrevs = [
14786            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
14787            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
14788            " sgt", " aest", " aedt", " acst", " acdt", " awst",
14789        ];
14790
14791        for abbrev in &tz_abbrevs {
14792            if ts_lower.ends_with(abbrev) {
14793                return true;
14794            }
14795        }
14796
14797        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
14798        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
14799        // Look for pattern: space followed by + or - and digits (optionally with :)
14800        let trimmed = ts.trim();
14801        if let Some(last_space) = trimmed.rfind(' ') {
14802            let suffix = &trimmed[last_space + 1..];
14803            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
14804                // Check if rest is numeric (possibly with : for hh:mm format)
14805                let rest = &suffix[1..];
14806                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
14807                    return true;
14808                }
14809            }
14810        }
14811
14812        false
14813    }
14814
14815    /// Generate a DATETIME literal with dialect-specific formatting
14816    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
14817        use crate::dialects::DialectType;
14818
14819        match self.config.dialect {
14820            // BigQuery uses CAST syntax for type literals
14821            // DATETIME 'value' -> CAST('value' AS DATETIME)
14822            Some(DialectType::BigQuery) => {
14823                self.write("CAST('");
14824                self.write(dt);
14825                self.write("' AS DATETIME)");
14826            }
14827            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
14828            Some(DialectType::DuckDB) => {
14829                self.write("CAST('");
14830                self.write(dt);
14831                self.write("' AS TIMESTAMP)");
14832            }
14833            // DATETIME is primarily a BigQuery type
14834            // Output as DATETIME '...' for dialects that support it
14835            _ => {
14836                self.write_keyword("DATETIME");
14837                self.write(" '");
14838                self.write(dt);
14839                self.write("'");
14840            }
14841        }
14842        Ok(())
14843    }
14844
14845    /// Generate a string literal with dialect-specific escaping
14846    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
14847        use crate::dialects::DialectType;
14848
14849        match self.config.dialect {
14850            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
14851            // and backslash escaping for special characters like newlines
14852            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
14853            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
14854                // Hive/Spark use backslash escaping for quotes (\') and special chars
14855                self.write("'");
14856                for c in s.chars() {
14857                    match c {
14858                        '\'' => self.write("\\'"),
14859                        '\\' => self.write("\\\\"),
14860                        '\n' => self.write("\\n"),
14861                        '\r' => self.write("\\r"),
14862                        '\t' => self.write("\\t"),
14863                        '\0' => self.write("\\0"),
14864                        _ => self.output.push(c),
14865                    }
14866                }
14867                self.write("'");
14868            }
14869            Some(DialectType::Drill) => {
14870                // Drill uses SQL-standard quote doubling ('') for quotes,
14871                // but backslash escaping for special characters
14872                self.write("'");
14873                for c in s.chars() {
14874                    match c {
14875                        '\'' => self.write("''"),
14876                        '\\' => self.write("\\\\"),
14877                        '\n' => self.write("\\n"),
14878                        '\r' => self.write("\\r"),
14879                        '\t' => self.write("\\t"),
14880                        '\0' => self.write("\\0"),
14881                        _ => self.output.push(c),
14882                    }
14883                }
14884                self.write("'");
14885            }
14886            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
14887                self.write("'");
14888                for c in s.chars() {
14889                    match c {
14890                        // MySQL uses SQL standard quote doubling
14891                        '\'' => self.write("''"),
14892                        '\\' => self.write("\\\\"),
14893                        '\n' => self.write("\\n"),
14894                        '\r' => self.write("\\r"),
14895                        '\t' => self.write("\\t"),
14896                        // sqlglot writes a literal NUL for this case
14897                        '\0' => self.output.push('\0'),
14898                        _ => self.output.push(c),
14899                    }
14900                }
14901                self.write("'");
14902            }
14903            // BigQuery: Uses backslash escaping
14904            Some(DialectType::BigQuery) => {
14905                self.write("'");
14906                for c in s.chars() {
14907                    match c {
14908                        '\'' => self.write("\\'"),
14909                        '\\' => self.write("\\\\"),
14910                        '\n' => self.write("\\n"),
14911                        '\r' => self.write("\\r"),
14912                        '\t' => self.write("\\t"),
14913                        '\0' => self.write("\\0"),
14914                        '\x07' => self.write("\\a"),
14915                        '\x08' => self.write("\\b"),
14916                        '\x0C' => self.write("\\f"),
14917                        '\x0B' => self.write("\\v"),
14918                        _ => self.output.push(c),
14919                    }
14920                }
14921                self.write("'");
14922            }
14923            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
14924            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
14925            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
14926            Some(DialectType::Athena) => {
14927                if self.athena_hive_context {
14928                    // Hive-style: backslash escaping
14929                    self.write("'");
14930                    for c in s.chars() {
14931                        match c {
14932                            '\'' => self.write("\\'"),
14933                            '\\' => self.write("\\\\"),
14934                            '\n' => self.write("\\n"),
14935                            '\r' => self.write("\\r"),
14936                            '\t' => self.write("\\t"),
14937                            '\0' => self.write("\\0"),
14938                            _ => self.output.push(c),
14939                        }
14940                    }
14941                    self.write("'");
14942                } else {
14943                    // Trino-style: SQL-standard escaping, preserve backslashes
14944                    self.write("'");
14945                    for c in s.chars() {
14946                        match c {
14947                            '\'' => self.write("''"),
14948                            // Preserve backslashes literally (no re-escaping)
14949                            _ => self.output.push(c),
14950                        }
14951                    }
14952                    self.write("'");
14953                }
14954            }
14955            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
14956            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
14957            // becomes string value '\\'), so we should NOT re-escape backslashes.
14958            // We only need to escape single quotes.
14959            Some(DialectType::Snowflake) => {
14960                self.write("'");
14961                for c in s.chars() {
14962                    match c {
14963                        '\'' => self.write("\\'"),
14964                        // Backslashes are already escaped in the tokenized string, don't re-escape
14965                        // Only escape special characters that might not have been escaped
14966                        '\n' => self.write("\\n"),
14967                        '\r' => self.write("\\r"),
14968                        '\t' => self.write("\\t"),
14969                        _ => self.output.push(c),
14970                    }
14971                }
14972                self.write("'");
14973            }
14974            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
14975            Some(DialectType::PostgreSQL) => {
14976                self.write("'");
14977                for c in s.chars() {
14978                    match c {
14979                        '\'' => self.write("''"),
14980                        _ => self.output.push(c),
14981                    }
14982                }
14983                self.write("'");
14984            }
14985            // Redshift: Uses backslash escaping for single quotes
14986            Some(DialectType::Redshift) => {
14987                self.write("'");
14988                for c in s.chars() {
14989                    match c {
14990                        '\'' => self.write("\\'"),
14991                        _ => self.output.push(c),
14992                    }
14993                }
14994                self.write("'");
14995            }
14996            // Oracle: Uses standard double single-quote escaping
14997            Some(DialectType::Oracle) => {
14998                self.write("'");
14999                for ch in s.chars() {
15000                    if ch == '\'' {
15001                        self.output.push_str("''");
15002                    } else {
15003                        self.output.push(ch);
15004                    }
15005                }
15006                self.write("'");
15007            }
15008            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
15009            // backslash escaping for backslashes and special characters
15010            Some(DialectType::ClickHouse) => {
15011                self.write("'");
15012                for c in s.chars() {
15013                    match c {
15014                        '\'' => self.write("''"),
15015                        '\\' => self.write("\\\\"),
15016                        '\n' => self.write("\\n"),
15017                        '\r' => self.write("\\r"),
15018                        '\t' => self.write("\\t"),
15019                        '\0' => self.write("\\0"),
15020                        '\x07' => self.write("\\a"),
15021                        '\x08' => self.write("\\b"),
15022                        '\x0C' => self.write("\\f"),
15023                        '\x0B' => self.write("\\v"),
15024                        // Non-printable characters: emit as \xNN hex escapes
15025                        c if c.is_control() || (c as u32) < 0x20 => {
15026                            let byte = c as u32;
15027                            if byte < 256 {
15028                                self.write(&format!("\\x{:02X}", byte));
15029                            } else {
15030                                self.output.push(c);
15031                            }
15032                        }
15033                        _ => self.output.push(c),
15034                    }
15035                }
15036                self.write("'");
15037            }
15038            // Default: SQL standard double single quotes (works for most dialects)
15039            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
15040            _ => {
15041                self.write("'");
15042                for ch in s.chars() {
15043                    if ch == '\'' {
15044                        self.output.push_str("''");
15045                    } else {
15046                        self.output.push(ch);
15047                    }
15048                }
15049                self.write("'");
15050            }
15051        }
15052        Ok(())
15053    }
15054
15055    /// Write a byte string with proper escaping for BigQuery-style byte literals
15056    /// Escapes characters as \xNN hex escapes where needed
15057    fn write_escaped_byte_string(&mut self, s: &str) {
15058        for c in s.chars() {
15059            match c {
15060                // Escape single quotes
15061                '\'' => self.write("\\'"),
15062                // Escape backslashes
15063                '\\' => self.write("\\\\"),
15064                // Keep all printable characters (including non-ASCII) as-is
15065                _ if !c.is_control() => self.output.push(c),
15066                // Escape control characters as hex
15067                _ => {
15068                    let byte = c as u32;
15069                    if byte < 256 {
15070                        self.write(&format!("\\x{:02x}", byte));
15071                    } else {
15072                        // For unicode characters, write each UTF-8 byte
15073                        for b in c.to_string().as_bytes() {
15074                            self.write(&format!("\\x{:02x}", b));
15075                        }
15076                    }
15077                }
15078            }
15079        }
15080    }
15081
15082    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
15083        use crate::dialects::DialectType;
15084
15085        // Different dialects have different boolean literal formats
15086        match self.config.dialect {
15087            // SQL Server typically uses 1/0 for boolean literals in many contexts
15088            // However, TRUE/FALSE also works in modern versions
15089            Some(DialectType::TSQL) => {
15090                self.write(if b.value { "1" } else { "0" });
15091            }
15092            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
15093            Some(DialectType::Oracle) => {
15094                self.write(if b.value { "1" } else { "0" });
15095            }
15096            // MySQL accepts TRUE/FALSE as aliases for 1/0
15097            Some(DialectType::MySQL) => {
15098                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
15099            }
15100            // Most other dialects support TRUE/FALSE
15101            _ => {
15102                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
15103            }
15104        }
15105        Ok(())
15106    }
15107
15108    /// Generate an identifier that's used as an alias name
15109    /// This quotes reserved keywords in addition to already-quoted identifiers
15110    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
15111        let name = &id.name;
15112        let quote_style = &self.config.identifier_quote_style;
15113
15114        // For aliases, quote if:
15115        // 1. The identifier was explicitly quoted in the source
15116        // 2. The identifier is a reserved keyword for the current dialect
15117        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
15118
15119        // Normalize identifier if configured
15120        let output_name = if self.config.normalize_identifiers && !id.quoted {
15121            name.to_ascii_lowercase()
15122        } else {
15123            name.to_string()
15124        };
15125
15126        if needs_quoting {
15127            // Escape any quote characters within the identifier
15128            let escaped_name = if quote_style.start == quote_style.end {
15129                output_name.replace(
15130                    quote_style.end,
15131                    &format!("{}{}", quote_style.end, quote_style.end),
15132                )
15133            } else {
15134                output_name.replace(
15135                    quote_style.end,
15136                    &format!("{}{}", quote_style.end, quote_style.end),
15137                )
15138            };
15139            self.write(&format!(
15140                "{}{}{}",
15141                quote_style.start, escaped_name, quote_style.end
15142            ));
15143        } else {
15144            self.write(&output_name);
15145        }
15146
15147        // Output trailing comments
15148        for comment in &id.trailing_comments {
15149            self.write(" ");
15150            self.write_formatted_comment(comment);
15151        }
15152        Ok(())
15153    }
15154
15155    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
15156        use crate::dialects::DialectType;
15157
15158        let name = &id.name;
15159
15160        // For Athena, use backticks in Hive context, double quotes in Trino context
15161        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
15162            && self.athena_hive_context
15163        {
15164            &IdentifierQuoteStyle::BACKTICK
15165        } else {
15166            &self.config.identifier_quote_style
15167        };
15168
15169        // Quote if:
15170        // 1. The identifier was explicitly quoted in the source
15171        // 2. The identifier is a reserved keyword for the current dialect
15172        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
15173        // This matches Python sqlglot's identifier_sql behavior
15174        // Also quote identifiers starting with digits if the target dialect doesn't support them
15175        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
15176        let needs_digit_quoting = starts_with_digit
15177            && !self.config.identifiers_can_start_with_digit
15178            && self.config.dialect.is_some();
15179        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
15180            && name.len() > 2
15181            && (name.starts_with("0x") || name.starts_with("0X"))
15182            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
15183        let needs_quoting = id.quoted
15184            || self.is_reserved_keyword(name)
15185            || self.config.always_quote_identifiers
15186            || needs_digit_quoting
15187            || mysql_invalid_hex_identifier;
15188
15189        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
15190        // When quoted, we need to output `name`(16) not `name(16)`
15191        let (base_name, suffix) = if needs_quoting {
15192            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
15193            if let Some(paren_pos) = name.find('(') {
15194                let base = &name[..paren_pos];
15195                let rest = &name[paren_pos..];
15196                // Verify it looks like (digits) or (digits) ASC/DESC
15197                if rest.starts_with('(')
15198                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
15199                {
15200                    // Check if content between parens is all digits
15201                    let close_paren = rest.find(')').unwrap_or(rest.len());
15202                    let inside = &rest[1..close_paren];
15203                    if inside.chars().all(|c| c.is_ascii_digit()) {
15204                        (base.to_string(), rest.to_string())
15205                    } else {
15206                        (name.to_string(), String::new())
15207                    }
15208                } else {
15209                    (name.to_string(), String::new())
15210                }
15211            } else if name.ends_with(" ASC") {
15212                let base = &name[..name.len() - 4];
15213                (base.to_string(), " ASC".to_string())
15214            } else if name.ends_with(" DESC") {
15215                let base = &name[..name.len() - 5];
15216                (base.to_string(), " DESC".to_string())
15217            } else {
15218                (name.to_string(), String::new())
15219            }
15220        } else {
15221            (name.to_string(), String::new())
15222        };
15223
15224        // Normalize identifier if configured, with special handling for Exasol
15225        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
15226        // should be uppercased when not already quoted (to match Python sqlglot behavior)
15227        let output_name = if self.config.normalize_identifiers && !id.quoted {
15228            base_name.to_ascii_lowercase()
15229        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
15230            && !id.quoted
15231            && self.is_reserved_keyword(name)
15232        {
15233            // Exasol: uppercase reserved keywords when quoting them
15234            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
15235            base_name.to_ascii_uppercase()
15236        } else {
15237            base_name
15238        };
15239
15240        if needs_quoting {
15241            // Escape any quote characters within the identifier
15242            let escaped_name = if quote_style.start == quote_style.end {
15243                // Same start/end char (e.g., " or `) - double the quote char
15244                output_name.replace(
15245                    quote_style.end,
15246                    &format!("{}{}", quote_style.end, quote_style.end),
15247                )
15248            } else {
15249                // Different start/end (e.g., [ and ]) - escape only the end char
15250                output_name.replace(
15251                    quote_style.end,
15252                    &format!("{}{}", quote_style.end, quote_style.end),
15253                )
15254            };
15255            self.write(&format!(
15256                "{}{}{}{}",
15257                quote_style.start, escaped_name, quote_style.end, suffix
15258            ));
15259        } else {
15260            self.write(&output_name);
15261        }
15262
15263        // Output trailing comments
15264        for comment in &id.trailing_comments {
15265            self.write(" ");
15266            self.write_formatted_comment(comment);
15267        }
15268        Ok(())
15269    }
15270
15271    fn generate_column(&mut self, col: &Column) -> Result<()> {
15272        use crate::dialects::DialectType;
15273
15274        if let Some(table) = &col.table {
15275            // Exasol special case: LOCAL as column table prefix should NOT be quoted
15276            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
15277            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
15278            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
15279                && !table.quoted
15280                && table.name.eq_ignore_ascii_case("LOCAL");
15281
15282            if is_exasol_local_prefix {
15283                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
15284                self.write("LOCAL");
15285            } else {
15286                self.generate_identifier(table)?;
15287            }
15288            self.write(".");
15289        }
15290        self.generate_identifier(&col.name)?;
15291        // Oracle-style join marker (+)
15292        // Only output if dialect supports it (Oracle, Exasol)
15293        if col.join_mark && self.config.supports_column_join_marks {
15294            self.write(" (+)");
15295        }
15296        // Output trailing comments
15297        for comment in &col.trailing_comments {
15298            self.write_space();
15299            self.write_formatted_comment(comment);
15300        }
15301        Ok(())
15302    }
15303
15304    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
15305    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
15306    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
15307        use crate::dialects::DialectType;
15308        use crate::expressions::PseudocolumnType;
15309
15310        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
15311        if pc.kind == PseudocolumnType::Sysdate
15312            && !matches!(
15313                self.config.dialect,
15314                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
15315            )
15316        {
15317            self.write_keyword("CURRENT_TIMESTAMP");
15318            // Add () for dialects that expect it
15319            if matches!(
15320                self.config.dialect,
15321                Some(DialectType::MySQL)
15322                    | Some(DialectType::ClickHouse)
15323                    | Some(DialectType::Spark)
15324                    | Some(DialectType::Databricks)
15325                    | Some(DialectType::Hive)
15326            ) {
15327                self.write("()");
15328            }
15329        } else {
15330            self.write(pc.kind.as_str());
15331        }
15332        Ok(())
15333    }
15334
15335    /// Generate CONNECT BY clause (Oracle hierarchical queries)
15336    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
15337        use crate::dialects::DialectType;
15338
15339        // Generate native CONNECT BY for Oracle and Snowflake
15340        // For other dialects, add a comment noting manual conversion needed
15341        let supports_connect_by = matches!(
15342            self.config.dialect,
15343            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
15344        );
15345
15346        if !supports_connect_by && self.config.dialect.is_some() {
15347            // Add comment for unsupported dialects
15348            if self.config.pretty {
15349                self.write_newline();
15350            } else {
15351                self.write_space();
15352            }
15353            self.write_unsupported_comment(
15354                "CONNECT BY requires manual conversion to recursive CTE",
15355            )?;
15356        }
15357
15358        // Generate START WITH if present (before CONNECT BY)
15359        if let Some(start) = &connect.start {
15360            if self.config.pretty {
15361                self.write_newline();
15362            } else {
15363                self.write_space();
15364            }
15365            self.write_keyword("START WITH");
15366            self.write_space();
15367            self.generate_expression(start)?;
15368        }
15369
15370        // Generate CONNECT BY
15371        if self.config.pretty {
15372            self.write_newline();
15373        } else {
15374            self.write_space();
15375        }
15376        self.write_keyword("CONNECT BY");
15377        if connect.nocycle {
15378            self.write_space();
15379            self.write_keyword("NOCYCLE");
15380        }
15381        self.write_space();
15382        self.generate_expression(&connect.connect)?;
15383
15384        Ok(())
15385    }
15386
15387    /// Generate Connect expression (for Expression::Connect variant)
15388    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
15389        self.generate_connect(connect)
15390    }
15391
15392    /// Generate PRIOR expression
15393    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
15394        self.write_keyword("PRIOR");
15395        self.write_space();
15396        self.generate_expression(&prior.this)?;
15397        Ok(())
15398    }
15399
15400    /// Generate CONNECT_BY_ROOT function
15401    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
15402    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
15403        self.write_keyword("CONNECT_BY_ROOT");
15404        self.write_space();
15405        self.generate_expression(&cbr.this)?;
15406        Ok(())
15407    }
15408
15409    /// Generate MATCH_RECOGNIZE clause
15410    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
15411        use crate::dialects::DialectType;
15412
15413        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
15414        let supports_match_recognize = matches!(
15415            self.config.dialect,
15416            Some(DialectType::Oracle)
15417                | Some(DialectType::Snowflake)
15418                | Some(DialectType::Presto)
15419                | Some(DialectType::Trino)
15420        );
15421
15422        // Generate the source table first
15423        if let Some(source) = &mr.this {
15424            self.generate_expression(source)?;
15425        }
15426
15427        if !supports_match_recognize {
15428            self.write_unsupported_comment("MATCH_RECOGNIZE not supported in this dialect")?;
15429            return Ok(());
15430        }
15431
15432        // In pretty mode, MATCH_RECOGNIZE should be on a new line
15433        if self.config.pretty {
15434            self.write_newline();
15435        } else {
15436            self.write_space();
15437        }
15438
15439        self.write_keyword("MATCH_RECOGNIZE");
15440        self.write(" (");
15441
15442        if self.config.pretty {
15443            self.indent_level += 1;
15444        }
15445
15446        let mut needs_separator = false;
15447
15448        // PARTITION BY
15449        if let Some(partition_by) = &mr.partition_by {
15450            if !partition_by.is_empty() {
15451                if self.config.pretty {
15452                    self.write_newline();
15453                    self.write_indent();
15454                }
15455                self.write_keyword("PARTITION BY");
15456                self.write_space();
15457                for (i, expr) in partition_by.iter().enumerate() {
15458                    if i > 0 {
15459                        self.write(", ");
15460                    }
15461                    self.generate_expression(expr)?;
15462                }
15463                needs_separator = true;
15464            }
15465        }
15466
15467        // ORDER BY
15468        if let Some(order_by) = &mr.order_by {
15469            if !order_by.is_empty() {
15470                if needs_separator {
15471                    if self.config.pretty {
15472                        self.write_newline();
15473                        self.write_indent();
15474                    } else {
15475                        self.write_space();
15476                    }
15477                } else if self.config.pretty {
15478                    self.write_newline();
15479                    self.write_indent();
15480                }
15481                self.write_keyword("ORDER BY");
15482                // In pretty mode, put each ORDER BY column on a new indented line
15483                if self.config.pretty {
15484                    self.indent_level += 1;
15485                    for (i, ordered) in order_by.iter().enumerate() {
15486                        if i > 0 {
15487                            self.write(",");
15488                        }
15489                        self.write_newline();
15490                        self.write_indent();
15491                        self.generate_ordered(ordered)?;
15492                    }
15493                    self.indent_level -= 1;
15494                } else {
15495                    self.write_space();
15496                    for (i, ordered) in order_by.iter().enumerate() {
15497                        if i > 0 {
15498                            self.write(", ");
15499                        }
15500                        self.generate_ordered(ordered)?;
15501                    }
15502                }
15503                needs_separator = true;
15504            }
15505        }
15506
15507        // MEASURES
15508        if let Some(measures) = &mr.measures {
15509            if !measures.is_empty() {
15510                if needs_separator {
15511                    if self.config.pretty {
15512                        self.write_newline();
15513                        self.write_indent();
15514                    } else {
15515                        self.write_space();
15516                    }
15517                } else if self.config.pretty {
15518                    self.write_newline();
15519                    self.write_indent();
15520                }
15521                self.write_keyword("MEASURES");
15522                // In pretty mode, put each MEASURE on a new indented line
15523                if self.config.pretty {
15524                    self.indent_level += 1;
15525                    for (i, measure) in measures.iter().enumerate() {
15526                        if i > 0 {
15527                            self.write(",");
15528                        }
15529                        self.write_newline();
15530                        self.write_indent();
15531                        // Handle RUNNING/FINAL prefix
15532                        if let Some(semantics) = &measure.window_frame {
15533                            match semantics {
15534                                MatchRecognizeSemantics::Running => {
15535                                    self.write_keyword("RUNNING");
15536                                    self.write_space();
15537                                }
15538                                MatchRecognizeSemantics::Final => {
15539                                    self.write_keyword("FINAL");
15540                                    self.write_space();
15541                                }
15542                            }
15543                        }
15544                        self.generate_expression(&measure.this)?;
15545                    }
15546                    self.indent_level -= 1;
15547                } else {
15548                    self.write_space();
15549                    for (i, measure) in measures.iter().enumerate() {
15550                        if i > 0 {
15551                            self.write(", ");
15552                        }
15553                        // Handle RUNNING/FINAL prefix
15554                        if let Some(semantics) = &measure.window_frame {
15555                            match semantics {
15556                                MatchRecognizeSemantics::Running => {
15557                                    self.write_keyword("RUNNING");
15558                                    self.write_space();
15559                                }
15560                                MatchRecognizeSemantics::Final => {
15561                                    self.write_keyword("FINAL");
15562                                    self.write_space();
15563                                }
15564                            }
15565                        }
15566                        self.generate_expression(&measure.this)?;
15567                    }
15568                }
15569                needs_separator = true;
15570            }
15571        }
15572
15573        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
15574        if let Some(rows) = &mr.rows {
15575            if needs_separator {
15576                if self.config.pretty {
15577                    self.write_newline();
15578                    self.write_indent();
15579                } else {
15580                    self.write_space();
15581                }
15582            } else if self.config.pretty {
15583                self.write_newline();
15584                self.write_indent();
15585            }
15586            match rows {
15587                MatchRecognizeRows::OneRowPerMatch => {
15588                    self.write_keyword("ONE ROW PER MATCH");
15589                }
15590                MatchRecognizeRows::AllRowsPerMatch => {
15591                    self.write_keyword("ALL ROWS PER MATCH");
15592                }
15593                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
15594                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
15595                }
15596                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
15597                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
15598                }
15599                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
15600                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
15601                }
15602            }
15603            needs_separator = true;
15604        }
15605
15606        // AFTER MATCH SKIP
15607        if let Some(after) = &mr.after {
15608            if needs_separator {
15609                if self.config.pretty {
15610                    self.write_newline();
15611                    self.write_indent();
15612                } else {
15613                    self.write_space();
15614                }
15615            } else if self.config.pretty {
15616                self.write_newline();
15617                self.write_indent();
15618            }
15619            match after {
15620                MatchRecognizeAfter::PastLastRow => {
15621                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
15622                }
15623                MatchRecognizeAfter::ToNextRow => {
15624                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
15625                }
15626                MatchRecognizeAfter::ToFirst(ident) => {
15627                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
15628                    self.write_space();
15629                    self.generate_identifier(ident)?;
15630                }
15631                MatchRecognizeAfter::ToLast(ident) => {
15632                    self.write_keyword("AFTER MATCH SKIP TO LAST");
15633                    self.write_space();
15634                    self.generate_identifier(ident)?;
15635                }
15636            }
15637            needs_separator = true;
15638        }
15639
15640        // PATTERN
15641        if let Some(pattern) = &mr.pattern {
15642            if needs_separator {
15643                if self.config.pretty {
15644                    self.write_newline();
15645                    self.write_indent();
15646                } else {
15647                    self.write_space();
15648                }
15649            } else if self.config.pretty {
15650                self.write_newline();
15651                self.write_indent();
15652            }
15653            self.write_keyword("PATTERN");
15654            self.write_space();
15655            self.write("(");
15656            self.write(pattern);
15657            self.write(")");
15658            needs_separator = true;
15659        }
15660
15661        // DEFINE
15662        if let Some(define) = &mr.define {
15663            if !define.is_empty() {
15664                if needs_separator {
15665                    if self.config.pretty {
15666                        self.write_newline();
15667                        self.write_indent();
15668                    } else {
15669                        self.write_space();
15670                    }
15671                } else if self.config.pretty {
15672                    self.write_newline();
15673                    self.write_indent();
15674                }
15675                self.write_keyword("DEFINE");
15676                // In pretty mode, put each DEFINE on a new indented line
15677                if self.config.pretty {
15678                    self.indent_level += 1;
15679                    for (i, (name, expr)) in define.iter().enumerate() {
15680                        if i > 0 {
15681                            self.write(",");
15682                        }
15683                        self.write_newline();
15684                        self.write_indent();
15685                        self.generate_identifier(name)?;
15686                        self.write(" AS ");
15687                        self.generate_expression(expr)?;
15688                    }
15689                    self.indent_level -= 1;
15690                } else {
15691                    self.write_space();
15692                    for (i, (name, expr)) in define.iter().enumerate() {
15693                        if i > 0 {
15694                            self.write(", ");
15695                        }
15696                        self.generate_identifier(name)?;
15697                        self.write(" AS ");
15698                        self.generate_expression(expr)?;
15699                    }
15700                }
15701            }
15702        }
15703
15704        if self.config.pretty {
15705            self.indent_level -= 1;
15706            self.write_newline();
15707        }
15708        self.write(")");
15709
15710        // Alias - only include AS if it was explicitly present in the input
15711        if let Some(alias) = &mr.alias {
15712            self.write(" ");
15713            if mr.alias_explicit_as {
15714                self.write_keyword("AS");
15715                self.write(" ");
15716            }
15717            self.generate_identifier(alias)?;
15718        }
15719
15720        Ok(())
15721    }
15722
15723    /// Generate a query hint /*+ ... */
15724    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
15725        use crate::dialects::DialectType;
15726
15727        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
15728        let supports_hints = matches!(
15729            self.config.dialect,
15730            None |  // No dialect = preserve everything
15731            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
15732            Some(DialectType::Spark) | Some(DialectType::Hive) |
15733            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
15734        );
15735
15736        if !supports_hints || hint.expressions.is_empty() {
15737            return Ok(());
15738        }
15739
15740        // First, expand raw hint text into individual hint strings
15741        // This handles the case where the parser stored multiple hints as a single raw string
15742        let mut hint_strings: Vec<String> = Vec::new();
15743        for expr in &hint.expressions {
15744            match expr {
15745                HintExpression::Raw(text) => {
15746                    // Parse raw hint text into individual hint function calls
15747                    let parsed = self.parse_raw_hint_text(text);
15748                    hint_strings.extend(parsed);
15749                }
15750                _ => {
15751                    hint_strings.push(self.hint_expression_to_string(expr)?);
15752                }
15753            }
15754        }
15755
15756        // In pretty mode with multiple hints, always use multiline format
15757        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
15758        // always joins with newlines in pretty mode
15759        let use_multiline = self.config.pretty && hint_strings.len() > 1;
15760
15761        if use_multiline {
15762            // Pretty print with each hint on its own line
15763            self.write(" /*+ ");
15764            for (i, hint_str) in hint_strings.iter().enumerate() {
15765                if i > 0 {
15766                    self.write_newline();
15767                    self.write("  "); // 2-space indent within hint block
15768                }
15769                self.write(hint_str);
15770            }
15771            self.write(" */");
15772        } else {
15773            // Single line format
15774            self.write(" /*+ ");
15775            let sep = match self.config.dialect {
15776                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
15777                _ => " ",
15778            };
15779            for (i, hint_str) in hint_strings.iter().enumerate() {
15780                if i > 0 {
15781                    self.write(sep);
15782                }
15783                self.write(hint_str);
15784            }
15785            self.write(" */");
15786        }
15787
15788        Ok(())
15789    }
15790
15791    /// Parse raw hint text into individual hint function calls
15792    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
15793    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
15794    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
15795        let mut results = Vec::new();
15796        let mut chars = text.chars().peekable();
15797        let mut current = String::new();
15798        let mut paren_depth = 0;
15799        let mut has_unparseable_content = false;
15800        let mut position_after_last_function = 0;
15801        let mut char_position = 0;
15802
15803        while let Some(c) = chars.next() {
15804            char_position += c.len_utf8();
15805            match c {
15806                '(' => {
15807                    paren_depth += 1;
15808                    current.push(c);
15809                }
15810                ')' => {
15811                    paren_depth -= 1;
15812                    current.push(c);
15813                    // When we close the outer parenthesis, we've completed a hint function
15814                    if paren_depth == 0 {
15815                        let trimmed = current.trim().to_string();
15816                        if !trimmed.is_empty() {
15817                            // Format this hint for pretty printing if needed
15818                            let formatted = self.format_hint_function(&trimmed);
15819                            results.push(formatted);
15820                        }
15821                        current.clear();
15822                        position_after_last_function = char_position;
15823                    }
15824                }
15825                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
15826                    // Space/comma/whitespace outside parentheses - skip
15827                }
15828                _ if paren_depth == 0 => {
15829                    // Character outside parentheses - accumulate for potential hint name
15830                    current.push(c);
15831                }
15832                _ => {
15833                    current.push(c);
15834                }
15835            }
15836        }
15837
15838        // Check if there's remaining text after the last function call
15839        let remaining_text = text[position_after_last_function..].trim();
15840        if !remaining_text.is_empty() {
15841            // Check if it looks like valid hint function names
15842            // Valid hint identifiers typically are uppercase alphanumeric with underscores
15843            // If we see multiple words without parens, it's likely unparseable
15844            let words: Vec<&str> = remaining_text.split_whitespace().collect();
15845            let looks_like_hint_functions = words.iter().all(|word| {
15846                // A valid hint name followed by opening paren, or a standalone uppercase identifier
15847                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
15848            });
15849
15850            if !looks_like_hint_functions && words.len() > 1 {
15851                has_unparseable_content = true;
15852            }
15853        }
15854
15855        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
15856        if has_unparseable_content {
15857            return vec![text.trim().to_string()];
15858        }
15859
15860        // If we couldn't parse anything, return the original text as a single hint
15861        if results.is_empty() {
15862            results.push(text.trim().to_string());
15863        }
15864
15865        results
15866    }
15867
15868    /// Format a hint function for pretty printing
15869    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
15870    fn format_hint_function(&self, hint: &str) -> String {
15871        if !self.config.pretty {
15872            return hint.to_string();
15873        }
15874
15875        // Try to parse NAME(args) pattern
15876        if let Some(paren_pos) = hint.find('(') {
15877            if hint.ends_with(')') {
15878                let name = &hint[..paren_pos];
15879                let args_str = &hint[paren_pos + 1..hint.len() - 1];
15880
15881                // Parse arguments (space-separated for Oracle hints)
15882                let args: Vec<&str> = args_str.split_whitespace().collect();
15883
15884                // Calculate total width of arguments
15885                let total_args_width: usize =
15886                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
15887
15888                // If too wide, format on multiple lines
15889                if total_args_width > self.config.max_text_width && !args.is_empty() {
15890                    let mut result = format!("{}(\n", name);
15891                    for arg in &args {
15892                        result.push_str("    "); // 4-space indent for args
15893                        result.push_str(arg);
15894                        result.push('\n');
15895                    }
15896                    result.push_str("  )"); // 2-space indent for closing paren
15897                    return result;
15898                }
15899            }
15900        }
15901
15902        hint.to_string()
15903    }
15904
15905    /// Convert a hint expression to a string, handling multiline formatting for long arguments
15906    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
15907        match expr {
15908            HintExpression::Function { name, args } => {
15909                // Generate each argument to a string
15910                let arg_strings: Vec<String> = args
15911                    .iter()
15912                    .map(|arg| {
15913                        let mut gen = Generator::with_arc_config(self.config.clone());
15914                        gen.generate_expression(arg)?;
15915                        Ok(gen.output)
15916                    })
15917                    .collect::<Result<Vec<_>>>()?;
15918
15919                // Oracle hints use space-separated arguments, not comma-separated
15920                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
15921                    + arg_strings.len().saturating_sub(1); // spaces between args
15922
15923                // Check if function args need multiline formatting
15924                // Use too_wide check for argument formatting
15925                let args_multiline =
15926                    self.config.pretty && total_args_width > self.config.max_text_width;
15927
15928                if args_multiline && !arg_strings.is_empty() {
15929                    // Multiline format for long argument lists
15930                    let mut result = format!("{}(\n", name);
15931                    for arg_str in &arg_strings {
15932                        result.push_str("    "); // 4-space indent for args
15933                        result.push_str(arg_str);
15934                        result.push('\n');
15935                    }
15936                    result.push_str("  )"); // 2-space indent for closing paren
15937                    Ok(result)
15938                } else {
15939                    // Single line format with space-separated args (Oracle style)
15940                    let args_str = arg_strings.join(" ");
15941                    Ok(format!("{}({})", name, args_str))
15942                }
15943            }
15944            HintExpression::Identifier(name) => Ok(name.clone()),
15945            HintExpression::Raw(text) => {
15946                // For pretty printing, try to format the raw text
15947                if self.config.pretty {
15948                    Ok(self.format_hint_function(text))
15949                } else {
15950                    Ok(text.clone())
15951                }
15952            }
15953        }
15954    }
15955
15956    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
15957        // PostgreSQL ONLY modifier: prevents scanning child tables
15958        if table.only {
15959            self.write_keyword("ONLY");
15960            self.write_space();
15961        }
15962
15963        // Check for IDENTIFIER() (Snowflake) or OPENDATASOURCE(...).db.schema.table (TSQL)
15964        if let Some(ref identifier_func) = table.identifier_func {
15965            self.generate_expression(identifier_func)?;
15966            // If table name parts are present, emit .catalog.schema.name after the function
15967            if !table.name.name.is_empty() {
15968                if let Some(catalog) = &table.catalog {
15969                    self.write(".");
15970                    self.generate_identifier(catalog)?;
15971                }
15972                if let Some(schema) = &table.schema {
15973                    self.write(".");
15974                    self.generate_identifier(schema)?;
15975                }
15976                self.write(".");
15977                self.generate_identifier(&table.name)?;
15978            }
15979        } else {
15980            if let Some(catalog) = &table.catalog {
15981                self.generate_identifier(catalog)?;
15982                self.write(".");
15983            }
15984            if let Some(schema) = &table.schema {
15985                self.generate_identifier(schema)?;
15986                self.write(".");
15987            }
15988            self.generate_identifier(&table.name)?;
15989        }
15990
15991        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
15992        if let Some(changes) = &table.changes {
15993            self.write(" ");
15994            self.generate_changes(changes)?;
15995        }
15996
15997        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
15998        if !table.partitions.is_empty() {
15999            self.write_space();
16000            self.write_keyword("PARTITION");
16001            self.write("(");
16002            for (i, partition) in table.partitions.iter().enumerate() {
16003                if i > 0 {
16004                    self.write(", ");
16005                }
16006                self.generate_identifier(partition)?;
16007            }
16008            self.write(")");
16009        }
16010
16011        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
16012        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
16013        if table.changes.is_none() {
16014            if let Some(when) = &table.when {
16015                self.write_space();
16016                self.generate_historical_data(when)?;
16017            }
16018        }
16019
16020        // Output TSQL FOR SYSTEM_TIME temporal clause (before alias, except BigQuery)
16021        let system_time_post_alias = matches!(self.config.dialect, Some(DialectType::BigQuery));
16022        if !system_time_post_alias {
16023            if let Some(ref system_time) = table.system_time {
16024                self.write_space();
16025                self.write(system_time);
16026            }
16027        }
16028
16029        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
16030        if let Some(ref version) = table.version {
16031            self.write_space();
16032            self.generate_version(version)?;
16033        }
16034
16035        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
16036        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
16037        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
16038        let alias_post_tablesample = self.config.alias_post_tablesample;
16039
16040        if alias_post_tablesample {
16041            // TABLESAMPLE before alias (Oracle, Hive, Spark)
16042            self.generate_table_sample_clause(table)?;
16043        }
16044
16045        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
16046        // For SQLite, INDEXED BY hints come after the alias, so skip here
16047        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
16048            && table.hints.iter().any(|h| {
16049                if let Expression::Identifier(id) = h {
16050                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
16051                } else {
16052                    false
16053                }
16054            });
16055        if !table.hints.is_empty() && !is_sqlite_hint {
16056            for hint in &table.hints {
16057                self.write_space();
16058                self.generate_expression(hint)?;
16059            }
16060        }
16061
16062        if let Some(alias) = &table.alias {
16063            self.write_space();
16064            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
16065            // Generic mode and most dialects always use AS for table aliases
16066            let always_use_as = self.config.dialect.is_none()
16067                || matches!(
16068                    self.config.dialect,
16069                    Some(DialectType::Generic)
16070                        | Some(DialectType::PostgreSQL)
16071                        | Some(DialectType::Redshift)
16072                        | Some(DialectType::Snowflake)
16073                        | Some(DialectType::BigQuery)
16074                        | Some(DialectType::DuckDB)
16075                        | Some(DialectType::Presto)
16076                        | Some(DialectType::Trino)
16077                        | Some(DialectType::TSQL)
16078                        | Some(DialectType::Fabric)
16079                        | Some(DialectType::MySQL)
16080                        | Some(DialectType::Spark)
16081                        | Some(DialectType::Hive)
16082                        | Some(DialectType::SQLite)
16083                        | Some(DialectType::Drill)
16084                );
16085            let is_stage_ref = table.name.name.starts_with('@');
16086            // Oracle never uses AS for table aliases
16087            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
16088            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
16089                self.write_keyword("AS");
16090                self.write_space();
16091            }
16092            self.generate_identifier(alias)?;
16093
16094            // Output column aliases if present: AS t(c1, c2)
16095            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
16096            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
16097                self.write("(");
16098                for (i, col_alias) in table.column_aliases.iter().enumerate() {
16099                    if i > 0 {
16100                        self.write(", ");
16101                    }
16102                    self.generate_identifier(col_alias)?;
16103                }
16104                self.write(")");
16105            }
16106        }
16107
16108        // BigQuery: FOR SYSTEM_TIME AS OF after alias
16109        if system_time_post_alias {
16110            if let Some(ref system_time) = table.system_time {
16111                self.write_space();
16112                self.write(system_time);
16113            }
16114        }
16115
16116        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
16117        if !alias_post_tablesample {
16118            self.generate_table_sample_clause(table)?;
16119        }
16120
16121        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
16122        if is_sqlite_hint {
16123            for hint in &table.hints {
16124                self.write_space();
16125                self.generate_expression(hint)?;
16126            }
16127        }
16128
16129        // ClickHouse FINAL modifier
16130        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
16131            self.write_space();
16132            self.write_keyword("FINAL");
16133        }
16134
16135        // Output trailing comments
16136        for comment in &table.trailing_comments {
16137            self.write_space();
16138            self.write_formatted_comment(comment);
16139        }
16140        // Note: leading_comments (from before table in FROM clause) are intentionally NOT
16141        // output here - they are output by the FROM/PIVOT generator after the full expression
16142
16143        Ok(())
16144    }
16145
16146    /// Helper to output TABLESAMPLE clause for a table reference
16147    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
16148        if let Some(ref ts) = table.table_sample {
16149            self.write_space();
16150            if ts.is_using_sample {
16151                self.write_keyword("USING SAMPLE");
16152            } else {
16153                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
16154                self.write_keyword(self.config.tablesample_keywords);
16155            }
16156            self.generate_sample_body(ts)?;
16157            // Seed for table-level sample - use dialect's configured keyword
16158            if let Some(ref seed) = ts.seed {
16159                self.write_space();
16160                self.write_keyword(self.config.tablesample_seed_keyword);
16161                self.write(" (");
16162                self.generate_expression(seed)?;
16163                self.write(")");
16164            }
16165        }
16166        Ok(())
16167    }
16168
16169    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
16170        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
16171        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
16172
16173        if sr.quoted {
16174            self.write("'");
16175        }
16176
16177        self.write(&sr.name);
16178        if let Some(path) = &sr.path {
16179            self.write(path);
16180        }
16181
16182        if sr.quoted {
16183            self.write("'");
16184        }
16185
16186        // Output FILE_FORMAT and PATTERN if present
16187        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
16188        if has_options {
16189            self.write(" (");
16190            let mut first = true;
16191
16192            if let Some(file_format) = &sr.file_format {
16193                if !first {
16194                    self.write(", ");
16195                }
16196                self.write_keyword("FILE_FORMAT");
16197                self.write(" => ");
16198                self.generate_expression(file_format)?;
16199                first = false;
16200            }
16201
16202            if let Some(pattern) = &sr.pattern {
16203                if !first {
16204                    self.write(", ");
16205                }
16206                self.write_keyword("PATTERN");
16207                self.write(" => '");
16208                self.write(pattern);
16209                self.write("'");
16210            }
16211
16212            self.write(")");
16213        }
16214        Ok(())
16215    }
16216
16217    fn generate_star(&mut self, star: &Star) -> Result<()> {
16218        use crate::dialects::DialectType;
16219
16220        if let Some(table) = &star.table {
16221            self.generate_identifier(table)?;
16222            self.write(".");
16223        }
16224        self.write("*");
16225
16226        // Generate EXCLUDE/EXCEPT clause based on dialect
16227        if let Some(except) = &star.except {
16228            if !except.is_empty() {
16229                self.write_space();
16230                // Use dialect-appropriate keyword
16231                match self.config.dialect {
16232                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
16233                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
16234                        self.write_keyword("EXCLUDE")
16235                    }
16236                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
16237                }
16238                self.write(" (");
16239                for (i, col) in except.iter().enumerate() {
16240                    if i > 0 {
16241                        self.write(", ");
16242                    }
16243                    self.generate_identifier(col)?;
16244                }
16245                self.write(")");
16246            }
16247        }
16248
16249        // Generate REPLACE clause
16250        if let Some(replace) = &star.replace {
16251            if !replace.is_empty() {
16252                self.write_space();
16253                self.write_keyword("REPLACE");
16254                self.write(" (");
16255                for (i, alias) in replace.iter().enumerate() {
16256                    if i > 0 {
16257                        self.write(", ");
16258                    }
16259                    self.generate_expression(&alias.this)?;
16260                    self.write_space();
16261                    self.write_keyword("AS");
16262                    self.write_space();
16263                    self.generate_identifier(&alias.alias)?;
16264                }
16265                self.write(")");
16266            }
16267        }
16268
16269        // Generate RENAME clause (Snowflake specific)
16270        if let Some(rename) = &star.rename {
16271            if !rename.is_empty() {
16272                self.write_space();
16273                self.write_keyword("RENAME");
16274                self.write(" (");
16275                for (i, (old_name, new_name)) in rename.iter().enumerate() {
16276                    if i > 0 {
16277                        self.write(", ");
16278                    }
16279                    self.generate_identifier(old_name)?;
16280                    self.write_space();
16281                    self.write_keyword("AS");
16282                    self.write_space();
16283                    self.generate_identifier(new_name)?;
16284                }
16285                self.write(")");
16286            }
16287        }
16288
16289        // Output trailing comments
16290        for comment in &star.trailing_comments {
16291            self.write_space();
16292            self.write_formatted_comment(comment);
16293        }
16294
16295        Ok(())
16296    }
16297
16298    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
16299    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
16300        self.write("{");
16301        match expr {
16302            Expression::Star(star) => {
16303                // Generate the star (table.* or just * with optional EXCLUDE)
16304                self.generate_star(star)?;
16305            }
16306            Expression::ILike(ilike) => {
16307                // {* ILIKE 'pattern'} syntax
16308                self.generate_expression(&ilike.left)?;
16309                self.write_space();
16310                self.write_keyword("ILIKE");
16311                self.write_space();
16312                self.generate_expression(&ilike.right)?;
16313            }
16314            _ => {
16315                self.generate_expression(expr)?;
16316            }
16317        }
16318        self.write("}");
16319        Ok(())
16320    }
16321
16322    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
16323        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
16324        // to avoid duplication (comments are captured as both Column.trailing_comments
16325        // and Alias.pre_alias_comments during parsing)
16326        match &alias.this {
16327            Expression::Column(col) => {
16328                // Generate column without trailing comments - they're in pre_alias_comments
16329                if let Some(table) = &col.table {
16330                    self.generate_identifier(table)?;
16331                    self.write(".");
16332                }
16333                self.generate_identifier(&col.name)?;
16334            }
16335            _ => {
16336                self.generate_expression(&alias.this)?;
16337            }
16338        }
16339
16340        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
16341        // moves pre-alias comments to after the alias. When there are also trailing_comments,
16342        // keep pre-alias comments in their original position (between expression and AS).
16343        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
16344            for comment in &alias.pre_alias_comments {
16345                self.write_space();
16346                self.write_formatted_comment(comment);
16347            }
16348        }
16349
16350        use crate::dialects::DialectType;
16351
16352        // Determine if we should skip AS keyword for table-valued function aliases
16353        // Oracle and some other dialects don't use AS for table aliases
16354        // Note: We specifically use TableFromRows here, NOT Function, because Function
16355        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
16356        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
16357        let is_table_source = matches!(
16358            &alias.this,
16359            Expression::JSONTable(_)
16360                | Expression::XMLTable(_)
16361                | Expression::TableFromRows(_)
16362                | Expression::Unnest(_)
16363                | Expression::MatchRecognize(_)
16364                | Expression::Select(_)
16365                | Expression::Subquery(_)
16366                | Expression::Paren(_)
16367        );
16368        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
16369        let skip_as = is_table_source && dialect_skips_table_alias_as;
16370
16371        self.write_space();
16372        if !skip_as {
16373            self.write_keyword("AS");
16374            self.write_space();
16375        }
16376
16377        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
16378        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
16379
16380        // Check if we have column aliases only (no table alias name)
16381        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
16382            // Generate AS (col1, col2, ...)
16383            self.write("(");
16384            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
16385                if i > 0 {
16386                    self.write(", ");
16387                }
16388                self.generate_alias_identifier(col_alias)?;
16389            }
16390            self.write(")");
16391        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
16392            // Generate AS alias(col1, col2, ...)
16393            self.generate_alias_identifier(&alias.alias)?;
16394            self.write("(");
16395            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
16396                if i > 0 {
16397                    self.write(", ");
16398                }
16399                self.generate_alias_identifier(col_alias)?;
16400            }
16401            self.write(")");
16402        } else {
16403            // Simple alias (or BigQuery without column aliases)
16404            self.generate_alias_identifier(&alias.alias)?;
16405        }
16406
16407        // Output trailing comments (comments after the alias)
16408        for comment in &alias.trailing_comments {
16409            self.write_space();
16410            self.write_formatted_comment(comment);
16411        }
16412
16413        // Output pre-alias comments: when there are no trailing_comments, sqlglot
16414        // moves pre-alias comments to after the alias. When there are trailing_comments,
16415        // the pre-alias comments were already lost (consumed as column trailing comments
16416        // that were then used as pre_alias_comments). We always emit them after alias.
16417        if alias.trailing_comments.is_empty() {
16418            for comment in &alias.pre_alias_comments {
16419                self.write_space();
16420                self.write_formatted_comment(comment);
16421            }
16422        }
16423
16424        Ok(())
16425    }
16426
16427    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
16428        use crate::dialects::DialectType;
16429
16430        // SingleStore uses :> syntax
16431        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
16432            self.generate_expression(&cast.this)?;
16433            self.write(" :> ");
16434            self.generate_data_type(&cast.to)?;
16435            return Ok(());
16436        }
16437
16438        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
16439        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
16440            let is_unknown_type = matches!(cast.to, DataType::Unknown)
16441                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
16442            if is_unknown_type {
16443                if let Some(format) = &cast.format {
16444                    self.write_keyword("CAST");
16445                    self.write("(");
16446                    self.generate_expression(&cast.this)?;
16447                    self.write_space();
16448                    self.write_keyword("AS");
16449                    self.write_space();
16450                    self.write_keyword("FORMAT");
16451                    self.write_space();
16452                    self.generate_expression(format)?;
16453                    self.write(")");
16454                    return Ok(());
16455                }
16456            }
16457        }
16458
16459        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
16460        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
16461        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
16462            if let Some(format) = &cast.format {
16463                // Check if target type is DATE or TIMESTAMP
16464                let is_date = matches!(cast.to, DataType::Date);
16465                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
16466
16467                if is_date || is_timestamp {
16468                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
16469                    self.write_keyword(func_name);
16470                    self.write("(");
16471                    self.generate_expression(&cast.this)?;
16472                    self.write(", ");
16473
16474                    // Normalize format string for Oracle (HH -> HH12)
16475                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
16476                    if let Expression::Literal(lit) = format.as_ref() {
16477                        if let Literal::String(fmt_str) = lit.as_ref() {
16478                            let normalized = self.normalize_oracle_format(fmt_str);
16479                            self.write("'");
16480                            self.write(&normalized);
16481                            self.write("'");
16482                        }
16483                    } else {
16484                        self.generate_expression(format)?;
16485                    }
16486
16487                    self.write(")");
16488                    return Ok(());
16489                }
16490            }
16491        }
16492
16493        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
16494        // This preserves sqlglot's typed inline array literal output.
16495        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
16496            if let Expression::Array(arr) = &cast.this {
16497                self.generate_data_type(&cast.to)?;
16498                // Output just the bracket content [values] without the ARRAY prefix
16499                self.write("[");
16500                for (i, expr) in arr.expressions.iter().enumerate() {
16501                    if i > 0 {
16502                        self.write(", ");
16503                    }
16504                    self.generate_expression(expr)?;
16505                }
16506                self.write("]");
16507                return Ok(());
16508            }
16509            if matches!(&cast.this, Expression::ArrayFunc(_)) {
16510                self.generate_data_type(&cast.to)?;
16511                self.generate_expression(&cast.this)?;
16512                return Ok(());
16513            }
16514        }
16515
16516        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
16517        // convert the inner Struct to ROW(values...) format
16518        if matches!(
16519            self.config.dialect,
16520            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
16521        ) {
16522            if let Expression::Struct(ref s) = cast.this {
16523                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
16524                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
16525                    self.write_keyword("CAST");
16526                    self.write("(");
16527                    self.generate_struct_as_row(s)?;
16528                    self.write_space();
16529                    self.write_keyword("AS");
16530                    self.write_space();
16531                    self.generate_data_type(&cast.to)?;
16532                    self.write(")");
16533                    return Ok(());
16534                }
16535            }
16536        }
16537
16538        // Determine if we should use :: syntax based on dialect
16539        // PostgreSQL prefers :: for identity, most others prefer CAST()
16540        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
16541
16542        if use_double_colon {
16543            // PostgreSQL :: syntax: expr::type
16544            self.generate_expression(&cast.this)?;
16545            self.write("::");
16546            self.generate_data_type(&cast.to)?;
16547        } else {
16548            // Standard CAST() syntax
16549            self.write_keyword("CAST");
16550            self.write("(");
16551            self.generate_expression(&cast.this)?;
16552            self.write_space();
16553            self.write_keyword("AS");
16554            self.write_space();
16555            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
16556            // This matches Python sqlglot's CAST_MAPPING behavior
16557            if matches!(
16558                self.config.dialect,
16559                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
16560            ) {
16561                match &cast.to {
16562                    DataType::Custom { ref name } => {
16563                        if name.eq_ignore_ascii_case("LONGTEXT")
16564                            || name.eq_ignore_ascii_case("MEDIUMTEXT")
16565                            || name.eq_ignore_ascii_case("TINYTEXT")
16566                            || name.eq_ignore_ascii_case("LONGBLOB")
16567                            || name.eq_ignore_ascii_case("MEDIUMBLOB")
16568                            || name.eq_ignore_ascii_case("TINYBLOB")
16569                        {
16570                            self.write_keyword("CHAR");
16571                        } else {
16572                            self.generate_data_type(&cast.to)?;
16573                        }
16574                    }
16575                    DataType::VarChar { length, .. } => {
16576                        // MySQL CAST: VARCHAR -> CHAR
16577                        self.write_keyword("CHAR");
16578                        if let Some(n) = length {
16579                            self.write(&format!("({})", n));
16580                        }
16581                    }
16582                    DataType::Text => {
16583                        // MySQL CAST: TEXT -> CHAR
16584                        self.write_keyword("CHAR");
16585                    }
16586                    DataType::Timestamp {
16587                        precision,
16588                        timezone: false,
16589                    } => {
16590                        // MySQL CAST: TIMESTAMP -> DATETIME
16591                        self.write_keyword("DATETIME");
16592                        if let Some(p) = precision {
16593                            self.write(&format!("({})", p));
16594                        }
16595                    }
16596                    _ => {
16597                        self.generate_data_type(&cast.to)?;
16598                    }
16599                }
16600            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
16601                // Snowflake CAST: STRING -> VARCHAR
16602                match &cast.to {
16603                    DataType::String { length } => {
16604                        self.write_keyword("VARCHAR");
16605                        if let Some(n) = length {
16606                            self.write(&format!("({})", n));
16607                        }
16608                    }
16609                    _ => {
16610                        self.generate_data_type(&cast.to)?;
16611                    }
16612                }
16613            } else {
16614                self.generate_data_type(&cast.to)?;
16615            }
16616
16617            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
16618            if let Some(default) = &cast.default {
16619                self.write_space();
16620                self.write_keyword("DEFAULT");
16621                self.write_space();
16622                self.generate_expression(default)?;
16623                self.write_space();
16624                self.write_keyword("ON");
16625                self.write_space();
16626                self.write_keyword("CONVERSION");
16627                self.write_space();
16628                self.write_keyword("ERROR");
16629            }
16630
16631            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
16632            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
16633            if let Some(format) = &cast.format {
16634                // Check if Oracle dialect - use comma syntax
16635                if matches!(
16636                    self.config.dialect,
16637                    Some(crate::dialects::DialectType::Oracle)
16638                ) {
16639                    self.write(", ");
16640                } else {
16641                    self.write_space();
16642                    self.write_keyword("FORMAT");
16643                    self.write_space();
16644                }
16645                self.generate_expression(format)?;
16646            }
16647
16648            self.write(")");
16649            // Output trailing comments
16650            for comment in &cast.trailing_comments {
16651                self.write_space();
16652                self.write_formatted_comment(comment);
16653            }
16654        }
16655        Ok(())
16656    }
16657
16658    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
16659    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
16660    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
16661        self.write_keyword("ROW");
16662        self.write("(");
16663        for (i, (_, expr)) in s.fields.iter().enumerate() {
16664            if i > 0 {
16665                self.write(", ");
16666            }
16667            // Recursively convert inner Struct to ROW format
16668            if let Expression::Struct(ref inner_s) = expr {
16669                self.generate_struct_as_row(inner_s)?;
16670            } else {
16671                self.generate_expression(expr)?;
16672            }
16673        }
16674        self.write(")");
16675        Ok(())
16676    }
16677
16678    /// Normalize Oracle date/time format strings
16679    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
16680    fn normalize_oracle_format(&self, format: &str) -> String {
16681        // Replace standalone HH with HH12 (but not HH12 or HH24)
16682        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
16683        let mut result = String::new();
16684        let chars: Vec<char> = format.chars().collect();
16685        let mut i = 0;
16686
16687        while i < chars.len() {
16688            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
16689                // Check what follows HH
16690                if i + 2 < chars.len() {
16691                    let next = chars[i + 2];
16692                    if next == '1' || next == '2' {
16693                        // This is HH12 or HH24, keep as is
16694                        result.push('H');
16695                        result.push('H');
16696                        i += 2;
16697                        continue;
16698                    }
16699                }
16700                // Standalone HH -> HH12
16701                result.push_str("HH12");
16702                i += 2;
16703            } else {
16704                result.push(chars[i]);
16705                i += 1;
16706            }
16707        }
16708
16709        result
16710    }
16711
16712    /// Check if the current dialect prefers :: cast syntax
16713    /// Note: Python sqlglot normalizes all :: to CAST() for output, even for PostgreSQL
16714    /// So we return false for all dialects to match Python sqlglot's behavior
16715    fn dialect_prefers_double_colon(&self) -> bool {
16716        // Python sqlglot normalizes :: syntax to CAST() for all dialects
16717        // Even PostgreSQL outputs CAST() not ::
16718        false
16719    }
16720
16721    /// Generate MOD function - uses % operator for Snowflake/MySQL/Presto/Trino, MOD() for others
16722    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
16723        use crate::dialects::DialectType;
16724
16725        // Snowflake, MySQL, Presto, Trino, PostgreSQL, and DuckDB prefer x % y instead of MOD(x, y)
16726        let use_percent_operator = matches!(
16727            self.config.dialect,
16728            Some(DialectType::Snowflake)
16729                | Some(DialectType::MySQL)
16730                | Some(DialectType::Presto)
16731                | Some(DialectType::Trino)
16732                | Some(DialectType::PostgreSQL)
16733                | Some(DialectType::DuckDB)
16734                | Some(DialectType::Hive)
16735                | Some(DialectType::Spark)
16736                | Some(DialectType::Databricks)
16737                | Some(DialectType::Athena)
16738        );
16739
16740        if use_percent_operator {
16741            // Wrap complex expressions in parens to preserve precedence
16742            // Since % has higher precedence than +/-, we need parens for Add/Sub on either side
16743            let needs_paren = |e: &Expression| matches!(e, Expression::Add(_) | Expression::Sub(_));
16744            if needs_paren(&f.this) {
16745                self.write("(");
16746                self.generate_expression(&f.this)?;
16747                self.write(")");
16748            } else {
16749                self.generate_expression(&f.this)?;
16750            }
16751            self.write(" % ");
16752            if needs_paren(&f.expression) {
16753                self.write("(");
16754                self.generate_expression(&f.expression)?;
16755                self.write(")");
16756            } else {
16757                self.generate_expression(&f.expression)?;
16758            }
16759            Ok(())
16760        } else {
16761            self.generate_binary_func("MOD", &f.this, &f.expression)
16762        }
16763    }
16764
16765    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
16766    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
16767        use crate::dialects::DialectType;
16768
16769        // Snowflake normalizes IFNULL to COALESCE
16770        let func_name = match self.config.dialect {
16771            Some(DialectType::Snowflake) => "COALESCE",
16772            _ => "IFNULL",
16773        };
16774
16775        self.generate_binary_func(func_name, &f.this, &f.expression)
16776    }
16777
16778    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
16779    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
16780        // Use original function name if preserved (for identity tests)
16781        if let Some(ref original_name) = f.original_name {
16782            return self.generate_binary_func(original_name, &f.this, &f.expression);
16783        }
16784
16785        // Otherwise, use dialect-specific function names
16786        use crate::dialects::DialectType;
16787        let func_name = match self.config.dialect {
16788            Some(DialectType::Snowflake)
16789            | Some(DialectType::ClickHouse)
16790            | Some(DialectType::PostgreSQL)
16791            | Some(DialectType::Presto)
16792            | Some(DialectType::Trino)
16793            | Some(DialectType::Athena)
16794            | Some(DialectType::DuckDB)
16795            | Some(DialectType::BigQuery)
16796            | Some(DialectType::Spark)
16797            | Some(DialectType::Databricks)
16798            | Some(DialectType::Hive) => "COALESCE",
16799            Some(DialectType::MySQL)
16800            | Some(DialectType::Doris)
16801            | Some(DialectType::StarRocks)
16802            | Some(DialectType::SingleStore)
16803            | Some(DialectType::TiDB) => "IFNULL",
16804            _ => "NVL",
16805        };
16806
16807        self.generate_binary_func(func_name, &f.this, &f.expression)
16808    }
16809
16810    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
16811    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
16812        use crate::dialects::DialectType;
16813
16814        // Snowflake normalizes STDDEV_SAMP to STDDEV
16815        let func_name = match self.config.dialect {
16816            Some(DialectType::Snowflake) => "STDDEV",
16817            _ => "STDDEV_SAMP",
16818        };
16819
16820        self.generate_agg_func(func_name, f)
16821    }
16822
16823    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
16824        self.generate_expression(&coll.this)?;
16825        self.write_space();
16826        self.write_keyword("COLLATE");
16827        self.write_space();
16828        if coll.quoted {
16829            // Single-quoted string: COLLATE 'de_DE'
16830            self.write("'");
16831            self.write(&coll.collation);
16832            self.write("'");
16833        } else if coll.double_quoted {
16834            // Double-quoted identifier: COLLATE "de_DE"
16835            self.write("\"");
16836            self.write(&coll.collation);
16837            self.write("\"");
16838        } else {
16839            // Unquoted identifier: COLLATE de_DE
16840            self.write(&coll.collation);
16841        }
16842        Ok(())
16843    }
16844
16845    fn generate_case(&mut self, case: &Case) -> Result<()> {
16846        // In pretty mode, decide whether to expand based on total text width
16847        let multiline_case = if self.config.pretty {
16848            // Build the flat representation to check width
16849            let mut statements: Vec<String> = Vec::new();
16850            let operand_str = if let Some(operand) = &case.operand {
16851                let s = self.generate_to_string(operand)?;
16852                statements.push(format!("CASE {}", s));
16853                s
16854            } else {
16855                statements.push("CASE".to_string());
16856                String::new()
16857            };
16858            let _ = operand_str;
16859            for (condition, result) in &case.whens {
16860                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
16861                statements.push(format!("THEN {}", self.generate_to_string(result)?));
16862            }
16863            if let Some(else_) = &case.else_ {
16864                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
16865            }
16866            statements.push("END".to_string());
16867            self.too_wide(&statements)
16868        } else {
16869            false
16870        };
16871
16872        self.write_keyword("CASE");
16873        if let Some(operand) = &case.operand {
16874            self.write_space();
16875            self.generate_expression(operand)?;
16876        }
16877        if multiline_case {
16878            self.indent_level += 1;
16879        }
16880        for (condition, result) in &case.whens {
16881            if multiline_case {
16882                self.write_newline();
16883                self.write_indent();
16884            } else {
16885                self.write_space();
16886            }
16887            self.write_keyword("WHEN");
16888            self.write_space();
16889            self.generate_expression(condition)?;
16890            if multiline_case {
16891                self.write_newline();
16892                self.write_indent();
16893            } else {
16894                self.write_space();
16895            }
16896            self.write_keyword("THEN");
16897            self.write_space();
16898            self.generate_expression(result)?;
16899        }
16900        if let Some(else_) = &case.else_ {
16901            if multiline_case {
16902                self.write_newline();
16903                self.write_indent();
16904            } else {
16905                self.write_space();
16906            }
16907            self.write_keyword("ELSE");
16908            self.write_space();
16909            self.generate_expression(else_)?;
16910        }
16911        if multiline_case {
16912            self.indent_level -= 1;
16913            self.write_newline();
16914            self.write_indent();
16915        } else {
16916            self.write_space();
16917        }
16918        self.write_keyword("END");
16919        // Emit any comments that were attached to the CASE keyword
16920        for comment in &case.comments {
16921            self.write(" ");
16922            self.write_formatted_comment(comment);
16923        }
16924        Ok(())
16925    }
16926
16927    fn generate_function(&mut self, func: &Function) -> Result<()> {
16928        // Normalize function name based on dialect settings
16929        let normalized_name = self.normalize_func_name(&func.name);
16930
16931        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
16932        if matches!(self.config.dialect, Some(DialectType::DuckDB))
16933            && func.name.eq_ignore_ascii_case("ARRAY_CONSTRUCT_COMPACT")
16934        {
16935            self.write("LIST_FILTER(");
16936            self.write("[");
16937            for (i, arg) in func.args.iter().enumerate() {
16938                if i > 0 {
16939                    self.write(", ");
16940                }
16941                self.generate_expression(arg)?;
16942            }
16943            self.write("], _u -> NOT _u IS NULL)");
16944            return Ok(());
16945        }
16946
16947        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
16948        if func.name.eq_ignore_ascii_case("STRUCT")
16949            && !matches!(
16950                self.config.dialect,
16951                Some(DialectType::BigQuery)
16952                    | Some(DialectType::Spark)
16953                    | Some(DialectType::Databricks)
16954                    | Some(DialectType::Hive)
16955                    | None
16956            )
16957        {
16958            return self.generate_struct_function_cross_dialect(func);
16959        }
16960
16961        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
16962        // This is an internal marker function for ::? JSON path syntax
16963        if func.name.eq_ignore_ascii_case("__SS_JSON_PATH_QMARK__") && func.args.len() == 2 {
16964            self.generate_expression(&func.args[0])?;
16965            self.write("::?");
16966            // Extract the key from the string literal
16967            if let Expression::Literal(lit) = &func.args[1] {
16968                if let crate::expressions::Literal::String(key) = lit.as_ref() {
16969                    self.write(key);
16970                }
16971            } else {
16972                self.generate_expression(&func.args[1])?;
16973            }
16974            return Ok(());
16975        }
16976
16977        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
16978        if func.name.eq_ignore_ascii_case("__PG_BITWISE_XOR__") && func.args.len() == 2 {
16979            self.generate_expression(&func.args[0])?;
16980            self.write(" # ");
16981            self.generate_expression(&func.args[1])?;
16982            return Ok(());
16983        }
16984
16985        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
16986        if matches!(
16987            self.config.dialect,
16988            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
16989        ) && func.name.eq_ignore_ascii_case("TRY")
16990            && func.args.len() == 1
16991        {
16992            self.generate_expression(&func.args[0])?;
16993            return Ok(());
16994        }
16995
16996        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
16997        if self.config.dialect == Some(DialectType::ClickHouse)
16998            && func.name.eq_ignore_ascii_case("TOSTARTOFDAY")
16999            && func.args.len() == 1
17000        {
17001            self.write("dateTrunc('DAY', ");
17002            self.generate_expression(&func.args[0])?;
17003            self.write(")");
17004            return Ok(());
17005        }
17006
17007        // Redshift: CONCAT(a, b, ...) -> a || b || ...
17008        if self.config.dialect == Some(DialectType::Redshift)
17009            && func.name.eq_ignore_ascii_case("CONCAT")
17010            && func.args.len() >= 2
17011        {
17012            for (i, arg) in func.args.iter().enumerate() {
17013                if i > 0 {
17014                    self.write(" || ");
17015                }
17016                self.generate_expression(arg)?;
17017            }
17018            return Ok(());
17019        }
17020
17021        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
17022        if self.config.dialect == Some(DialectType::Redshift)
17023            && func.name.eq_ignore_ascii_case("CONCAT_WS")
17024            && func.args.len() >= 2
17025        {
17026            let sep = &func.args[0];
17027            for (i, arg) in func.args.iter().skip(1).enumerate() {
17028                if i > 0 {
17029                    self.write(" || ");
17030                    self.generate_expression(sep)?;
17031                    self.write(" || ");
17032                }
17033                self.generate_expression(arg)?;
17034            }
17035            return Ok(());
17036        }
17037
17038        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
17039        // Unit should be unquoted uppercase identifier
17040        if self.config.dialect == Some(DialectType::Redshift)
17041            && (func.name.eq_ignore_ascii_case("DATEDIFF")
17042                || func.name.eq_ignore_ascii_case("DATE_DIFF"))
17043            && func.args.len() == 3
17044        {
17045            self.write_keyword("DATEDIFF");
17046            self.write("(");
17047            // First arg is unit - normalize to unquoted uppercase
17048            self.write_redshift_date_part(&func.args[0]);
17049            self.write(", ");
17050            self.generate_expression(&func.args[1])?;
17051            self.write(", ");
17052            self.generate_expression(&func.args[2])?;
17053            self.write(")");
17054            return Ok(());
17055        }
17056
17057        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
17058        // Unit should be unquoted uppercase identifier
17059        if self.config.dialect == Some(DialectType::Redshift)
17060            && (func.name.eq_ignore_ascii_case("DATEADD")
17061                || func.name.eq_ignore_ascii_case("DATE_ADD"))
17062            && func.args.len() == 3
17063        {
17064            self.write_keyword("DATEADD");
17065            self.write("(");
17066            // First arg is unit - normalize to unquoted uppercase
17067            self.write_redshift_date_part(&func.args[0]);
17068            self.write(", ");
17069            self.generate_expression(&func.args[1])?;
17070            self.write(", ");
17071            self.generate_expression(&func.args[2])?;
17072            self.write(")");
17073            return Ok(());
17074        }
17075
17076        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function (dropping args)
17077        if func.name.eq_ignore_ascii_case("UUID_STRING")
17078            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
17079        {
17080            let func_name = match self.config.dialect {
17081                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
17082                Some(DialectType::BigQuery) => "GENERATE_UUID",
17083                _ => "UUID",
17084            };
17085            self.write_keyword(func_name);
17086            self.write("()");
17087            return Ok(());
17088        }
17089
17090        // Snowflake: GENERATOR(val) -> GENERATOR(ROWCOUNT => val)
17091        // GENERATOR(val1, val2) -> GENERATOR(ROWCOUNT => val1, TIMELIMIT => val2)
17092        // Positional args are mapped to named parameters.
17093        if matches!(self.config.dialect, Some(DialectType::Snowflake))
17094            && func.name.eq_ignore_ascii_case("GENERATOR")
17095        {
17096            let has_positional_args =
17097                !func.args.is_empty() && !matches!(&func.args[0], Expression::NamedArgument(_));
17098            if has_positional_args {
17099                let param_names = ["ROWCOUNT", "TIMELIMIT"];
17100                self.write_keyword("GENERATOR");
17101                self.write("(");
17102                for (i, arg) in func.args.iter().enumerate() {
17103                    if i > 0 {
17104                        self.write(", ");
17105                    }
17106                    if i < param_names.len() {
17107                        self.write_keyword(param_names[i]);
17108                        self.write(" => ");
17109                        self.generate_expression(arg)?;
17110                    } else {
17111                        self.generate_expression(arg)?;
17112                    }
17113                }
17114                self.write(")");
17115                return Ok(());
17116            }
17117        }
17118
17119        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
17120        // Unit should be quoted uppercase string
17121        if self.config.dialect == Some(DialectType::Redshift)
17122            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
17123            && func.args.len() == 2
17124        {
17125            self.write_keyword("DATE_TRUNC");
17126            self.write("(");
17127            // First arg is unit - normalize to quoted uppercase
17128            self.write_redshift_date_part_quoted(&func.args[0]);
17129            self.write(", ");
17130            self.generate_expression(&func.args[1])?;
17131            self.write(")");
17132            return Ok(());
17133        }
17134
17135        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
17136        if matches!(
17137            self.config.dialect,
17138            Some(DialectType::TSQL) | Some(DialectType::Fabric)
17139        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
17140            || func.name.eq_ignore_ascii_case("DATEPART"))
17141            && func.args.len() == 2
17142        {
17143            self.write_keyword("DATEPART");
17144            self.write("(");
17145            self.generate_expression(&func.args[0])?;
17146            self.write(", ");
17147            self.generate_expression(&func.args[1])?;
17148            self.write(")");
17149            return Ok(());
17150        }
17151
17152        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
17153        if matches!(
17154            self.config.dialect,
17155            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
17156        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
17157            || func.name.eq_ignore_ascii_case("DATEPART"))
17158            && func.args.len() == 2
17159        {
17160            self.write_keyword("EXTRACT");
17161            self.write("(");
17162            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
17163            match &func.args[0] {
17164                Expression::Literal(lit)
17165                    if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
17166                {
17167                    let crate::expressions::Literal::String(s) = lit.as_ref() else {
17168                        unreachable!()
17169                    };
17170                    self.write(&s.to_ascii_lowercase());
17171                }
17172                _ => self.generate_expression(&func.args[0])?,
17173            }
17174            self.write_space();
17175            self.write_keyword("FROM");
17176            self.write_space();
17177            self.generate_expression(&func.args[1])?;
17178            self.write(")");
17179            return Ok(());
17180        }
17181
17182        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
17183        // Also DATE literals in Dremio should be CAST(...AS DATE)
17184        if self.config.dialect == Some(DialectType::Dremio)
17185            && (func.name.eq_ignore_ascii_case("DATE_PART")
17186                || func.name.eq_ignore_ascii_case("DATEPART"))
17187            && func.args.len() == 2
17188        {
17189            self.write_keyword("EXTRACT");
17190            self.write("(");
17191            self.generate_expression(&func.args[0])?;
17192            self.write_space();
17193            self.write_keyword("FROM");
17194            self.write_space();
17195            // For Dremio, DATE literals should become CAST('value' AS DATE)
17196            self.generate_dremio_date_expression(&func.args[1])?;
17197            self.write(")");
17198            return Ok(());
17199        }
17200
17201        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
17202        if self.config.dialect == Some(DialectType::Dremio)
17203            && func.name.eq_ignore_ascii_case("CURRENT_DATE_UTC")
17204            && func.args.is_empty()
17205        {
17206            self.write_keyword("CURRENT_DATE_UTC");
17207            return Ok(());
17208        }
17209
17210        // Dremio: DATETYPE(year, month, day) transformation
17211        // - If all args are integer literals: DATE('YYYY-MM-DD')
17212        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
17213        if self.config.dialect == Some(DialectType::Dremio)
17214            && func.name.eq_ignore_ascii_case("DATETYPE")
17215            && func.args.len() == 3
17216        {
17217            // Helper function to extract integer from number literal
17218            fn get_int_literal(expr: &Expression) -> Option<i64> {
17219                if let Expression::Literal(lit) = expr {
17220                    if let crate::expressions::Literal::Number(s) = lit.as_ref() {
17221                        s.parse::<i64>().ok()
17222                    } else {
17223                        None
17224                    }
17225                } else {
17226                    None
17227                }
17228            }
17229
17230            // Check if all arguments are integer literals
17231            if let (Some(year), Some(month), Some(day)) = (
17232                get_int_literal(&func.args[0]),
17233                get_int_literal(&func.args[1]),
17234                get_int_literal(&func.args[2]),
17235            ) {
17236                // All are integer literals: DATE('YYYY-MM-DD')
17237                self.write_keyword("DATE");
17238                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
17239                return Ok(());
17240            }
17241
17242            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
17243            self.write_keyword("CAST");
17244            self.write("(");
17245            self.write_keyword("CONCAT");
17246            self.write("(");
17247            self.generate_expression(&func.args[0])?;
17248            self.write(", '-', ");
17249            self.generate_expression(&func.args[1])?;
17250            self.write(", '-', ");
17251            self.generate_expression(&func.args[2])?;
17252            self.write(")");
17253            self.write_space();
17254            self.write_keyword("AS");
17255            self.write_space();
17256            self.write_keyword("DATE");
17257            self.write(")");
17258            return Ok(());
17259        }
17260
17261        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
17262        // when it's not an integer literal
17263        let is_presto_like = matches!(
17264            self.config.dialect,
17265            Some(DialectType::Presto) | Some(DialectType::Trino)
17266        );
17267        if is_presto_like && func.name.eq_ignore_ascii_case("DATE_ADD") && func.args.len() == 3 {
17268            self.write_keyword("DATE_ADD");
17269            self.write("(");
17270            // First arg: unit (pass through as-is, e.g., 'DAY')
17271            self.generate_expression(&func.args[0])?;
17272            self.write(", ");
17273            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
17274            let interval = &func.args[1];
17275            let needs_cast = !self.returns_integer_type(interval);
17276            if needs_cast {
17277                self.write_keyword("CAST");
17278                self.write("(");
17279            }
17280            self.generate_expression(interval)?;
17281            if needs_cast {
17282                self.write_space();
17283                self.write_keyword("AS");
17284                self.write_space();
17285                self.write_keyword("BIGINT");
17286                self.write(")");
17287            }
17288            self.write(", ");
17289            // Third arg: date
17290            self.generate_expression(&func.args[2])?;
17291            self.write(")");
17292            return Ok(());
17293        }
17294
17295        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
17296        let use_brackets = func.use_bracket_syntax;
17297
17298        // Special case: functions WITH ORDINALITY need special output order
17299        // Input: FUNC(args) WITH ORDINALITY
17300        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
17301        // Output must be: FUNC(args) WITH ORDINALITY
17302        let has_ordinality = func.name.len() >= 16
17303            && func.name[func.name.len() - 16..].eq_ignore_ascii_case(" WITH ORDINALITY");
17304        let output_name = if has_ordinality {
17305            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
17306            self.normalize_func_name(base_name)
17307        } else {
17308            normalized_name.clone()
17309        };
17310
17311        // For qualified names (schema.function or object.method), preserve original case
17312        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
17313        if func.name.contains('.') && !has_ordinality {
17314            // Don't normalize qualified functions - preserve original case
17315            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
17316            if func.quoted {
17317                self.write("`");
17318                self.write(&func.name);
17319                self.write("`");
17320            } else {
17321                self.write(&func.name);
17322            }
17323        } else {
17324            self.write(&output_name);
17325        }
17326
17327        // If no_parens is true and there are no args, output just the function name
17328        // Unless the target dialect requires parens for this function
17329        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
17330            let needs_parens = if func.name.eq_ignore_ascii_case("CURRENT_USER")
17331                || func.name.eq_ignore_ascii_case("SESSION_USER")
17332                || func.name.eq_ignore_ascii_case("SYSTEM_USER")
17333            {
17334                matches!(
17335                    self.config.dialect,
17336                    Some(DialectType::Snowflake)
17337                        | Some(DialectType::Spark)
17338                        | Some(DialectType::Databricks)
17339                        | Some(DialectType::Hive)
17340                )
17341            } else {
17342                false
17343            };
17344            !needs_parens
17345        };
17346        if force_parens {
17347            // Output trailing comments
17348            for comment in &func.trailing_comments {
17349                self.write_space();
17350                self.write_formatted_comment(comment);
17351            }
17352            return Ok(());
17353        }
17354
17355        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
17356        if func.name.eq_ignore_ascii_case("CUBE")
17357            || func.name.eq_ignore_ascii_case("ROLLUP")
17358            || func.name.eq_ignore_ascii_case("GROUPING SETS")
17359        {
17360            self.write(" (");
17361        } else if use_brackets {
17362            self.write("[");
17363        } else {
17364            self.write("(");
17365        }
17366        if func.distinct {
17367            self.write_keyword("DISTINCT");
17368            self.write_space();
17369        }
17370
17371        // Check if arguments should be split onto multiple lines (pretty + too wide)
17372        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
17373            && (func.name.eq_ignore_ascii_case("TABLE")
17374                || func.name.eq_ignore_ascii_case("FLATTEN"));
17375        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
17376        let is_grouping_func = func.name.eq_ignore_ascii_case("GROUPING SETS")
17377            || func.name.eq_ignore_ascii_case("CUBE")
17378            || func.name.eq_ignore_ascii_case("ROLLUP");
17379        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
17380            if is_grouping_func {
17381                true
17382            } else {
17383                // Pre-render arguments to check total width
17384                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
17385                for arg in &func.args {
17386                    let mut temp_gen = Generator::with_arc_config(self.config.clone());
17387                    Arc::make_mut(&mut temp_gen.config).pretty = false; // Don't recurse into pretty
17388                    temp_gen.generate_expression(arg)?;
17389                    expr_strings.push(temp_gen.output);
17390                }
17391                self.too_wide(&expr_strings)
17392            }
17393        } else {
17394            false
17395        };
17396
17397        if should_split {
17398            // Split onto multiple lines
17399            self.write_newline();
17400            self.indent_level += 1;
17401            for (i, arg) in func.args.iter().enumerate() {
17402                self.write_indent();
17403                self.generate_expression(arg)?;
17404                if i + 1 < func.args.len() {
17405                    self.write(",");
17406                }
17407                self.write_newline();
17408            }
17409            self.indent_level -= 1;
17410            self.write_indent();
17411        } else {
17412            // All on one line
17413            for (i, arg) in func.args.iter().enumerate() {
17414                if i > 0 {
17415                    self.write(", ");
17416                }
17417                self.generate_expression(arg)?;
17418            }
17419        }
17420
17421        if use_brackets {
17422            self.write("]");
17423        } else {
17424            self.write(")");
17425        }
17426        // Append WITH ORDINALITY after closing paren for table-valued functions
17427        if has_ordinality {
17428            self.write_space();
17429            self.write_keyword("WITH ORDINALITY");
17430        }
17431        // Output trailing comments
17432        for comment in &func.trailing_comments {
17433            self.write_space();
17434            self.write_formatted_comment(comment);
17435        }
17436        Ok(())
17437    }
17438
17439    fn generate_function_emits(&mut self, fe: &FunctionEmits) -> Result<()> {
17440        self.generate_expression(&fe.this)?;
17441        self.write_keyword(" EMITS ");
17442        self.generate_expression(&fe.emits)?;
17443        Ok(())
17444    }
17445
17446    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
17447        // Normalize function name based on dialect settings
17448        let mut normalized_name = self.normalize_func_name(&func.name);
17449
17450        // Dialect-specific name mappings for aggregate functions
17451        if func.name.eq_ignore_ascii_case("MAX_BY") || func.name.eq_ignore_ascii_case("MIN_BY") {
17452            let is_max = func.name.eq_ignore_ascii_case("MAX_BY");
17453            match self.config.dialect {
17454                Some(DialectType::ClickHouse) => {
17455                    normalized_name = if is_max {
17456                        Cow::Borrowed("argMax")
17457                    } else {
17458                        Cow::Borrowed("argMin")
17459                    };
17460                }
17461                Some(DialectType::DuckDB) => {
17462                    normalized_name = if is_max {
17463                        Cow::Borrowed("ARG_MAX")
17464                    } else {
17465                        Cow::Borrowed("ARG_MIN")
17466                    };
17467                }
17468                _ => {}
17469            }
17470        }
17471        self.write(normalized_name.as_ref());
17472        self.write("(");
17473        if func.distinct {
17474            self.write_keyword("DISTINCT");
17475            self.write_space();
17476        }
17477
17478        // Check if we need to transform multi-arg COUNT DISTINCT
17479        // When dialect doesn't support multi_arg_distinct, transform:
17480        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
17481        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
17482        let needs_multi_arg_transform =
17483            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
17484
17485        if needs_multi_arg_transform {
17486            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
17487            self.write_keyword("CASE");
17488            for arg in &func.args {
17489                self.write_space();
17490                self.write_keyword("WHEN");
17491                self.write_space();
17492                self.generate_expression(arg)?;
17493                self.write_space();
17494                self.write_keyword("IS NULL THEN NULL");
17495            }
17496            self.write_space();
17497            self.write_keyword("ELSE");
17498            self.write(" (");
17499            for (i, arg) in func.args.iter().enumerate() {
17500                if i > 0 {
17501                    self.write(", ");
17502                }
17503                self.generate_expression(arg)?;
17504            }
17505            self.write(")");
17506            self.write_space();
17507            self.write_keyword("END");
17508        } else {
17509            for (i, arg) in func.args.iter().enumerate() {
17510                if i > 0 {
17511                    self.write(", ");
17512                }
17513                self.generate_expression(arg)?;
17514            }
17515        }
17516
17517        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
17518        if self.config.ignore_nulls_in_func
17519            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
17520        {
17521            if let Some(ignore) = func.ignore_nulls {
17522                self.write_space();
17523                if ignore {
17524                    self.write_keyword("IGNORE NULLS");
17525                } else {
17526                    self.write_keyword("RESPECT NULLS");
17527                }
17528            }
17529        }
17530
17531        // ORDER BY inside aggregate
17532        if !func.order_by.is_empty() {
17533            self.write_space();
17534            self.write_keyword("ORDER BY");
17535            self.write_space();
17536            for (i, ord) in func.order_by.iter().enumerate() {
17537                if i > 0 {
17538                    self.write(", ");
17539                }
17540                self.generate_ordered(ord)?;
17541            }
17542        }
17543
17544        // LIMIT inside aggregate
17545        if let Some(limit) = &func.limit {
17546            self.write_space();
17547            self.write_keyword("LIMIT");
17548            self.write_space();
17549            // Check if this is a Tuple representing LIMIT offset, count
17550            if let Expression::Tuple(t) = limit.as_ref() {
17551                if t.expressions.len() == 2 {
17552                    self.generate_expression(&t.expressions[0])?;
17553                    self.write(", ");
17554                    self.generate_expression(&t.expressions[1])?;
17555                } else {
17556                    self.generate_expression(limit)?;
17557                }
17558            } else {
17559                self.generate_expression(limit)?;
17560            }
17561        }
17562
17563        self.write(")");
17564
17565        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
17566        if !self.config.ignore_nulls_in_func
17567            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
17568        {
17569            if let Some(ignore) = func.ignore_nulls {
17570                self.write_space();
17571                if ignore {
17572                    self.write_keyword("IGNORE NULLS");
17573                } else {
17574                    self.write_keyword("RESPECT NULLS");
17575                }
17576            }
17577        }
17578
17579        if let Some(filter) = &func.filter {
17580            self.write_space();
17581            self.write_keyword("FILTER");
17582            self.write("(");
17583            self.write_keyword("WHERE");
17584            self.write_space();
17585            self.generate_expression(filter)?;
17586            self.write(")");
17587        }
17588
17589        Ok(())
17590    }
17591
17592    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
17593        self.generate_expression(&wf.this)?;
17594
17595        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
17596        if let Some(keep) = &wf.keep {
17597            self.write_space();
17598            self.write_keyword("KEEP");
17599            self.write(" (");
17600            self.write_keyword("DENSE_RANK");
17601            self.write_space();
17602            if keep.first {
17603                self.write_keyword("FIRST");
17604            } else {
17605                self.write_keyword("LAST");
17606            }
17607            self.write_space();
17608            self.write_keyword("ORDER BY");
17609            self.write_space();
17610            for (i, ord) in keep.order_by.iter().enumerate() {
17611                if i > 0 {
17612                    self.write(", ");
17613                }
17614                self.generate_ordered(ord)?;
17615            }
17616            self.write(")");
17617        }
17618
17619        // Check if there's any OVER clause content
17620        let has_over = !wf.over.partition_by.is_empty()
17621            || !wf.over.order_by.is_empty()
17622            || wf.over.frame.is_some()
17623            || wf.over.window_name.is_some();
17624
17625        // Only output OVER if there's actual window specification (not just KEEP alone)
17626        if has_over {
17627            self.write_space();
17628            self.write_keyword("OVER");
17629
17630            // Check if this is just a bare named window reference (no parens needed)
17631            let has_specs = !wf.over.partition_by.is_empty()
17632                || !wf.over.order_by.is_empty()
17633                || wf.over.frame.is_some();
17634
17635            if wf.over.window_name.is_some() && !has_specs {
17636                // OVER window_name (without parentheses)
17637                self.write_space();
17638                self.write(&wf.over.window_name.as_ref().unwrap().name);
17639            } else {
17640                // OVER (...) or OVER (window_name ...)
17641                self.write(" (");
17642                self.generate_over(&wf.over)?;
17643                self.write(")");
17644            }
17645        } else if wf.keep.is_none() {
17646            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
17647            self.write_space();
17648            self.write_keyword("OVER");
17649            self.write(" ()");
17650        }
17651
17652        Ok(())
17653    }
17654
17655    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
17656    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
17657        self.generate_expression(&wg.this)?;
17658        self.write_space();
17659        self.write_keyword("WITHIN GROUP");
17660        self.write(" (");
17661        self.write_keyword("ORDER BY");
17662        self.write_space();
17663        for (i, ord) in wg.order_by.iter().enumerate() {
17664            if i > 0 {
17665                self.write(", ");
17666            }
17667            self.generate_ordered(ord)?;
17668        }
17669        self.write(")");
17670        Ok(())
17671    }
17672
17673    /// Generate the contents of an OVER clause (without parentheses)
17674    fn generate_over(&mut self, over: &Over) -> Result<()> {
17675        let mut has_content = false;
17676
17677        // Named window reference
17678        if let Some(name) = &over.window_name {
17679            self.write(&name.name);
17680            has_content = true;
17681        }
17682
17683        // PARTITION BY
17684        if !over.partition_by.is_empty() {
17685            if has_content {
17686                self.write_space();
17687            }
17688            self.write_keyword("PARTITION BY");
17689            self.write_space();
17690            for (i, expr) in over.partition_by.iter().enumerate() {
17691                if i > 0 {
17692                    self.write(", ");
17693                }
17694                self.generate_expression(expr)?;
17695            }
17696            has_content = true;
17697        }
17698
17699        // ORDER BY
17700        if !over.order_by.is_empty() {
17701            if has_content {
17702                self.write_space();
17703            }
17704            self.write_keyword("ORDER BY");
17705            self.write_space();
17706            for (i, ordered) in over.order_by.iter().enumerate() {
17707                if i > 0 {
17708                    self.write(", ");
17709                }
17710                self.generate_ordered(ordered)?;
17711            }
17712            has_content = true;
17713        }
17714
17715        // Window frame
17716        if let Some(frame) = &over.frame {
17717            if has_content {
17718                self.write_space();
17719            }
17720            self.generate_window_frame(frame)?;
17721        }
17722
17723        Ok(())
17724    }
17725
17726    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
17727        // Exasol uses lowercase for frame kind (rows/range/groups)
17728        let lowercase_frame = self.config.lowercase_window_frame_keywords;
17729
17730        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
17731        if !lowercase_frame {
17732            if let Some(kind_text) = &frame.kind_text {
17733                self.write(kind_text);
17734            } else {
17735                match frame.kind {
17736                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
17737                    WindowFrameKind::Range => self.write_keyword("RANGE"),
17738                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
17739                }
17740            }
17741        } else {
17742            match frame.kind {
17743                WindowFrameKind::Rows => self.write("rows"),
17744                WindowFrameKind::Range => self.write("range"),
17745                WindowFrameKind::Groups => self.write("groups"),
17746            }
17747        }
17748
17749        // Use BETWEEN format only when there's an explicit end bound,
17750        // or when normalize_window_frame_between is enabled and the start is a directional bound
17751        self.write_space();
17752        let should_normalize = self.config.normalize_window_frame_between
17753            && frame.end.is_none()
17754            && matches!(
17755                frame.start,
17756                WindowFrameBound::Preceding(_)
17757                    | WindowFrameBound::Following(_)
17758                    | WindowFrameBound::UnboundedPreceding
17759                    | WindowFrameBound::UnboundedFollowing
17760            );
17761
17762        if let Some(end) = &frame.end {
17763            // BETWEEN format: RANGE BETWEEN start AND end
17764            self.write_keyword("BETWEEN");
17765            self.write_space();
17766            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
17767            self.write_space();
17768            self.write_keyword("AND");
17769            self.write_space();
17770            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
17771        } else if should_normalize {
17772            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
17773            self.write_keyword("BETWEEN");
17774            self.write_space();
17775            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
17776            self.write_space();
17777            self.write_keyword("AND");
17778            self.write_space();
17779            self.write_keyword("CURRENT ROW");
17780        } else {
17781            // Single bound format: RANGE CURRENT ROW
17782            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
17783        }
17784
17785        // EXCLUDE clause
17786        if let Some(exclude) = &frame.exclude {
17787            self.write_space();
17788            self.write_keyword("EXCLUDE");
17789            self.write_space();
17790            match exclude {
17791                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
17792                WindowFrameExclude::Group => self.write_keyword("GROUP"),
17793                WindowFrameExclude::Ties => self.write_keyword("TIES"),
17794                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
17795            }
17796        }
17797
17798        Ok(())
17799    }
17800
17801    fn generate_window_frame_bound(
17802        &mut self,
17803        bound: &WindowFrameBound,
17804        side_text: Option<&str>,
17805    ) -> Result<()> {
17806        // Exasol uses lowercase for preceding/following
17807        let lowercase_frame = self.config.lowercase_window_frame_keywords;
17808
17809        match bound {
17810            WindowFrameBound::CurrentRow => {
17811                self.write_keyword("CURRENT ROW");
17812            }
17813            WindowFrameBound::UnboundedPreceding => {
17814                self.write_keyword("UNBOUNDED");
17815                self.write_space();
17816                if lowercase_frame {
17817                    self.write("preceding");
17818                } else if let Some(text) = side_text {
17819                    self.write(text);
17820                } else {
17821                    self.write_keyword("PRECEDING");
17822                }
17823            }
17824            WindowFrameBound::UnboundedFollowing => {
17825                self.write_keyword("UNBOUNDED");
17826                self.write_space();
17827                if lowercase_frame {
17828                    self.write("following");
17829                } else if let Some(text) = side_text {
17830                    self.write(text);
17831                } else {
17832                    self.write_keyword("FOLLOWING");
17833                }
17834            }
17835            WindowFrameBound::Preceding(expr) => {
17836                self.generate_expression(expr)?;
17837                self.write_space();
17838                if lowercase_frame {
17839                    self.write("preceding");
17840                } else if let Some(text) = side_text {
17841                    self.write(text);
17842                } else {
17843                    self.write_keyword("PRECEDING");
17844                }
17845            }
17846            WindowFrameBound::Following(expr) => {
17847                self.generate_expression(expr)?;
17848                self.write_space();
17849                if lowercase_frame {
17850                    self.write("following");
17851                } else if let Some(text) = side_text {
17852                    self.write(text);
17853                } else {
17854                    self.write_keyword("FOLLOWING");
17855                }
17856            }
17857            WindowFrameBound::BarePreceding => {
17858                if lowercase_frame {
17859                    self.write("preceding");
17860                } else if let Some(text) = side_text {
17861                    self.write(text);
17862                } else {
17863                    self.write_keyword("PRECEDING");
17864                }
17865            }
17866            WindowFrameBound::BareFollowing => {
17867                if lowercase_frame {
17868                    self.write("following");
17869                } else if let Some(text) = side_text {
17870                    self.write(text);
17871                } else {
17872                    self.write_keyword("FOLLOWING");
17873                }
17874            }
17875            WindowFrameBound::Value(expr) => {
17876                // Bare numeric bound without PRECEDING/FOLLOWING
17877                self.generate_expression(expr)?;
17878            }
17879        }
17880        Ok(())
17881    }
17882
17883    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
17884        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
17885        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
17886        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
17887            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
17888            && !matches!(&interval.this, Some(Expression::Literal(_)));
17889
17890        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
17891        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
17892        if self.config.single_string_interval {
17893            if let (
17894                Some(Expression::Literal(lit)),
17895                Some(IntervalUnitSpec::Simple {
17896                    ref unit,
17897                    ref use_plural,
17898                }),
17899            ) = (&interval.this, &interval.unit)
17900            {
17901                if let Literal::String(ref val) = lit.as_ref() {
17902                    self.write_keyword("INTERVAL");
17903                    self.write_space();
17904                    let effective_plural = *use_plural && self.config.interval_allows_plural_form;
17905                    let unit_str = self.interval_unit_str(unit, effective_plural);
17906                    self.write("'");
17907                    self.write(val);
17908                    self.write(" ");
17909                    self.write(&unit_str);
17910                    self.write("'");
17911                    return Ok(());
17912                }
17913            }
17914        }
17915
17916        if !skip_interval_keyword {
17917            self.write_keyword("INTERVAL");
17918        }
17919
17920        // Generate value if present
17921        if let Some(ref value) = interval.this {
17922            if !skip_interval_keyword {
17923                self.write_space();
17924            }
17925            // If the value is a complex expression (not a literal/column/function call)
17926            // and there's a unit, wrap it in parentheses
17927            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
17928            let needs_parens = interval.unit.is_some()
17929                && matches!(
17930                    value,
17931                    Expression::Add(_)
17932                        | Expression::Sub(_)
17933                        | Expression::Mul(_)
17934                        | Expression::Div(_)
17935                        | Expression::Mod(_)
17936                        | Expression::BitwiseAnd(_)
17937                        | Expression::BitwiseOr(_)
17938                        | Expression::BitwiseXor(_)
17939                );
17940            if needs_parens {
17941                self.write("(");
17942            }
17943            self.generate_expression(value)?;
17944            if needs_parens {
17945                self.write(")");
17946            }
17947        }
17948
17949        // Generate unit if present
17950        if let Some(ref unit_spec) = interval.unit {
17951            self.write_space();
17952            self.write_interval_unit_spec(unit_spec)?;
17953        }
17954
17955        Ok(())
17956    }
17957
17958    /// Return the string representation of an interval unit
17959    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
17960        match (unit, use_plural) {
17961            (IntervalUnit::Year, false) => "YEAR",
17962            (IntervalUnit::Year, true) => "YEARS",
17963            (IntervalUnit::Quarter, false) => "QUARTER",
17964            (IntervalUnit::Quarter, true) => "QUARTERS",
17965            (IntervalUnit::Month, false) => "MONTH",
17966            (IntervalUnit::Month, true) => "MONTHS",
17967            (IntervalUnit::Week, false) => "WEEK",
17968            (IntervalUnit::Week, true) => "WEEKS",
17969            (IntervalUnit::Day, false) => "DAY",
17970            (IntervalUnit::Day, true) => "DAYS",
17971            (IntervalUnit::Hour, false) => "HOUR",
17972            (IntervalUnit::Hour, true) => "HOURS",
17973            (IntervalUnit::Minute, false) => "MINUTE",
17974            (IntervalUnit::Minute, true) => "MINUTES",
17975            (IntervalUnit::Second, false) => "SECOND",
17976            (IntervalUnit::Second, true) => "SECONDS",
17977            (IntervalUnit::Millisecond, false) => "MILLISECOND",
17978            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
17979            (IntervalUnit::Microsecond, false) => "MICROSECOND",
17980            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
17981            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
17982            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
17983        }
17984    }
17985
17986    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
17987        match unit_spec {
17988            IntervalUnitSpec::Simple { unit, use_plural } => {
17989                // If dialect doesn't allow plural forms, force singular
17990                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
17991                self.write_simple_interval_unit(unit, effective_plural);
17992            }
17993            IntervalUnitSpec::Span(span) => {
17994                self.write_simple_interval_unit(&span.this, false);
17995                self.write_space();
17996                self.write_keyword("TO");
17997                self.write_space();
17998                self.write_simple_interval_unit(&span.expression, false);
17999            }
18000            IntervalUnitSpec::ExprSpan(span) => {
18001                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
18002                self.generate_expression(&span.this)?;
18003                self.write_space();
18004                self.write_keyword("TO");
18005                self.write_space();
18006                self.generate_expression(&span.expression)?;
18007            }
18008            IntervalUnitSpec::Expr(expr) => {
18009                self.generate_expression(expr)?;
18010            }
18011        }
18012        Ok(())
18013    }
18014
18015    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
18016        // Output interval unit, respecting plural preference
18017        match (unit, use_plural) {
18018            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
18019            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
18020            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
18021            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
18022            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
18023            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
18024            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
18025            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
18026            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
18027            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
18028            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
18029            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
18030            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
18031            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
18032            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
18033            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
18034            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
18035            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
18036            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
18037            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
18038            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
18039            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
18040        }
18041    }
18042
18043    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
18044    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
18045    fn write_redshift_date_part(&mut self, expr: &Expression) {
18046        let part_str = self.extract_date_part_string(expr);
18047        if let Some(part) = part_str {
18048            let normalized = self.normalize_date_part(&part);
18049            self.write_keyword(&normalized);
18050        } else {
18051            // If we can't extract a date part string, fall back to generating the expression
18052            let _ = self.generate_expression(expr);
18053        }
18054    }
18055
18056    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
18057    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
18058    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
18059        let part_str = self.extract_date_part_string(expr);
18060        if let Some(part) = part_str {
18061            let normalized = self.normalize_date_part(&part);
18062            self.write("'");
18063            self.write(&normalized);
18064            self.write("'");
18065        } else {
18066            // If we can't extract a date part string, fall back to generating the expression
18067            let _ = self.generate_expression(expr);
18068        }
18069    }
18070
18071    /// Extract date part string from expression (handles string literals and identifiers)
18072    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
18073        match expr {
18074            Expression::Literal(lit)
18075                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
18076            {
18077                let crate::expressions::Literal::String(s) = lit.as_ref() else {
18078                    unreachable!()
18079                };
18080                Some(s.clone())
18081            }
18082            Expression::Identifier(id) => Some(id.name.clone()),
18083            Expression::Var(v) => Some(v.this.clone()),
18084            Expression::Column(col) if col.table.is_none() => {
18085                // Simple column reference without table prefix, treat as identifier
18086                Some(col.name.name.clone())
18087            }
18088            _ => None,
18089        }
18090    }
18091
18092    /// Normalize date part to uppercase singular form
18093    /// days -> DAY, months -> MONTH, etc.
18094    fn normalize_date_part(&self, part: &str) -> String {
18095        let mut buf = [0u8; 64];
18096        let lower: &str = if part.len() <= 64 {
18097            for (i, b) in part.bytes().enumerate() {
18098                buf[i] = b.to_ascii_lowercase();
18099            }
18100            std::str::from_utf8(&buf[..part.len()]).unwrap_or(part)
18101        } else {
18102            return part.to_ascii_uppercase();
18103        };
18104        match lower {
18105            "day" | "days" | "d" => "DAY".to_string(),
18106            "month" | "months" | "mon" | "mm" => "MONTH".to_string(),
18107            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
18108            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
18109            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
18110            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
18111            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
18112            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
18113            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
18114            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
18115            _ => part.to_ascii_uppercase(),
18116        }
18117    }
18118
18119    fn write_datetime_field(&mut self, field: &DateTimeField) {
18120        match field {
18121            DateTimeField::Year => self.write_keyword("YEAR"),
18122            DateTimeField::Month => self.write_keyword("MONTH"),
18123            DateTimeField::Day => self.write_keyword("DAY"),
18124            DateTimeField::Hour => self.write_keyword("HOUR"),
18125            DateTimeField::Minute => self.write_keyword("MINUTE"),
18126            DateTimeField::Second => self.write_keyword("SECOND"),
18127            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
18128            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
18129            DateTimeField::DayOfWeek => {
18130                let name = match self.config.dialect {
18131                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
18132                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => "WEEKDAY",
18133                    _ => "DOW",
18134                };
18135                self.write_keyword(name);
18136            }
18137            DateTimeField::DayOfYear => {
18138                let name = match self.config.dialect {
18139                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
18140                    _ => "DOY",
18141                };
18142                self.write_keyword(name);
18143            }
18144            DateTimeField::Week => self.write_keyword("WEEK"),
18145            DateTimeField::WeekWithModifier(modifier) => {
18146                self.write_keyword("WEEK");
18147                self.write("(");
18148                self.write(modifier);
18149                self.write(")");
18150            }
18151            DateTimeField::Quarter => self.write_keyword("QUARTER"),
18152            DateTimeField::Epoch => self.write_keyword("EPOCH"),
18153            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
18154            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
18155            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
18156            DateTimeField::Date => self.write_keyword("DATE"),
18157            DateTimeField::Time => self.write_keyword("TIME"),
18158            DateTimeField::Custom(name) => self.write(name),
18159        }
18160    }
18161
18162    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
18163    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
18164        match field {
18165            DateTimeField::Year => self.write("year"),
18166            DateTimeField::Month => self.write("month"),
18167            DateTimeField::Day => self.write("day"),
18168            DateTimeField::Hour => self.write("hour"),
18169            DateTimeField::Minute => self.write("minute"),
18170            DateTimeField::Second => self.write("second"),
18171            DateTimeField::Millisecond => self.write("millisecond"),
18172            DateTimeField::Microsecond => self.write("microsecond"),
18173            DateTimeField::DayOfWeek => self.write("dow"),
18174            DateTimeField::DayOfYear => self.write("doy"),
18175            DateTimeField::Week => self.write("week"),
18176            DateTimeField::WeekWithModifier(modifier) => {
18177                self.write("week(");
18178                self.write(modifier);
18179                self.write(")");
18180            }
18181            DateTimeField::Quarter => self.write("quarter"),
18182            DateTimeField::Epoch => self.write("epoch"),
18183            DateTimeField::Timezone => self.write("timezone"),
18184            DateTimeField::TimezoneHour => self.write("timezone_hour"),
18185            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
18186            DateTimeField::Date => self.write("date"),
18187            DateTimeField::Time => self.write("time"),
18188            DateTimeField::Custom(name) => self.write(name),
18189        }
18190    }
18191
18192    // Helper function generators
18193
18194    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
18195        self.write_keyword(name);
18196        self.write("(");
18197        self.generate_expression(arg)?;
18198        self.write(")");
18199        Ok(())
18200    }
18201
18202    /// Generate a unary function, using the original name if available for round-trip preservation
18203    fn generate_unary_func(
18204        &mut self,
18205        default_name: &str,
18206        f: &crate::expressions::UnaryFunc,
18207    ) -> Result<()> {
18208        let name = f.original_name.as_deref().unwrap_or(default_name);
18209        self.write_keyword(name);
18210        self.write("(");
18211        self.generate_expression(&f.this)?;
18212        self.write(")");
18213        Ok(())
18214    }
18215
18216    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
18217    fn generate_sqrt_cbrt(
18218        &mut self,
18219        f: &crate::expressions::UnaryFunc,
18220        func_name: &str,
18221        _op: &str,
18222    ) -> Result<()> {
18223        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
18224        // Always use function syntax for consistency
18225        self.write_keyword(func_name);
18226        self.write("(");
18227        self.generate_expression(&f.this)?;
18228        self.write(")");
18229        Ok(())
18230    }
18231
18232    fn generate_binary_func(
18233        &mut self,
18234        name: &str,
18235        arg1: &Expression,
18236        arg2: &Expression,
18237    ) -> Result<()> {
18238        self.write_keyword(name);
18239        self.write("(");
18240        self.generate_expression(arg1)?;
18241        self.write(", ");
18242        self.generate_expression(arg2)?;
18243        self.write(")");
18244        Ok(())
18245    }
18246
18247    /// Generate CHAR/CHR function with optional USING charset
18248    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
18249    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
18250    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
18251        // Use stored name if available, otherwise default to CHAR
18252        let func_name = f.name.as_deref().unwrap_or("CHAR");
18253        self.write_keyword(func_name);
18254        self.write("(");
18255        for (i, arg) in f.args.iter().enumerate() {
18256            if i > 0 {
18257                self.write(", ");
18258            }
18259            self.generate_expression(arg)?;
18260        }
18261        if let Some(ref charset) = f.charset {
18262            self.write(" ");
18263            self.write_keyword("USING");
18264            self.write(" ");
18265            self.write(charset);
18266        }
18267        self.write(")");
18268        Ok(())
18269    }
18270
18271    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
18272        use crate::dialects::DialectType;
18273
18274        match self.config.dialect {
18275            Some(DialectType::Teradata) => {
18276                // Teradata uses ** operator for exponentiation
18277                self.generate_expression(&f.this)?;
18278                self.write(" ** ");
18279                self.generate_expression(&f.expression)?;
18280                Ok(())
18281            }
18282            _ => {
18283                // Other dialects use POWER function
18284                self.generate_binary_func("POWER", &f.this, &f.expression)
18285            }
18286        }
18287    }
18288
18289    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
18290        self.write_func_name(name);
18291        self.write("(");
18292        for (i, arg) in args.iter().enumerate() {
18293            if i > 0 {
18294                self.write(", ");
18295            }
18296            self.generate_expression(arg)?;
18297        }
18298        self.write(")");
18299        Ok(())
18300    }
18301
18302    // String function generators
18303
18304    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
18305        self.write_keyword("CONCAT_WS");
18306        self.write("(");
18307        self.generate_expression(&f.separator)?;
18308        for expr in &f.expressions {
18309            self.write(", ");
18310            self.generate_expression(expr)?;
18311        }
18312        self.write(")");
18313        Ok(())
18314    }
18315
18316    fn collect_concat_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
18317        if let Expression::Concat(op) = expr {
18318            Self::collect_concat_operands(&op.left, out);
18319            Self::collect_concat_operands(&op.right, out);
18320        } else {
18321            out.push(expr);
18322        }
18323    }
18324
18325    fn generate_mysql_concat_from_concat(&mut self, op: &BinaryOp) -> Result<()> {
18326        let mut operands = Vec::new();
18327        Self::collect_concat_operands(&op.left, &mut operands);
18328        Self::collect_concat_operands(&op.right, &mut operands);
18329
18330        self.write_keyword("CONCAT");
18331        self.write("(");
18332        for (i, operand) in operands.iter().enumerate() {
18333            if i > 0 {
18334                self.write(", ");
18335            }
18336            self.generate_expression(operand)?;
18337        }
18338        self.write(")");
18339        Ok(())
18340    }
18341
18342    fn collect_dpipe_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
18343        if let Expression::DPipe(dpipe) = expr {
18344            Self::collect_dpipe_operands(&dpipe.this, out);
18345            Self::collect_dpipe_operands(&dpipe.expression, out);
18346        } else {
18347            out.push(expr);
18348        }
18349    }
18350
18351    fn generate_mysql_concat_from_dpipe(&mut self, e: &DPipe) -> Result<()> {
18352        let mut operands = Vec::new();
18353        Self::collect_dpipe_operands(&e.this, &mut operands);
18354        Self::collect_dpipe_operands(&e.expression, &mut operands);
18355
18356        self.write_keyword("CONCAT");
18357        self.write("(");
18358        for (i, operand) in operands.iter().enumerate() {
18359            if i > 0 {
18360                self.write(", ");
18361            }
18362            self.generate_expression(operand)?;
18363        }
18364        self.write(")");
18365        Ok(())
18366    }
18367
18368    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
18369        // Oracle uses SUBSTR; most others use SUBSTRING
18370        let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
18371        if is_oracle {
18372            self.write_keyword("SUBSTR");
18373        } else {
18374            self.write_keyword("SUBSTRING");
18375        }
18376        self.write("(");
18377        self.generate_expression(&f.this)?;
18378        // PostgreSQL always uses FROM/FOR syntax
18379        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
18380        // Spark/Hive use comma syntax, not FROM/FOR syntax
18381        let use_comma_syntax = matches!(
18382            self.config.dialect,
18383            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
18384        );
18385        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
18386            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
18387            self.write_space();
18388            self.write_keyword("FROM");
18389            self.write_space();
18390            self.generate_expression(&f.start)?;
18391            if let Some(length) = &f.length {
18392                self.write_space();
18393                self.write_keyword("FOR");
18394                self.write_space();
18395                self.generate_expression(length)?;
18396            }
18397        } else {
18398            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
18399            self.write(", ");
18400            self.generate_expression(&f.start)?;
18401            if let Some(length) = &f.length {
18402                self.write(", ");
18403                self.generate_expression(length)?;
18404            }
18405        }
18406        self.write(")");
18407        Ok(())
18408    }
18409
18410    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
18411        self.write_keyword("OVERLAY");
18412        self.write("(");
18413        self.generate_expression(&f.this)?;
18414        self.write_space();
18415        self.write_keyword("PLACING");
18416        self.write_space();
18417        self.generate_expression(&f.replacement)?;
18418        self.write_space();
18419        self.write_keyword("FROM");
18420        self.write_space();
18421        self.generate_expression(&f.from)?;
18422        if let Some(length) = &f.length {
18423            self.write_space();
18424            self.write_keyword("FOR");
18425            self.write_space();
18426            self.generate_expression(length)?;
18427        }
18428        self.write(")");
18429        Ok(())
18430    }
18431
18432    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
18433        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
18434        // when no characters are specified (PostgreSQL style)
18435        if f.position_explicit && f.characters.is_none() {
18436            match f.position {
18437                TrimPosition::Leading => {
18438                    self.write_keyword("LTRIM");
18439                    self.write("(");
18440                    self.generate_expression(&f.this)?;
18441                    self.write(")");
18442                    return Ok(());
18443                }
18444                TrimPosition::Trailing => {
18445                    self.write_keyword("RTRIM");
18446                    self.write("(");
18447                    self.generate_expression(&f.this)?;
18448                    self.write(")");
18449                    return Ok(());
18450                }
18451                TrimPosition::Both => {
18452                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
18453                    // Fall through to standard TRIM handling
18454                }
18455            }
18456        }
18457
18458        self.write_keyword("TRIM");
18459        self.write("(");
18460        // When BOTH is specified without trim characters, simplify to just TRIM(str)
18461        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
18462        let force_standard = f.characters.is_some()
18463            && !f.sql_standard_syntax
18464            && matches!(
18465                self.config.dialect,
18466                Some(DialectType::Hive)
18467                    | Some(DialectType::Spark)
18468                    | Some(DialectType::Databricks)
18469                    | Some(DialectType::ClickHouse)
18470            );
18471        let use_standard = (f.sql_standard_syntax || force_standard)
18472            && !(f.position_explicit
18473                && f.characters.is_none()
18474                && matches!(f.position, TrimPosition::Both));
18475        if use_standard {
18476            // SQL standard syntax: TRIM(BOTH chars FROM str)
18477            // Only output position if it was explicitly specified
18478            if f.position_explicit {
18479                match f.position {
18480                    TrimPosition::Both => self.write_keyword("BOTH"),
18481                    TrimPosition::Leading => self.write_keyword("LEADING"),
18482                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
18483                }
18484                self.write_space();
18485            }
18486            if let Some(chars) = &f.characters {
18487                self.generate_expression(chars)?;
18488                self.write_space();
18489            }
18490            self.write_keyword("FROM");
18491            self.write_space();
18492            self.generate_expression(&f.this)?;
18493        } else {
18494            // Simple function syntax: TRIM(str) or TRIM(str, chars)
18495            self.generate_expression(&f.this)?;
18496            if let Some(chars) = &f.characters {
18497                self.write(", ");
18498                self.generate_expression(chars)?;
18499            }
18500        }
18501        self.write(")");
18502        Ok(())
18503    }
18504
18505    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
18506        self.write_keyword("REPLACE");
18507        self.write("(");
18508        self.generate_expression(&f.this)?;
18509        self.write(", ");
18510        self.generate_expression(&f.old)?;
18511        self.write(", ");
18512        self.generate_expression(&f.new)?;
18513        self.write(")");
18514        Ok(())
18515    }
18516
18517    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
18518        self.write_keyword(name);
18519        self.write("(");
18520        self.generate_expression(&f.this)?;
18521        self.write(", ");
18522        self.generate_expression(&f.length)?;
18523        self.write(")");
18524        Ok(())
18525    }
18526
18527    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
18528        self.write_keyword("REPEAT");
18529        self.write("(");
18530        self.generate_expression(&f.this)?;
18531        self.write(", ");
18532        self.generate_expression(&f.times)?;
18533        self.write(")");
18534        Ok(())
18535    }
18536
18537    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
18538        self.write_keyword(name);
18539        self.write("(");
18540        self.generate_expression(&f.this)?;
18541        self.write(", ");
18542        self.generate_expression(&f.length)?;
18543        if let Some(fill) = &f.fill {
18544            self.write(", ");
18545            self.generate_expression(fill)?;
18546        }
18547        self.write(")");
18548        Ok(())
18549    }
18550
18551    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
18552        self.write_keyword("SPLIT");
18553        self.write("(");
18554        self.generate_expression(&f.this)?;
18555        self.write(", ");
18556        self.generate_expression(&f.delimiter)?;
18557        self.write(")");
18558        Ok(())
18559    }
18560
18561    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
18562        use crate::dialects::DialectType;
18563        // PostgreSQL uses ~ operator for regex matching
18564        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
18565            self.generate_expression(&f.this)?;
18566            self.write(" ~ ");
18567            self.generate_expression(&f.pattern)?;
18568        } else if matches!(self.config.dialect, Some(DialectType::Exasol)) && f.flags.is_none() {
18569            // Exasol uses REGEXP_LIKE as infix binary operator
18570            self.generate_expression(&f.this)?;
18571            self.write_keyword(" REGEXP_LIKE ");
18572            self.generate_expression(&f.pattern)?;
18573        } else if matches!(
18574            self.config.dialect,
18575            Some(DialectType::SingleStore)
18576                | Some(DialectType::Spark)
18577                | Some(DialectType::Hive)
18578                | Some(DialectType::Databricks)
18579        ) && f.flags.is_none()
18580        {
18581            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
18582            self.generate_expression(&f.this)?;
18583            self.write_keyword(" RLIKE ");
18584            self.generate_expression(&f.pattern)?;
18585        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
18586            // StarRocks uses REGEXP function syntax
18587            self.write_keyword("REGEXP");
18588            self.write("(");
18589            self.generate_expression(&f.this)?;
18590            self.write(", ");
18591            self.generate_expression(&f.pattern)?;
18592            if let Some(flags) = &f.flags {
18593                self.write(", ");
18594                self.generate_expression(flags)?;
18595            }
18596            self.write(")");
18597        } else {
18598            self.write_keyword("REGEXP_LIKE");
18599            self.write("(");
18600            self.generate_expression(&f.this)?;
18601            self.write(", ");
18602            self.generate_expression(&f.pattern)?;
18603            if let Some(flags) = &f.flags {
18604                self.write(", ");
18605                self.generate_expression(flags)?;
18606            }
18607            self.write(")");
18608        }
18609        Ok(())
18610    }
18611
18612    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
18613        self.write_keyword("REGEXP_REPLACE");
18614        self.write("(");
18615        self.generate_expression(&f.this)?;
18616        self.write(", ");
18617        self.generate_expression(&f.pattern)?;
18618        self.write(", ");
18619        self.generate_expression(&f.replacement)?;
18620        if let Some(flags) = &f.flags {
18621            self.write(", ");
18622            self.generate_expression(flags)?;
18623        }
18624        self.write(")");
18625        Ok(())
18626    }
18627
18628    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
18629        self.write_keyword("REGEXP_EXTRACT");
18630        self.write("(");
18631        self.generate_expression(&f.this)?;
18632        self.write(", ");
18633        self.generate_expression(&f.pattern)?;
18634        if let Some(group) = &f.group {
18635            self.write(", ");
18636            self.generate_expression(group)?;
18637        }
18638        self.write(")");
18639        Ok(())
18640    }
18641
18642    // Math function generators
18643
18644    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
18645        self.write_keyword("ROUND");
18646        self.write("(");
18647        self.generate_expression(&f.this)?;
18648        if let Some(decimals) = &f.decimals {
18649            self.write(", ");
18650            self.generate_expression(decimals)?;
18651        }
18652        self.write(")");
18653        Ok(())
18654    }
18655
18656    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
18657        self.write_keyword("FLOOR");
18658        self.write("(");
18659        self.generate_expression(&f.this)?;
18660        // Handle Druid-style FLOOR(time TO unit) syntax
18661        if let Some(to) = &f.to {
18662            self.write(" ");
18663            self.write_keyword("TO");
18664            self.write(" ");
18665            self.generate_expression(to)?;
18666        } else if let Some(scale) = &f.scale {
18667            self.write(", ");
18668            self.generate_expression(scale)?;
18669        }
18670        self.write(")");
18671        Ok(())
18672    }
18673
18674    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
18675        self.write_keyword("CEIL");
18676        self.write("(");
18677        self.generate_expression(&f.this)?;
18678        // Handle Druid-style CEIL(time TO unit) syntax
18679        if let Some(to) = &f.to {
18680            self.write(" ");
18681            self.write_keyword("TO");
18682            self.write(" ");
18683            self.generate_expression(to)?;
18684        } else if let Some(decimals) = &f.decimals {
18685            self.write(", ");
18686            self.generate_expression(decimals)?;
18687        }
18688        self.write(")");
18689        Ok(())
18690    }
18691
18692    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
18693        use crate::expressions::Literal;
18694
18695        if let Some(base) = &f.base {
18696            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
18697            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
18698            if self.is_log_base_none() {
18699                if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "2"))
18700                {
18701                    self.write_func_name("LOG2");
18702                    self.write("(");
18703                    self.generate_expression(&f.this)?;
18704                    self.write(")");
18705                    return Ok(());
18706                } else if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "10"))
18707                {
18708                    self.write_func_name("LOG10");
18709                    self.write("(");
18710                    self.generate_expression(&f.this)?;
18711                    self.write(")");
18712                    return Ok(());
18713                }
18714                // Other bases: fall through to LOG(base, value) — best effort
18715            }
18716
18717            self.write_func_name("LOG");
18718            self.write("(");
18719            if self.is_log_value_first() {
18720                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
18721                self.generate_expression(&f.this)?;
18722                self.write(", ");
18723                self.generate_expression(base)?;
18724            } else {
18725                // Default (PostgreSQL, etc.): LOG(base, value)
18726                self.generate_expression(base)?;
18727                self.write(", ");
18728                self.generate_expression(&f.this)?;
18729            }
18730            self.write(")");
18731        } else {
18732            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
18733            self.write_func_name("LOG");
18734            self.write("(");
18735            self.generate_expression(&f.this)?;
18736            self.write(")");
18737        }
18738        Ok(())
18739    }
18740
18741    /// Whether the target dialect uses LOG(value, base) order (value first).
18742    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
18743    fn is_log_value_first(&self) -> bool {
18744        use crate::dialects::DialectType;
18745        matches!(
18746            self.config.dialect,
18747            Some(DialectType::BigQuery)
18748                | Some(DialectType::TSQL)
18749                | Some(DialectType::Tableau)
18750                | Some(DialectType::Fabric)
18751        )
18752    }
18753
18754    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
18755    /// Presto, Trino, ClickHouse, Athena.
18756    fn is_log_base_none(&self) -> bool {
18757        use crate::dialects::DialectType;
18758        matches!(
18759            self.config.dialect,
18760            Some(DialectType::Presto)
18761                | Some(DialectType::Trino)
18762                | Some(DialectType::ClickHouse)
18763                | Some(DialectType::Athena)
18764        )
18765    }
18766
18767    // Date/time function generators
18768
18769    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
18770        self.write_keyword("CURRENT_TIME");
18771        if let Some(precision) = f.precision {
18772            self.write(&format!("({})", precision));
18773        } else if matches!(
18774            self.config.dialect,
18775            Some(crate::dialects::DialectType::MySQL)
18776                | Some(crate::dialects::DialectType::SingleStore)
18777                | Some(crate::dialects::DialectType::TiDB)
18778        ) {
18779            self.write("()");
18780        }
18781        Ok(())
18782    }
18783
18784    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
18785        use crate::dialects::DialectType;
18786
18787        // Oracle/Redshift SYSDATE handling
18788        if f.sysdate {
18789            match self.config.dialect {
18790                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
18791                    self.write_keyword("SYSDATE");
18792                    return Ok(());
18793                }
18794                Some(DialectType::Snowflake) => {
18795                    // Snowflake uses SYSDATE() function
18796                    self.write_keyword("SYSDATE");
18797                    self.write("()");
18798                    return Ok(());
18799                }
18800                _ => {
18801                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
18802                }
18803            }
18804        }
18805
18806        self.write_keyword("CURRENT_TIMESTAMP");
18807        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
18808        if let Some(precision) = f.precision {
18809            self.write(&format!("({})", precision));
18810        } else if matches!(
18811            self.config.dialect,
18812            Some(crate::dialects::DialectType::MySQL)
18813                | Some(crate::dialects::DialectType::SingleStore)
18814                | Some(crate::dialects::DialectType::TiDB)
18815                | Some(crate::dialects::DialectType::Spark)
18816                | Some(crate::dialects::DialectType::Hive)
18817                | Some(crate::dialects::DialectType::Databricks)
18818                | Some(crate::dialects::DialectType::ClickHouse)
18819                | Some(crate::dialects::DialectType::BigQuery)
18820                | Some(crate::dialects::DialectType::Snowflake)
18821                | Some(crate::dialects::DialectType::Exasol)
18822        ) {
18823            self.write("()");
18824        }
18825        Ok(())
18826    }
18827
18828    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
18829        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
18830        if self.config.dialect == Some(DialectType::Exasol) {
18831            self.write_keyword("CONVERT_TZ");
18832            self.write("(");
18833            self.generate_expression(&f.this)?;
18834            self.write(", 'UTC', ");
18835            self.generate_expression(&f.zone)?;
18836            self.write(")");
18837            return Ok(());
18838        }
18839
18840        self.generate_expression(&f.this)?;
18841        self.write_space();
18842        self.write_keyword("AT TIME ZONE");
18843        self.write_space();
18844        self.generate_expression(&f.zone)?;
18845        Ok(())
18846    }
18847
18848    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
18849        use crate::dialects::DialectType;
18850
18851        // Presto/Trino use DATE_ADD('unit', interval, date) format
18852        // with the interval cast to BIGINT when needed
18853        let is_presto_like = matches!(
18854            self.config.dialect,
18855            Some(DialectType::Presto) | Some(DialectType::Trino)
18856        );
18857
18858        if is_presto_like {
18859            self.write_keyword(name);
18860            self.write("(");
18861            // Unit as string literal
18862            self.write("'");
18863            self.write_simple_interval_unit(&f.unit, false);
18864            self.write("'");
18865            self.write(", ");
18866            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
18867            let needs_cast = !self.returns_integer_type(&f.interval);
18868            if needs_cast {
18869                self.write_keyword("CAST");
18870                self.write("(");
18871            }
18872            self.generate_expression(&f.interval)?;
18873            if needs_cast {
18874                self.write_space();
18875                self.write_keyword("AS");
18876                self.write_space();
18877                self.write_keyword("BIGINT");
18878                self.write(")");
18879            }
18880            self.write(", ");
18881            self.generate_expression(&f.this)?;
18882            self.write(")");
18883        } else {
18884            self.write_keyword(name);
18885            self.write("(");
18886            self.generate_expression(&f.this)?;
18887            self.write(", ");
18888            self.write_keyword("INTERVAL");
18889            self.write_space();
18890            self.generate_expression(&f.interval)?;
18891            self.write_space();
18892            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
18893            self.write(")");
18894        }
18895        Ok(())
18896    }
18897
18898    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
18899    /// This is a heuristic to avoid full type inference
18900    fn returns_integer_type(&self, expr: &Expression) -> bool {
18901        use crate::expressions::{DataType, Literal};
18902        match expr {
18903            // Integer literals (no decimal point)
18904            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
18905                let Literal::Number(n) = lit.as_ref() else {
18906                    unreachable!()
18907                };
18908                !n.contains('.')
18909            }
18910
18911            // FLOOR(x) returns integer if x is integer
18912            Expression::Floor(f) => self.returns_integer_type(&f.this),
18913
18914            // ROUND(x) returns integer if x is integer
18915            Expression::Round(f) => {
18916                // Only if no decimals arg or it's returning an integer
18917                f.decimals.is_none() && self.returns_integer_type(&f.this)
18918            }
18919
18920            // SIGN returns integer if input is integer
18921            Expression::Sign(f) => self.returns_integer_type(&f.this),
18922
18923            // ABS returns the same type as input
18924            Expression::Abs(f) => self.returns_integer_type(&f.this),
18925
18926            // Arithmetic operations on integers return integers
18927            Expression::Mul(op) => {
18928                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
18929            }
18930            Expression::Add(op) => {
18931                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
18932            }
18933            Expression::Sub(op) => {
18934                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
18935            }
18936            Expression::Mod(op) => self.returns_integer_type(&op.left),
18937
18938            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
18939            Expression::Cast(c) => matches!(
18940                &c.to,
18941                DataType::BigInt { .. }
18942                    | DataType::Int { .. }
18943                    | DataType::SmallInt { .. }
18944                    | DataType::TinyInt { .. }
18945            ),
18946
18947            // Negation: -x returns integer if x is integer
18948            Expression::Neg(op) => self.returns_integer_type(&op.this),
18949
18950            // Parenthesized expression
18951            Expression::Paren(p) => self.returns_integer_type(&p.this),
18952
18953            // Column references and most expressions are assumed to need casting
18954            // since we don't have full type information
18955            _ => false,
18956        }
18957    }
18958
18959    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
18960        self.write_keyword("DATEDIFF");
18961        self.write("(");
18962        if let Some(unit) = &f.unit {
18963            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
18964            self.write(", ");
18965        }
18966        self.generate_expression(&f.this)?;
18967        self.write(", ");
18968        self.generate_expression(&f.expression)?;
18969        self.write(")");
18970        Ok(())
18971    }
18972
18973    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
18974        self.write_keyword("DATE_TRUNC");
18975        self.write("('");
18976        self.write_datetime_field(&f.unit);
18977        self.write("', ");
18978        self.generate_expression(&f.this)?;
18979        self.write(")");
18980        Ok(())
18981    }
18982
18983    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
18984        use crate::dialects::DialectType;
18985        use crate::expressions::DateTimeField;
18986
18987        self.write_keyword("LAST_DAY");
18988        self.write("(");
18989        self.generate_expression(&f.this)?;
18990        if let Some(unit) = &f.unit {
18991            self.write(", ");
18992            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
18993            // WEEK(SUNDAY) -> WEEK
18994            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
18995                if let DateTimeField::WeekWithModifier(_) = unit {
18996                    self.write_keyword("WEEK");
18997                } else {
18998                    self.write_datetime_field(unit);
18999                }
19000            } else {
19001                self.write_datetime_field(unit);
19002            }
19003        }
19004        self.write(")");
19005        Ok(())
19006    }
19007
19008    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
19009        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
19010        if matches!(
19011            self.config.dialect,
19012            Some(DialectType::TSQL) | Some(DialectType::Fabric)
19013        ) {
19014            self.write_keyword("DATEPART");
19015            self.write("(");
19016            self.write_datetime_field(&f.field);
19017            self.write(", ");
19018            self.generate_expression(&f.this)?;
19019            self.write(")");
19020            return Ok(());
19021        }
19022        self.write_keyword("EXTRACT");
19023        self.write("(");
19024        // Hive/Spark use lowercase datetime fields in EXTRACT
19025        if matches!(
19026            self.config.dialect,
19027            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
19028        ) {
19029            self.write_datetime_field_lower(&f.field);
19030        } else {
19031            self.write_datetime_field(&f.field);
19032        }
19033        self.write_space();
19034        self.write_keyword("FROM");
19035        self.write_space();
19036        self.generate_expression(&f.this)?;
19037        self.write(")");
19038        Ok(())
19039    }
19040
19041    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
19042        self.write_keyword("TO_DATE");
19043        self.write("(");
19044        self.generate_expression(&f.this)?;
19045        if let Some(format) = &f.format {
19046            self.write(", ");
19047            self.generate_expression(format)?;
19048        }
19049        self.write(")");
19050        Ok(())
19051    }
19052
19053    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
19054        self.write_keyword("TO_TIMESTAMP");
19055        self.write("(");
19056        self.generate_expression(&f.this)?;
19057        if let Some(format) = &f.format {
19058            self.write(", ");
19059            self.generate_expression(format)?;
19060        }
19061        self.write(")");
19062        Ok(())
19063    }
19064
19065    // Control flow function generators
19066
19067    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
19068        use crate::dialects::DialectType;
19069
19070        // Generic mode: normalize IF to CASE WHEN
19071        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
19072            self.write_keyword("CASE WHEN");
19073            self.write_space();
19074            self.generate_expression(&f.condition)?;
19075            self.write_space();
19076            self.write_keyword("THEN");
19077            self.write_space();
19078            self.generate_expression(&f.true_value)?;
19079            if let Some(false_val) = &f.false_value {
19080                self.write_space();
19081                self.write_keyword("ELSE");
19082                self.write_space();
19083                self.generate_expression(false_val)?;
19084            }
19085            self.write_space();
19086            self.write_keyword("END");
19087            return Ok(());
19088        }
19089
19090        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
19091        if self.config.dialect == Some(DialectType::Exasol) {
19092            self.write_keyword("IF");
19093            self.write_space();
19094            self.generate_expression(&f.condition)?;
19095            self.write_space();
19096            self.write_keyword("THEN");
19097            self.write_space();
19098            self.generate_expression(&f.true_value)?;
19099            if let Some(false_val) = &f.false_value {
19100                self.write_space();
19101                self.write_keyword("ELSE");
19102                self.write_space();
19103                self.generate_expression(false_val)?;
19104            }
19105            self.write_space();
19106            self.write_keyword("ENDIF");
19107            return Ok(());
19108        }
19109
19110        // Choose function name based on target dialect
19111        let func_name = match self.config.dialect {
19112            Some(DialectType::Snowflake) => "IFF",
19113            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
19114            Some(DialectType::Drill) => "`IF`",
19115            _ => "IF",
19116        };
19117        self.write(func_name);
19118        self.write("(");
19119        self.generate_expression(&f.condition)?;
19120        self.write(", ");
19121        self.generate_expression(&f.true_value)?;
19122        if let Some(false_val) = &f.false_value {
19123            self.write(", ");
19124            self.generate_expression(false_val)?;
19125        }
19126        self.write(")");
19127        Ok(())
19128    }
19129
19130    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
19131        self.write_keyword("NVL2");
19132        self.write("(");
19133        self.generate_expression(&f.this)?;
19134        self.write(", ");
19135        self.generate_expression(&f.true_value)?;
19136        self.write(", ");
19137        self.generate_expression(&f.false_value)?;
19138        self.write(")");
19139        Ok(())
19140    }
19141
19142    // Typed aggregate function generators
19143
19144    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
19145        // Use normalize_functions for COUNT to respect ClickHouse case preservation
19146        let count_name = match self.config.normalize_functions {
19147            NormalizeFunctions::Upper => "COUNT".to_string(),
19148            NormalizeFunctions::Lower => "count".to_string(),
19149            NormalizeFunctions::None => f
19150                .original_name
19151                .clone()
19152                .unwrap_or_else(|| "COUNT".to_string()),
19153        };
19154        self.write(&count_name);
19155        self.write("(");
19156        if f.distinct {
19157            self.write_keyword("DISTINCT");
19158            self.write_space();
19159        }
19160        if f.star {
19161            self.write("*");
19162        } else if let Some(ref expr) = f.this {
19163            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
19164            if let Expression::Tuple(tuple) = expr {
19165                // Check if we need to transform multi-arg COUNT DISTINCT
19166                // When dialect doesn't support multi_arg_distinct, transform:
19167                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
19168                let needs_transform =
19169                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
19170
19171                if needs_transform {
19172                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
19173                    self.write_keyword("CASE");
19174                    for e in &tuple.expressions {
19175                        self.write_space();
19176                        self.write_keyword("WHEN");
19177                        self.write_space();
19178                        self.generate_expression(e)?;
19179                        self.write_space();
19180                        self.write_keyword("IS NULL THEN NULL");
19181                    }
19182                    self.write_space();
19183                    self.write_keyword("ELSE");
19184                    self.write(" (");
19185                    for (i, e) in tuple.expressions.iter().enumerate() {
19186                        if i > 0 {
19187                            self.write(", ");
19188                        }
19189                        self.generate_expression(e)?;
19190                    }
19191                    self.write(")");
19192                    self.write_space();
19193                    self.write_keyword("END");
19194                } else {
19195                    for (i, e) in tuple.expressions.iter().enumerate() {
19196                        if i > 0 {
19197                            self.write(", ");
19198                        }
19199                        self.generate_expression(e)?;
19200                    }
19201                }
19202            } else {
19203                self.generate_expression(expr)?;
19204            }
19205        }
19206        // RESPECT NULLS / IGNORE NULLS
19207        if let Some(ignore) = f.ignore_nulls {
19208            self.write_space();
19209            if ignore {
19210                self.write_keyword("IGNORE NULLS");
19211            } else {
19212                self.write_keyword("RESPECT NULLS");
19213            }
19214        }
19215        self.write(")");
19216        if let Some(ref filter) = f.filter {
19217            self.write_space();
19218            self.write_keyword("FILTER");
19219            self.write("(");
19220            self.write_keyword("WHERE");
19221            self.write_space();
19222            self.generate_expression(filter)?;
19223            self.write(")");
19224        }
19225        Ok(())
19226    }
19227
19228    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
19229        // Apply function name normalization based on config
19230        let func_name: Cow<'_, str> = match self.config.normalize_functions {
19231            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
19232            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
19233            NormalizeFunctions::None => {
19234                // Use the original function name from parsing if available,
19235                // otherwise fall back to lowercase of the hardcoded constant
19236                if let Some(ref original) = f.name {
19237                    Cow::Owned(original.clone())
19238                } else {
19239                    Cow::Owned(name.to_ascii_lowercase())
19240                }
19241            }
19242        };
19243        self.write(func_name.as_ref());
19244        self.write("(");
19245        if f.distinct {
19246            self.write_keyword("DISTINCT");
19247            self.write_space();
19248        }
19249        // Skip generating the expression if it's a NULL placeholder for zero-arg aggregates like MODE()
19250        if !matches!(f.this, Expression::Null(_)) {
19251            self.generate_expression(&f.this)?;
19252        }
19253        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
19254        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
19255        if self.config.ignore_nulls_in_func
19256            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
19257        {
19258            match f.ignore_nulls {
19259                Some(true) => {
19260                    self.write_space();
19261                    self.write_keyword("IGNORE NULLS");
19262                }
19263                Some(false) => {
19264                    self.write_space();
19265                    self.write_keyword("RESPECT NULLS");
19266                }
19267                None => {}
19268            }
19269        }
19270        // Generate HAVING MAX/MIN if present (BigQuery syntax)
19271        // e.g., ANY_VALUE(fruit HAVING MAX sold)
19272        if let Some((ref expr, is_max)) = f.having_max {
19273            self.write_space();
19274            self.write_keyword("HAVING");
19275            self.write_space();
19276            if is_max {
19277                self.write_keyword("MAX");
19278            } else {
19279                self.write_keyword("MIN");
19280            }
19281            self.write_space();
19282            self.generate_expression(expr)?;
19283        }
19284        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
19285        if !f.order_by.is_empty() {
19286            self.write_space();
19287            self.write_keyword("ORDER BY");
19288            self.write_space();
19289            for (i, ord) in f.order_by.iter().enumerate() {
19290                if i > 0 {
19291                    self.write(", ");
19292                }
19293                self.generate_ordered(ord)?;
19294            }
19295        }
19296        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
19297        if let Some(ref limit) = f.limit {
19298            self.write_space();
19299            self.write_keyword("LIMIT");
19300            self.write_space();
19301            // Check if this is a Tuple representing LIMIT offset, count
19302            if let Expression::Tuple(t) = limit.as_ref() {
19303                if t.expressions.len() == 2 {
19304                    self.generate_expression(&t.expressions[0])?;
19305                    self.write(", ");
19306                    self.generate_expression(&t.expressions[1])?;
19307                } else {
19308                    self.generate_expression(limit)?;
19309                }
19310            } else {
19311                self.generate_expression(limit)?;
19312            }
19313        }
19314        self.write(")");
19315        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
19316        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
19317        if !self.config.ignore_nulls_in_func
19318            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
19319        {
19320            match f.ignore_nulls {
19321                Some(true) => {
19322                    self.write_space();
19323                    self.write_keyword("IGNORE NULLS");
19324                }
19325                Some(false) => {
19326                    self.write_space();
19327                    self.write_keyword("RESPECT NULLS");
19328                }
19329                None => {}
19330            }
19331        }
19332        if let Some(ref filter) = f.filter {
19333            self.write_space();
19334            self.write_keyword("FILTER");
19335            self.write("(");
19336            self.write_keyword("WHERE");
19337            self.write_space();
19338            self.generate_expression(filter)?;
19339            self.write(")");
19340        }
19341        Ok(())
19342    }
19343
19344    /// Generate FIRST/LAST aggregate functions with Hive/Spark2-style boolean argument
19345    /// for IGNORE NULLS. In Hive/Spark2, `FIRST(col) IGNORE NULLS` is written as `FIRST(col, TRUE)`.
19346    fn generate_agg_func_with_ignore_nulls_bool(&mut self, name: &str, f: &AggFunc) -> Result<()> {
19347        // For Hive/Spark2 dialects, convert IGNORE NULLS to boolean TRUE argument
19348        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
19349            // Create a modified copy without ignore_nulls, add TRUE as part of the output
19350            let func_name: Cow<'_, str> = match self.config.normalize_functions {
19351                NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
19352                NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
19353                NormalizeFunctions::None => {
19354                    if let Some(ref original) = f.name {
19355                        Cow::Owned(original.clone())
19356                    } else {
19357                        Cow::Owned(name.to_ascii_lowercase())
19358                    }
19359                }
19360            };
19361            self.write(func_name.as_ref());
19362            self.write("(");
19363            if f.distinct {
19364                self.write_keyword("DISTINCT");
19365                self.write_space();
19366            }
19367            if !matches!(f.this, Expression::Null(_)) {
19368                self.generate_expression(&f.this)?;
19369            }
19370            self.write(", ");
19371            self.write_keyword("TRUE");
19372            self.write(")");
19373            return Ok(());
19374        }
19375        self.generate_agg_func(name, f)
19376    }
19377
19378    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
19379        self.write_keyword("GROUP_CONCAT");
19380        self.write("(");
19381        if f.distinct {
19382            self.write_keyword("DISTINCT");
19383            self.write_space();
19384        }
19385        self.generate_expression(&f.this)?;
19386        if let Some(ref order_by) = f.order_by {
19387            self.write_space();
19388            self.write_keyword("ORDER BY");
19389            self.write_space();
19390            for (i, ord) in order_by.iter().enumerate() {
19391                if i > 0 {
19392                    self.write(", ");
19393                }
19394                self.generate_ordered(ord)?;
19395            }
19396        }
19397        if let Some(ref sep) = f.separator {
19398            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
19399            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
19400            if matches!(
19401                self.config.dialect,
19402                Some(crate::dialects::DialectType::SQLite)
19403            ) {
19404                self.write(", ");
19405                self.generate_expression(sep)?;
19406            } else {
19407                self.write_space();
19408                self.write_keyword("SEPARATOR");
19409                self.write_space();
19410                self.generate_expression(sep)?;
19411            }
19412        }
19413        if let Some(ref limit) = f.limit {
19414            self.write_space();
19415            self.write_keyword("LIMIT");
19416            self.write_space();
19417            self.generate_expression(limit)?;
19418        }
19419        self.write(")");
19420        if let Some(ref filter) = f.filter {
19421            self.write_space();
19422            self.write_keyword("FILTER");
19423            self.write("(");
19424            self.write_keyword("WHERE");
19425            self.write_space();
19426            self.generate_expression(filter)?;
19427            self.write(")");
19428        }
19429        Ok(())
19430    }
19431
19432    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
19433        let is_tsql = matches!(
19434            self.config.dialect,
19435            Some(crate::dialects::DialectType::TSQL)
19436        );
19437        self.write_keyword("STRING_AGG");
19438        self.write("(");
19439        if f.distinct {
19440            self.write_keyword("DISTINCT");
19441            self.write_space();
19442        }
19443        self.generate_expression(&f.this)?;
19444        if let Some(ref separator) = f.separator {
19445            self.write(", ");
19446            self.generate_expression(separator)?;
19447        }
19448        // For TSQL, ORDER BY goes in WITHIN GROUP clause after the closing paren
19449        if !is_tsql {
19450            if let Some(ref order_by) = f.order_by {
19451                self.write_space();
19452                self.write_keyword("ORDER BY");
19453                self.write_space();
19454                for (i, ord) in order_by.iter().enumerate() {
19455                    if i > 0 {
19456                        self.write(", ");
19457                    }
19458                    self.generate_ordered(ord)?;
19459                }
19460            }
19461        }
19462        if let Some(ref limit) = f.limit {
19463            self.write_space();
19464            self.write_keyword("LIMIT");
19465            self.write_space();
19466            self.generate_expression(limit)?;
19467        }
19468        self.write(")");
19469        // TSQL uses WITHIN GROUP (ORDER BY ...) after the function call
19470        if is_tsql {
19471            if let Some(ref order_by) = f.order_by {
19472                self.write_space();
19473                self.write_keyword("WITHIN GROUP");
19474                self.write(" (");
19475                self.write_keyword("ORDER BY");
19476                self.write_space();
19477                for (i, ord) in order_by.iter().enumerate() {
19478                    if i > 0 {
19479                        self.write(", ");
19480                    }
19481                    self.generate_ordered(ord)?;
19482                }
19483                self.write(")");
19484            }
19485        }
19486        if let Some(ref filter) = f.filter {
19487            self.write_space();
19488            self.write_keyword("FILTER");
19489            self.write("(");
19490            self.write_keyword("WHERE");
19491            self.write_space();
19492            self.generate_expression(filter)?;
19493            self.write(")");
19494        }
19495        Ok(())
19496    }
19497
19498    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
19499        use crate::dialects::DialectType;
19500        self.write_keyword("LISTAGG");
19501        self.write("(");
19502        if f.distinct {
19503            self.write_keyword("DISTINCT");
19504            self.write_space();
19505        }
19506        self.generate_expression(&f.this)?;
19507        if let Some(ref sep) = f.separator {
19508            self.write(", ");
19509            self.generate_expression(sep)?;
19510        } else if matches!(
19511            self.config.dialect,
19512            Some(DialectType::Trino) | Some(DialectType::Presto)
19513        ) {
19514            // Trino/Presto require explicit separator; default to ','
19515            self.write(", ','");
19516        }
19517        if let Some(ref overflow) = f.on_overflow {
19518            self.write_space();
19519            self.write_keyword("ON OVERFLOW");
19520            self.write_space();
19521            match overflow {
19522                ListAggOverflow::Error => self.write_keyword("ERROR"),
19523                ListAggOverflow::Truncate { filler, with_count } => {
19524                    self.write_keyword("TRUNCATE");
19525                    if let Some(ref fill) = filler {
19526                        self.write_space();
19527                        self.generate_expression(fill)?;
19528                    }
19529                    if *with_count {
19530                        self.write_space();
19531                        self.write_keyword("WITH COUNT");
19532                    } else {
19533                        self.write_space();
19534                        self.write_keyword("WITHOUT COUNT");
19535                    }
19536                }
19537            }
19538        }
19539        self.write(")");
19540        if let Some(ref order_by) = f.order_by {
19541            self.write_space();
19542            self.write_keyword("WITHIN GROUP");
19543            self.write(" (");
19544            self.write_keyword("ORDER BY");
19545            self.write_space();
19546            for (i, ord) in order_by.iter().enumerate() {
19547                if i > 0 {
19548                    self.write(", ");
19549                }
19550                self.generate_ordered(ord)?;
19551            }
19552            self.write(")");
19553        }
19554        if let Some(ref filter) = f.filter {
19555            self.write_space();
19556            self.write_keyword("FILTER");
19557            self.write("(");
19558            self.write_keyword("WHERE");
19559            self.write_space();
19560            self.generate_expression(filter)?;
19561            self.write(")");
19562        }
19563        Ok(())
19564    }
19565
19566    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
19567        self.write_keyword("SUM_IF");
19568        self.write("(");
19569        self.generate_expression(&f.this)?;
19570        self.write(", ");
19571        self.generate_expression(&f.condition)?;
19572        self.write(")");
19573        if let Some(ref filter) = f.filter {
19574            self.write_space();
19575            self.write_keyword("FILTER");
19576            self.write("(");
19577            self.write_keyword("WHERE");
19578            self.write_space();
19579            self.generate_expression(filter)?;
19580            self.write(")");
19581        }
19582        Ok(())
19583    }
19584
19585    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
19586        self.write_keyword("APPROX_PERCENTILE");
19587        self.write("(");
19588        self.generate_expression(&f.this)?;
19589        self.write(", ");
19590        self.generate_expression(&f.percentile)?;
19591        if let Some(ref acc) = f.accuracy {
19592            self.write(", ");
19593            self.generate_expression(acc)?;
19594        }
19595        self.write(")");
19596        if let Some(ref filter) = f.filter {
19597            self.write_space();
19598            self.write_keyword("FILTER");
19599            self.write("(");
19600            self.write_keyword("WHERE");
19601            self.write_space();
19602            self.generate_expression(filter)?;
19603            self.write(")");
19604        }
19605        Ok(())
19606    }
19607
19608    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
19609        self.write_keyword(name);
19610        self.write("(");
19611        self.generate_expression(&f.percentile)?;
19612        self.write(")");
19613        if let Some(ref order_by) = f.order_by {
19614            self.write_space();
19615            self.write_keyword("WITHIN GROUP");
19616            self.write(" (");
19617            self.write_keyword("ORDER BY");
19618            self.write_space();
19619            self.generate_expression(&f.this)?;
19620            for ord in order_by.iter() {
19621                if ord.desc {
19622                    self.write_space();
19623                    self.write_keyword("DESC");
19624                }
19625            }
19626            self.write(")");
19627        }
19628        if let Some(ref filter) = f.filter {
19629            self.write_space();
19630            self.write_keyword("FILTER");
19631            self.write("(");
19632            self.write_keyword("WHERE");
19633            self.write_space();
19634            self.generate_expression(filter)?;
19635            self.write(")");
19636        }
19637        Ok(())
19638    }
19639
19640    // Window function generators
19641
19642    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
19643        self.write_keyword("NTILE");
19644        self.write("(");
19645        if let Some(num_buckets) = &f.num_buckets {
19646            self.generate_expression(num_buckets)?;
19647        }
19648        if let Some(order_by) = &f.order_by {
19649            self.write_keyword(" ORDER BY ");
19650            for (i, ob) in order_by.iter().enumerate() {
19651                if i > 0 {
19652                    self.write(", ");
19653                }
19654                self.generate_ordered(ob)?;
19655            }
19656        }
19657        self.write(")");
19658        Ok(())
19659    }
19660
19661    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
19662        self.write_keyword(name);
19663        self.write("(");
19664        self.generate_expression(&f.this)?;
19665        if let Some(ref offset) = f.offset {
19666            self.write(", ");
19667            self.generate_expression(offset)?;
19668            if let Some(ref default) = f.default {
19669                self.write(", ");
19670                self.generate_expression(default)?;
19671            }
19672        }
19673        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
19674        if self.config.ignore_nulls_in_func {
19675            match f.ignore_nulls {
19676                Some(true) => {
19677                    self.write_space();
19678                    self.write_keyword("IGNORE NULLS");
19679                }
19680                Some(false) => {
19681                    self.write_space();
19682                    self.write_keyword("RESPECT NULLS");
19683                }
19684                None => {}
19685            }
19686        }
19687        self.write(")");
19688        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
19689        if !self.config.ignore_nulls_in_func {
19690            match f.ignore_nulls {
19691                Some(true) => {
19692                    self.write_space();
19693                    self.write_keyword("IGNORE NULLS");
19694                }
19695                Some(false) => {
19696                    self.write_space();
19697                    self.write_keyword("RESPECT NULLS");
19698                }
19699                None => {}
19700            }
19701        }
19702        Ok(())
19703    }
19704
19705    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
19706        self.write_keyword(name);
19707        self.write("(");
19708        self.generate_expression(&f.this)?;
19709        // ORDER BY inside parens (e.g., DuckDB: LAST_VALUE(x ORDER BY x))
19710        if !f.order_by.is_empty() {
19711            self.write_space();
19712            self.write_keyword("ORDER BY");
19713            self.write_space();
19714            for (i, ordered) in f.order_by.iter().enumerate() {
19715                if i > 0 {
19716                    self.write(", ");
19717                }
19718                self.generate_ordered(ordered)?;
19719            }
19720        }
19721        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
19722        if self.config.ignore_nulls_in_func {
19723            match f.ignore_nulls {
19724                Some(true) => {
19725                    self.write_space();
19726                    self.write_keyword("IGNORE NULLS");
19727                }
19728                Some(false) => {
19729                    self.write_space();
19730                    self.write_keyword("RESPECT NULLS");
19731                }
19732                None => {}
19733            }
19734        }
19735        self.write(")");
19736        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
19737        if !self.config.ignore_nulls_in_func {
19738            match f.ignore_nulls {
19739                Some(true) => {
19740                    self.write_space();
19741                    self.write_keyword("IGNORE NULLS");
19742                }
19743                Some(false) => {
19744                    self.write_space();
19745                    self.write_keyword("RESPECT NULLS");
19746                }
19747                None => {}
19748            }
19749        }
19750        Ok(())
19751    }
19752
19753    /// Generate FIRST_VALUE/LAST_VALUE with Hive/Spark2-style boolean argument for IGNORE NULLS.
19754    /// In Hive/Spark2, `FIRST_VALUE(col) IGNORE NULLS` is written as `FIRST_VALUE(col, TRUE)`.
19755    fn generate_value_func_with_ignore_nulls_bool(
19756        &mut self,
19757        name: &str,
19758        f: &ValueFunc,
19759    ) -> Result<()> {
19760        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
19761            self.write_keyword(name);
19762            self.write("(");
19763            self.generate_expression(&f.this)?;
19764            self.write(", ");
19765            self.write_keyword("TRUE");
19766            self.write(")");
19767            return Ok(());
19768        }
19769        self.generate_value_func(name, f)
19770    }
19771
19772    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
19773        self.write_keyword("NTH_VALUE");
19774        self.write("(");
19775        self.generate_expression(&f.this)?;
19776        self.write(", ");
19777        self.generate_expression(&f.offset)?;
19778        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
19779        if self.config.ignore_nulls_in_func {
19780            match f.ignore_nulls {
19781                Some(true) => {
19782                    self.write_space();
19783                    self.write_keyword("IGNORE NULLS");
19784                }
19785                Some(false) => {
19786                    self.write_space();
19787                    self.write_keyword("RESPECT NULLS");
19788                }
19789                None => {}
19790            }
19791        }
19792        self.write(")");
19793        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
19794        if matches!(
19795            self.config.dialect,
19796            Some(crate::dialects::DialectType::Snowflake)
19797        ) {
19798            match f.from_first {
19799                Some(true) => {
19800                    self.write_space();
19801                    self.write_keyword("FROM FIRST");
19802                }
19803                Some(false) => {
19804                    self.write_space();
19805                    self.write_keyword("FROM LAST");
19806                }
19807                None => {}
19808            }
19809        }
19810        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
19811        if !self.config.ignore_nulls_in_func {
19812            match f.ignore_nulls {
19813                Some(true) => {
19814                    self.write_space();
19815                    self.write_keyword("IGNORE NULLS");
19816                }
19817                Some(false) => {
19818                    self.write_space();
19819                    self.write_keyword("RESPECT NULLS");
19820                }
19821                None => {}
19822            }
19823        }
19824        Ok(())
19825    }
19826
19827    // Additional string function generators
19828
19829    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
19830        // Standard syntax: POSITION(substr IN str)
19831        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
19832        if matches!(
19833            self.config.dialect,
19834            Some(crate::dialects::DialectType::ClickHouse)
19835        ) {
19836            self.write_keyword("POSITION");
19837            self.write("(");
19838            self.generate_expression(&f.string)?;
19839            self.write(", ");
19840            self.generate_expression(&f.substring)?;
19841            if let Some(ref start) = f.start {
19842                self.write(", ");
19843                self.generate_expression(start)?;
19844            }
19845            self.write(")");
19846            return Ok(());
19847        }
19848
19849        self.write_keyword("POSITION");
19850        self.write("(");
19851        self.generate_expression(&f.substring)?;
19852        self.write_space();
19853        self.write_keyword("IN");
19854        self.write_space();
19855        self.generate_expression(&f.string)?;
19856        if let Some(ref start) = f.start {
19857            self.write(", ");
19858            self.generate_expression(start)?;
19859        }
19860        self.write(")");
19861        Ok(())
19862    }
19863
19864    // Additional math function generators
19865
19866    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
19867        // Teradata RANDOM(lower, upper)
19868        if f.lower.is_some() || f.upper.is_some() {
19869            self.write_keyword("RANDOM");
19870            self.write("(");
19871            if let Some(ref lower) = f.lower {
19872                self.generate_expression(lower)?;
19873            }
19874            if let Some(ref upper) = f.upper {
19875                self.write(", ");
19876                self.generate_expression(upper)?;
19877            }
19878            self.write(")");
19879            return Ok(());
19880        }
19881        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
19882        let func_name = match self.config.dialect {
19883            Some(crate::dialects::DialectType::Snowflake)
19884            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
19885            _ => "RAND",
19886        };
19887        self.write_keyword(func_name);
19888        self.write("(");
19889        // DuckDB doesn't support seeded RANDOM, so skip the seed
19890        if !matches!(
19891            self.config.dialect,
19892            Some(crate::dialects::DialectType::DuckDB)
19893        ) {
19894            if let Some(ref seed) = f.seed {
19895                self.generate_expression(seed)?;
19896            }
19897        }
19898        self.write(")");
19899        Ok(())
19900    }
19901
19902    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
19903        self.write_keyword("TRUNCATE");
19904        self.write("(");
19905        self.generate_expression(&f.this)?;
19906        if let Some(ref decimals) = f.decimals {
19907            self.write(", ");
19908            self.generate_expression(decimals)?;
19909        }
19910        self.write(")");
19911        Ok(())
19912    }
19913
19914    // Control flow generators
19915
19916    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
19917        self.write_keyword("DECODE");
19918        self.write("(");
19919        self.generate_expression(&f.this)?;
19920        for (search, result) in &f.search_results {
19921            self.write(", ");
19922            self.generate_expression(search)?;
19923            self.write(", ");
19924            self.generate_expression(result)?;
19925        }
19926        if let Some(ref default) = f.default {
19927            self.write(", ");
19928            self.generate_expression(default)?;
19929        }
19930        self.write(")");
19931        Ok(())
19932    }
19933
19934    // Date/time function generators
19935
19936    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
19937        self.write_keyword(name);
19938        self.write("(");
19939        self.generate_expression(&f.this)?;
19940        self.write(", ");
19941        self.generate_expression(&f.format)?;
19942        self.write(")");
19943        Ok(())
19944    }
19945
19946    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
19947        self.write_keyword("FROM_UNIXTIME");
19948        self.write("(");
19949        self.generate_expression(&f.this)?;
19950        if let Some(ref format) = f.format {
19951            self.write(", ");
19952            self.generate_expression(format)?;
19953        }
19954        self.write(")");
19955        Ok(())
19956    }
19957
19958    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
19959        self.write_keyword("UNIX_TIMESTAMP");
19960        self.write("(");
19961        if let Some(ref expr) = f.this {
19962            self.generate_expression(expr)?;
19963            if let Some(ref format) = f.format {
19964                self.write(", ");
19965                self.generate_expression(format)?;
19966            }
19967        } else if matches!(
19968            self.config.dialect,
19969            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
19970        ) {
19971            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
19972            self.write_keyword("CURRENT_TIMESTAMP");
19973            self.write("()");
19974        }
19975        self.write(")");
19976        Ok(())
19977    }
19978
19979    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
19980        self.write_keyword("MAKE_DATE");
19981        self.write("(");
19982        self.generate_expression(&f.year)?;
19983        self.write(", ");
19984        self.generate_expression(&f.month)?;
19985        self.write(", ");
19986        self.generate_expression(&f.day)?;
19987        self.write(")");
19988        Ok(())
19989    }
19990
19991    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
19992        self.write_keyword("MAKE_TIMESTAMP");
19993        self.write("(");
19994        self.generate_expression(&f.year)?;
19995        self.write(", ");
19996        self.generate_expression(&f.month)?;
19997        self.write(", ");
19998        self.generate_expression(&f.day)?;
19999        self.write(", ");
20000        self.generate_expression(&f.hour)?;
20001        self.write(", ");
20002        self.generate_expression(&f.minute)?;
20003        self.write(", ");
20004        self.generate_expression(&f.second)?;
20005        if let Some(ref tz) = f.timezone {
20006            self.write(", ");
20007            self.generate_expression(tz)?;
20008        }
20009        self.write(")");
20010        Ok(())
20011    }
20012
20013    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
20014    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
20015        match expr {
20016            Expression::Struct(s) => {
20017                if s.fields.iter().all(|(name, _)| name.is_some()) {
20018                    Some(
20019                        s.fields
20020                            .iter()
20021                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
20022                            .collect(),
20023                    )
20024                } else {
20025                    None
20026                }
20027            }
20028            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20029                // Check if all args are Alias (named fields)
20030                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
20031                    Some(
20032                        f.args
20033                            .iter()
20034                            .filter_map(|a| {
20035                                if let Expression::Alias(alias) = a {
20036                                    Some(alias.alias.name.clone())
20037                                } else {
20038                                    None
20039                                }
20040                            })
20041                            .collect(),
20042                    )
20043                } else {
20044                    None
20045                }
20046            }
20047            _ => None,
20048        }
20049    }
20050
20051    /// Check if a struct expression has any unnamed fields
20052    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
20053        match expr {
20054            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
20055            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20056                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
20057            }
20058            _ => false,
20059        }
20060    }
20061
20062    /// Get the field count of a struct expression
20063    fn struct_field_count(expr: &Expression) -> usize {
20064        match expr {
20065            Expression::Struct(s) => s.fields.len(),
20066            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => f.args.len(),
20067            _ => 0,
20068        }
20069    }
20070
20071    /// Apply field names to an unnamed struct expression, producing a new expression with names
20072    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
20073        match expr {
20074            Expression::Struct(s) => {
20075                let mut new_fields = Vec::with_capacity(s.fields.len());
20076                for (i, (name, value)) in s.fields.iter().enumerate() {
20077                    if name.is_none() && i < field_names.len() {
20078                        new_fields.push((Some(field_names[i].clone()), value.clone()));
20079                    } else {
20080                        new_fields.push((name.clone(), value.clone()));
20081                    }
20082                }
20083                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
20084            }
20085            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20086                let mut new_args = Vec::with_capacity(f.args.len());
20087                for (i, arg) in f.args.iter().enumerate() {
20088                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
20089                        // Wrap the value in an Alias with the inherited name
20090                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
20091                            this: arg.clone(),
20092                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
20093                            column_aliases: Vec::new(),
20094                            pre_alias_comments: Vec::new(),
20095                            trailing_comments: Vec::new(),
20096                            inferred_type: None,
20097                        })));
20098                    } else {
20099                        new_args.push(arg.clone());
20100                    }
20101                }
20102                Expression::Function(Box::new(crate::expressions::Function {
20103                    name: f.name.clone(),
20104                    args: new_args,
20105                    distinct: f.distinct,
20106                    trailing_comments: f.trailing_comments.clone(),
20107                    use_bracket_syntax: f.use_bracket_syntax,
20108                    no_parens: f.no_parens,
20109                    quoted: f.quoted,
20110                    span: None,
20111                    inferred_type: None,
20112                }))
20113            }
20114            _ => expr.clone(),
20115        }
20116    }
20117
20118    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
20119    /// This implements BigQuery's implicit field name inheritance for struct arrays.
20120    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
20121    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
20122        let first = match expressions.first() {
20123            Some(e) => e,
20124            None => return expressions.to_vec(),
20125        };
20126
20127        let field_names = match Self::extract_struct_field_names(first) {
20128            Some(names) if !names.is_empty() => names,
20129            _ => return expressions.to_vec(),
20130        };
20131
20132        let mut result = Vec::with_capacity(expressions.len());
20133        for (idx, expr) in expressions.iter().enumerate() {
20134            if idx == 0 {
20135                result.push(expr.clone());
20136                continue;
20137            }
20138            // Check if this is a struct with unnamed fields that needs name propagation
20139            if Self::struct_field_count(expr) == field_names.len()
20140                && Self::struct_has_unnamed_fields(expr)
20141            {
20142                result.push(Self::apply_struct_field_names(expr, &field_names));
20143            } else {
20144                result.push(expr.clone());
20145            }
20146        }
20147        result
20148    }
20149
20150    // Array function generators
20151
20152    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
20153        // Apply struct name inheritance for target dialects that need it
20154        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
20155        let needs_inheritance = matches!(
20156            self.config.dialect,
20157            Some(DialectType::DuckDB)
20158                | Some(DialectType::Spark)
20159                | Some(DialectType::Databricks)
20160                | Some(DialectType::Hive)
20161                | Some(DialectType::Snowflake)
20162                | Some(DialectType::Presto)
20163                | Some(DialectType::Trino)
20164        );
20165        let propagated: Vec<Expression>;
20166        let expressions = if needs_inheritance && f.expressions.len() > 1 {
20167            propagated = Self::inherit_struct_field_names(&f.expressions);
20168            &propagated
20169        } else {
20170            &f.expressions
20171        };
20172
20173        // Check if elements should be split onto multiple lines (pretty + too wide)
20174        let should_split = if self.config.pretty && !expressions.is_empty() {
20175            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
20176            for expr in expressions {
20177                let mut temp_gen = Generator::with_arc_config(self.config.clone());
20178                Arc::make_mut(&mut temp_gen.config).pretty = false;
20179                temp_gen.generate_expression(expr)?;
20180                expr_strings.push(temp_gen.output);
20181            }
20182            self.too_wide(&expr_strings)
20183        } else {
20184            false
20185        };
20186
20187        if f.bracket_notation {
20188            // For Spark/Databricks, use ARRAY(...) with parens
20189            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
20190            // For others (DuckDB, Snowflake), use bare [...]
20191            let (open, close) = match self.config.dialect {
20192                None
20193                | Some(DialectType::Generic)
20194                | Some(DialectType::Spark)
20195                | Some(DialectType::Databricks)
20196                | Some(DialectType::Hive) => {
20197                    self.write_keyword("ARRAY");
20198                    ("(", ")")
20199                }
20200                Some(DialectType::Presto)
20201                | Some(DialectType::Trino)
20202                | Some(DialectType::PostgreSQL)
20203                | Some(DialectType::Redshift)
20204                | Some(DialectType::Materialize)
20205                | Some(DialectType::RisingWave)
20206                | Some(DialectType::CockroachDB) => {
20207                    self.write_keyword("ARRAY");
20208                    ("[", "]")
20209                }
20210                _ => ("[", "]"),
20211            };
20212            self.write(open);
20213            if should_split {
20214                self.write_newline();
20215                self.indent_level += 1;
20216                for (i, expr) in expressions.iter().enumerate() {
20217                    self.write_indent();
20218                    self.generate_expression(expr)?;
20219                    if i + 1 < expressions.len() {
20220                        self.write(",");
20221                    }
20222                    self.write_newline();
20223                }
20224                self.indent_level -= 1;
20225                self.write_indent();
20226            } else {
20227                for (i, expr) in expressions.iter().enumerate() {
20228                    if i > 0 {
20229                        self.write(", ");
20230                    }
20231                    self.generate_expression(expr)?;
20232                }
20233            }
20234            self.write(close);
20235        } else {
20236            // Use LIST keyword if that was the original syntax (DuckDB)
20237            if f.use_list_keyword {
20238                self.write_keyword("LIST");
20239            } else {
20240                self.write_keyword("ARRAY");
20241            }
20242            // For Spark/Hive, always use ARRAY(...) with parens
20243            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
20244            let has_subquery = expressions
20245                .iter()
20246                .any(|e| matches!(e, Expression::Select(_)));
20247            let (open, close) = if matches!(
20248                self.config.dialect,
20249                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
20250            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
20251                && has_subquery)
20252            {
20253                ("(", ")")
20254            } else {
20255                ("[", "]")
20256            };
20257            self.write(open);
20258            if should_split {
20259                self.write_newline();
20260                self.indent_level += 1;
20261                for (i, expr) in expressions.iter().enumerate() {
20262                    self.write_indent();
20263                    self.generate_expression(expr)?;
20264                    if i + 1 < expressions.len() {
20265                        self.write(",");
20266                    }
20267                    self.write_newline();
20268                }
20269                self.indent_level -= 1;
20270                self.write_indent();
20271            } else {
20272                for (i, expr) in expressions.iter().enumerate() {
20273                    if i > 0 {
20274                        self.write(", ");
20275                    }
20276                    self.generate_expression(expr)?;
20277                }
20278            }
20279            self.write(close);
20280        }
20281        Ok(())
20282    }
20283
20284    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
20285        self.write_keyword("ARRAY_SORT");
20286        self.write("(");
20287        self.generate_expression(&f.this)?;
20288        if let Some(ref comp) = f.comparator {
20289            self.write(", ");
20290            self.generate_expression(comp)?;
20291        }
20292        self.write(")");
20293        Ok(())
20294    }
20295
20296    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
20297        self.write_keyword(name);
20298        self.write("(");
20299        self.generate_expression(&f.this)?;
20300        self.write(", ");
20301        self.generate_expression(&f.separator)?;
20302        if let Some(ref null_rep) = f.null_replacement {
20303            self.write(", ");
20304            self.generate_expression(null_rep)?;
20305        }
20306        self.write(")");
20307        Ok(())
20308    }
20309
20310    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
20311        self.write_keyword("UNNEST");
20312        self.write("(");
20313        self.generate_expression(&f.this)?;
20314        for extra in &f.expressions {
20315            self.write(", ");
20316            self.generate_expression(extra)?;
20317        }
20318        self.write(")");
20319        if f.with_ordinality {
20320            self.write_space();
20321            if self.config.unnest_with_ordinality {
20322                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
20323                self.write_keyword("WITH ORDINALITY");
20324            } else if f.offset_alias.is_some() {
20325                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
20326                // Alias (if any) comes BEFORE WITH OFFSET
20327                if let Some(ref alias) = f.alias {
20328                    self.write_keyword("AS");
20329                    self.write_space();
20330                    self.generate_identifier(alias)?;
20331                    self.write_space();
20332                }
20333                self.write_keyword("WITH OFFSET");
20334                if let Some(ref offset_alias) = f.offset_alias {
20335                    self.write_space();
20336                    self.write_keyword("AS");
20337                    self.write_space();
20338                    self.generate_identifier(offset_alias)?;
20339                }
20340            } else {
20341                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
20342                self.write_keyword("WITH OFFSET");
20343                if f.alias.is_none() {
20344                    self.write(" AS offset");
20345                }
20346            }
20347        }
20348        if let Some(ref alias) = f.alias {
20349            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
20350            let should_add_alias = if !f.with_ordinality {
20351                true
20352            } else if self.config.unnest_with_ordinality {
20353                // Presto/Trino: alias comes after WITH ORDINALITY
20354                true
20355            } else if f.offset_alias.is_some() {
20356                // BigQuery expansion: alias already handled above
20357                false
20358            } else {
20359                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
20360                true
20361            };
20362            if should_add_alias {
20363                self.write_space();
20364                self.write_keyword("AS");
20365                self.write_space();
20366                self.generate_identifier(alias)?;
20367            }
20368        }
20369        Ok(())
20370    }
20371
20372    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
20373        self.write_keyword("FILTER");
20374        self.write("(");
20375        self.generate_expression(&f.this)?;
20376        self.write(", ");
20377        self.generate_expression(&f.filter)?;
20378        self.write(")");
20379        Ok(())
20380    }
20381
20382    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
20383        self.write_keyword("TRANSFORM");
20384        self.write("(");
20385        self.generate_expression(&f.this)?;
20386        self.write(", ");
20387        self.generate_expression(&f.transform)?;
20388        self.write(")");
20389        Ok(())
20390    }
20391
20392    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
20393        self.write_keyword(name);
20394        self.write("(");
20395        self.generate_expression(&f.start)?;
20396        self.write(", ");
20397        self.generate_expression(&f.stop)?;
20398        if let Some(ref step) = f.step {
20399            self.write(", ");
20400            self.generate_expression(step)?;
20401        }
20402        self.write(")");
20403        Ok(())
20404    }
20405
20406    // Struct function generators
20407
20408    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
20409        self.write_keyword("STRUCT");
20410        self.write("(");
20411        for (i, (name, expr)) in f.fields.iter().enumerate() {
20412            if i > 0 {
20413                self.write(", ");
20414            }
20415            if let Some(ref id) = name {
20416                self.generate_identifier(id)?;
20417                self.write(" ");
20418                self.write_keyword("AS");
20419                self.write(" ");
20420            }
20421            self.generate_expression(expr)?;
20422        }
20423        self.write(")");
20424        Ok(())
20425    }
20426
20427    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
20428    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
20429        // Extract named/unnamed fields from function args
20430        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
20431        let mut names: Vec<Option<String>> = Vec::new();
20432        let mut values: Vec<&Expression> = Vec::new();
20433        let mut all_named = true;
20434
20435        for arg in &func.args {
20436            match arg {
20437                Expression::Alias(a) => {
20438                    names.push(Some(a.alias.name.clone()));
20439                    values.push(&a.this);
20440                }
20441                _ => {
20442                    names.push(None);
20443                    values.push(arg);
20444                    all_named = false;
20445                }
20446            }
20447        }
20448
20449        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
20450            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
20451            self.write("{");
20452            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20453                if i > 0 {
20454                    self.write(", ");
20455                }
20456                if let Some(n) = name {
20457                    self.write("'");
20458                    self.write(n);
20459                    self.write("'");
20460                } else {
20461                    self.write("'_");
20462                    self.write(&i.to_string());
20463                    self.write("'");
20464                }
20465                self.write(": ");
20466                self.generate_expression(value)?;
20467            }
20468            self.write("}");
20469            return Ok(());
20470        }
20471
20472        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
20473            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
20474            self.write_keyword("OBJECT_CONSTRUCT");
20475            self.write("(");
20476            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20477                if i > 0 {
20478                    self.write(", ");
20479                }
20480                if let Some(n) = name {
20481                    self.write("'");
20482                    self.write(n);
20483                    self.write("'");
20484                } else {
20485                    self.write("'_");
20486                    self.write(&i.to_string());
20487                    self.write("'");
20488                }
20489                self.write(", ");
20490                self.generate_expression(value)?;
20491            }
20492            self.write(")");
20493            return Ok(());
20494        }
20495
20496        if matches!(
20497            self.config.dialect,
20498            Some(DialectType::Presto) | Some(DialectType::Trino)
20499        ) {
20500            if all_named && !names.is_empty() {
20501                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
20502                // Need to infer types from values
20503                self.write_keyword("CAST");
20504                self.write("(");
20505                self.write_keyword("ROW");
20506                self.write("(");
20507                for (i, value) in values.iter().enumerate() {
20508                    if i > 0 {
20509                        self.write(", ");
20510                    }
20511                    self.generate_expression(value)?;
20512                }
20513                self.write(")");
20514                self.write(" ");
20515                self.write_keyword("AS");
20516                self.write(" ");
20517                self.write_keyword("ROW");
20518                self.write("(");
20519                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
20520                    if i > 0 {
20521                        self.write(", ");
20522                    }
20523                    if let Some(n) = name {
20524                        self.write(n);
20525                    }
20526                    self.write(" ");
20527                    let type_str = Self::infer_sql_type_for_presto(value);
20528                    self.write_keyword(&type_str);
20529                }
20530                self.write(")");
20531                self.write(")");
20532            } else {
20533                // Unnamed: ROW(values...)
20534                self.write_keyword("ROW");
20535                self.write("(");
20536                for (i, value) in values.iter().enumerate() {
20537                    if i > 0 {
20538                        self.write(", ");
20539                    }
20540                    self.generate_expression(value)?;
20541                }
20542                self.write(")");
20543            }
20544            return Ok(());
20545        }
20546
20547        // Default: ROW(values...) for other dialects
20548        self.write_keyword("ROW");
20549        self.write("(");
20550        for (i, value) in values.iter().enumerate() {
20551            if i > 0 {
20552                self.write(", ");
20553            }
20554            self.generate_expression(value)?;
20555        }
20556        self.write(")");
20557        Ok(())
20558    }
20559
20560    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
20561    fn infer_sql_type_for_presto(expr: &Expression) -> String {
20562        match expr {
20563            Expression::Literal(lit)
20564                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
20565            {
20566                "VARCHAR".to_string()
20567            }
20568            Expression::Literal(lit)
20569                if matches!(lit.as_ref(), crate::expressions::Literal::Number(_)) =>
20570            {
20571                let crate::expressions::Literal::Number(n) = lit.as_ref() else {
20572                    unreachable!()
20573                };
20574                if n.contains('.') {
20575                    "DOUBLE".to_string()
20576                } else {
20577                    "INTEGER".to_string()
20578                }
20579            }
20580            Expression::Boolean(_) => "BOOLEAN".to_string(),
20581            Expression::Literal(lit)
20582                if matches!(lit.as_ref(), crate::expressions::Literal::Date(_)) =>
20583            {
20584                "DATE".to_string()
20585            }
20586            Expression::Literal(lit)
20587                if matches!(lit.as_ref(), crate::expressions::Literal::Timestamp(_)) =>
20588            {
20589                "TIMESTAMP".to_string()
20590            }
20591            Expression::Literal(lit)
20592                if matches!(lit.as_ref(), crate::expressions::Literal::Datetime(_)) =>
20593            {
20594                "TIMESTAMP".to_string()
20595            }
20596            Expression::Array(_) | Expression::ArrayFunc(_) => {
20597                // Try to infer element type from first element
20598                "ARRAY(VARCHAR)".to_string()
20599            }
20600            // For nested structs - generate a nested ROW type by inspecting fields
20601            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
20602            Expression::Function(f) => {
20603                if f.name.eq_ignore_ascii_case("STRUCT") {
20604                    "ROW".to_string()
20605                } else if f.name.eq_ignore_ascii_case("CURRENT_DATE") {
20606                    "DATE".to_string()
20607                } else if f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
20608                    || f.name.eq_ignore_ascii_case("NOW")
20609                {
20610                    "TIMESTAMP".to_string()
20611                } else {
20612                    "VARCHAR".to_string()
20613                }
20614            }
20615            _ => "VARCHAR".to_string(),
20616        }
20617    }
20618
20619    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
20620        // DuckDB uses STRUCT_EXTRACT function syntax
20621        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
20622            self.write_keyword("STRUCT_EXTRACT");
20623            self.write("(");
20624            self.generate_expression(&f.this)?;
20625            self.write(", ");
20626            // Output field name as string literal
20627            self.write("'");
20628            self.write(&f.field.name);
20629            self.write("'");
20630            self.write(")");
20631            return Ok(());
20632        }
20633        self.generate_expression(&f.this)?;
20634        self.write(".");
20635        self.generate_identifier(&f.field)
20636    }
20637
20638    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
20639        self.write_keyword("NAMED_STRUCT");
20640        self.write("(");
20641        for (i, (name, value)) in f.pairs.iter().enumerate() {
20642            if i > 0 {
20643                self.write(", ");
20644            }
20645            self.generate_expression(name)?;
20646            self.write(", ");
20647            self.generate_expression(value)?;
20648        }
20649        self.write(")");
20650        Ok(())
20651    }
20652
20653    // Map function generators
20654
20655    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
20656        if f.curly_brace_syntax {
20657            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
20658            if f.with_map_keyword {
20659                self.write_keyword("MAP");
20660                self.write(" ");
20661            }
20662            self.write("{");
20663            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
20664                if i > 0 {
20665                    self.write(", ");
20666                }
20667                self.generate_expression(key)?;
20668                self.write(": ");
20669                self.generate_expression(val)?;
20670            }
20671            self.write("}");
20672        } else {
20673            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
20674            self.write_keyword("MAP");
20675            self.write("(");
20676            self.write_keyword("ARRAY");
20677            self.write("[");
20678            for (i, key) in f.keys.iter().enumerate() {
20679                if i > 0 {
20680                    self.write(", ");
20681                }
20682                self.generate_expression(key)?;
20683            }
20684            self.write("], ");
20685            self.write_keyword("ARRAY");
20686            self.write("[");
20687            for (i, val) in f.values.iter().enumerate() {
20688                if i > 0 {
20689                    self.write(", ");
20690                }
20691                self.generate_expression(val)?;
20692            }
20693            self.write("])");
20694        }
20695        Ok(())
20696    }
20697
20698    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
20699        self.write_keyword(name);
20700        self.write("(");
20701        self.generate_expression(&f.this)?;
20702        self.write(", ");
20703        self.generate_expression(&f.transform)?;
20704        self.write(")");
20705        Ok(())
20706    }
20707
20708    // JSON function generators
20709
20710    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
20711        use crate::dialects::DialectType;
20712
20713        // Check if we should use arrow syntax (-> or ->>)
20714        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
20715
20716        if use_arrow {
20717            // Output arrow syntax: expr -> path or expr ->> path
20718            self.generate_expression(&f.this)?;
20719            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
20720                self.write(" ->> ");
20721            } else {
20722                self.write(" -> ");
20723            }
20724            self.generate_expression(&f.path)?;
20725            return Ok(());
20726        }
20727
20728        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
20729        if f.hash_arrow_syntax
20730            && matches!(
20731                self.config.dialect,
20732                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
20733            )
20734        {
20735            self.generate_expression(&f.this)?;
20736            self.write(" #>> ");
20737            self.generate_expression(&f.path)?;
20738            return Ok(());
20739        }
20740
20741        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
20742        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
20743        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
20744            match name {
20745                "JSON_EXTRACT_SCALAR"
20746                | "JSON_EXTRACT_PATH_TEXT"
20747                | "JSON_EXTRACT"
20748                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
20749                _ => name,
20750            }
20751        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
20752            match name {
20753                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
20754                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
20755                _ => name,
20756            }
20757        } else {
20758            name
20759        };
20760
20761        self.write_keyword(func_name);
20762        self.write("(");
20763        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
20764        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
20765            if let Expression::Cast(ref cast) = f.this {
20766                if matches!(cast.to, crate::expressions::DataType::Json) {
20767                    self.generate_expression(&cast.this)?;
20768                } else {
20769                    self.generate_expression(&f.this)?;
20770                }
20771            } else {
20772                self.generate_expression(&f.this)?;
20773            }
20774        } else {
20775            self.generate_expression(&f.this)?;
20776        }
20777        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
20778        // decompose JSON path into separate string arguments
20779        if matches!(
20780            self.config.dialect,
20781            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
20782        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
20783        {
20784            if let Expression::Literal(ref lit) = f.path {
20785                if let Literal::String(ref s) = lit.as_ref() {
20786                    let parts = Self::decompose_json_path(s);
20787                    for part in &parts {
20788                        self.write(", '");
20789                        self.write(part);
20790                        self.write("'");
20791                    }
20792                }
20793            } else {
20794                self.write(", ");
20795                self.generate_expression(&f.path)?;
20796            }
20797        } else {
20798            self.write(", ");
20799            self.generate_expression(&f.path)?;
20800        }
20801
20802        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
20803        // These go BEFORE the closing parenthesis
20804        if let Some(ref wrapper) = f.wrapper_option {
20805            self.write_space();
20806            self.write_keyword(wrapper);
20807        }
20808        if let Some(ref quotes) = f.quotes_option {
20809            self.write_space();
20810            self.write_keyword(quotes);
20811            if f.on_scalar_string {
20812                self.write_space();
20813                self.write_keyword("ON SCALAR STRING");
20814            }
20815        }
20816        if let Some(ref on_err) = f.on_error {
20817            self.write_space();
20818            self.write_keyword(on_err);
20819        }
20820        if let Some(ref ret_type) = f.returning {
20821            self.write_space();
20822            self.write_keyword("RETURNING");
20823            self.write_space();
20824            self.generate_data_type(ret_type)?;
20825        }
20826
20827        self.write(")");
20828        Ok(())
20829    }
20830
20831    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
20832    fn dialect_supports_json_arrow(&self) -> bool {
20833        use crate::dialects::DialectType;
20834        match self.config.dialect {
20835            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
20836            Some(DialectType::PostgreSQL) => true,
20837            Some(DialectType::MySQL) => true,
20838            Some(DialectType::DuckDB) => true,
20839            Some(DialectType::CockroachDB) => true,
20840            Some(DialectType::StarRocks) => true,
20841            Some(DialectType::SQLite) => true,
20842            // Other dialects use function syntax
20843            _ => false,
20844        }
20845    }
20846
20847    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
20848        use crate::dialects::DialectType;
20849
20850        // PostgreSQL uses #> operator for JSONB path extraction
20851        if matches!(
20852            self.config.dialect,
20853            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
20854        ) && name == "JSON_EXTRACT_PATH"
20855        {
20856            self.generate_expression(&f.this)?;
20857            self.write(" #> ");
20858            if f.paths.len() == 1 {
20859                self.generate_expression(&f.paths[0])?;
20860            } else {
20861                // Multiple paths: ARRAY[path1, path2, ...]
20862                self.write_keyword("ARRAY");
20863                self.write("[");
20864                for (i, path) in f.paths.iter().enumerate() {
20865                    if i > 0 {
20866                        self.write(", ");
20867                    }
20868                    self.generate_expression(path)?;
20869                }
20870                self.write("]");
20871            }
20872            return Ok(());
20873        }
20874
20875        self.write_keyword(name);
20876        self.write("(");
20877        self.generate_expression(&f.this)?;
20878        for path in &f.paths {
20879            self.write(", ");
20880            self.generate_expression(path)?;
20881        }
20882        self.write(")");
20883        Ok(())
20884    }
20885
20886    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
20887        use crate::dialects::DialectType;
20888
20889        self.write_keyword("JSON_OBJECT");
20890        self.write("(");
20891        if f.star {
20892            self.write("*");
20893        } else {
20894            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
20895            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
20896            // Also respect the json_key_value_pair_sep config
20897            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
20898                || matches!(
20899                    self.config.dialect,
20900                    Some(DialectType::BigQuery)
20901                        | Some(DialectType::MySQL)
20902                        | Some(DialectType::SQLite)
20903                );
20904
20905            for (i, (key, value)) in f.pairs.iter().enumerate() {
20906                if i > 0 {
20907                    self.write(", ");
20908                }
20909                self.generate_expression(key)?;
20910                if use_comma_syntax {
20911                    self.write(", ");
20912                } else {
20913                    self.write(": ");
20914                }
20915                self.generate_expression(value)?;
20916            }
20917        }
20918        if let Some(null_handling) = f.null_handling {
20919            self.write_space();
20920            match null_handling {
20921                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
20922                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
20923            }
20924        }
20925        if f.with_unique_keys {
20926            self.write_space();
20927            self.write_keyword("WITH UNIQUE KEYS");
20928        }
20929        if let Some(ref ret_type) = f.returning_type {
20930            self.write_space();
20931            self.write_keyword("RETURNING");
20932            self.write_space();
20933            self.generate_data_type(ret_type)?;
20934            if f.format_json {
20935                self.write_space();
20936                self.write_keyword("FORMAT JSON");
20937            }
20938            if let Some(ref enc) = f.encoding {
20939                self.write_space();
20940                self.write_keyword("ENCODING");
20941                self.write_space();
20942                self.write(enc);
20943            }
20944        }
20945        self.write(")");
20946        Ok(())
20947    }
20948
20949    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
20950        self.write_keyword(name);
20951        self.write("(");
20952        self.generate_expression(&f.this)?;
20953        for (path, value) in &f.path_values {
20954            self.write(", ");
20955            self.generate_expression(path)?;
20956            self.write(", ");
20957            self.generate_expression(value)?;
20958        }
20959        self.write(")");
20960        Ok(())
20961    }
20962
20963    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
20964        self.write_keyword("JSON_ARRAYAGG");
20965        self.write("(");
20966        self.generate_expression(&f.this)?;
20967        if let Some(ref order_by) = f.order_by {
20968            self.write_space();
20969            self.write_keyword("ORDER BY");
20970            self.write_space();
20971            for (i, ord) in order_by.iter().enumerate() {
20972                if i > 0 {
20973                    self.write(", ");
20974                }
20975                self.generate_ordered(ord)?;
20976            }
20977        }
20978        if let Some(null_handling) = f.null_handling {
20979            self.write_space();
20980            match null_handling {
20981                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
20982                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
20983            }
20984        }
20985        self.write(")");
20986        if let Some(ref filter) = f.filter {
20987            self.write_space();
20988            self.write_keyword("FILTER");
20989            self.write("(");
20990            self.write_keyword("WHERE");
20991            self.write_space();
20992            self.generate_expression(filter)?;
20993            self.write(")");
20994        }
20995        Ok(())
20996    }
20997
20998    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
20999        self.write_keyword("JSON_OBJECTAGG");
21000        self.write("(");
21001        self.generate_expression(&f.key)?;
21002        self.write(": ");
21003        self.generate_expression(&f.value)?;
21004        if let Some(null_handling) = f.null_handling {
21005            self.write_space();
21006            match null_handling {
21007                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21008                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21009            }
21010        }
21011        self.write(")");
21012        if let Some(ref filter) = f.filter {
21013            self.write_space();
21014            self.write_keyword("FILTER");
21015            self.write("(");
21016            self.write_keyword("WHERE");
21017            self.write_space();
21018            self.generate_expression(filter)?;
21019            self.write(")");
21020        }
21021        Ok(())
21022    }
21023
21024    // Type casting/conversion generators
21025
21026    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
21027        use crate::dialects::DialectType;
21028
21029        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
21030        if self.config.dialect == Some(DialectType::Redshift) {
21031            self.write_keyword("CAST");
21032            self.write("(");
21033            self.generate_expression(&f.this)?;
21034            self.write_space();
21035            self.write_keyword("AS");
21036            self.write_space();
21037            self.generate_data_type(&f.to)?;
21038            self.write(")");
21039            return Ok(());
21040        }
21041
21042        self.write_keyword("CONVERT");
21043        self.write("(");
21044        self.generate_data_type(&f.to)?;
21045        self.write(", ");
21046        self.generate_expression(&f.this)?;
21047        if let Some(ref style) = f.style {
21048            self.write(", ");
21049            self.generate_expression(style)?;
21050        }
21051        self.write(")");
21052        Ok(())
21053    }
21054
21055    // Additional expression generators
21056
21057    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
21058        if f.colon {
21059            // DuckDB syntax: LAMBDA x : expr
21060            self.write_keyword("LAMBDA");
21061            self.write_space();
21062            for (i, param) in f.parameters.iter().enumerate() {
21063                if i > 0 {
21064                    self.write(", ");
21065                }
21066                self.generate_identifier(param)?;
21067            }
21068            self.write(" : ");
21069        } else {
21070            // Standard syntax: x -> expr or (x, y) -> expr
21071            if f.parameters.len() == 1 {
21072                self.generate_identifier(&f.parameters[0])?;
21073            } else {
21074                self.write("(");
21075                for (i, param) in f.parameters.iter().enumerate() {
21076                    if i > 0 {
21077                        self.write(", ");
21078                    }
21079                    self.generate_identifier(param)?;
21080                }
21081                self.write(")");
21082            }
21083            self.write(" -> ");
21084        }
21085        self.generate_expression(&f.body)
21086    }
21087
21088    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
21089        self.generate_identifier(&f.name)?;
21090        match f.separator {
21091            NamedArgSeparator::DArrow => self.write(" => "),
21092            NamedArgSeparator::ColonEq => self.write(" := "),
21093            NamedArgSeparator::Eq => self.write(" = "),
21094        }
21095        self.generate_expression(&f.value)
21096    }
21097
21098    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
21099        self.write_keyword(&f.prefix);
21100        self.write(" ");
21101        self.generate_expression(&f.this)
21102    }
21103
21104    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
21105        match f.style {
21106            ParameterStyle::Question => self.write("?"),
21107            ParameterStyle::Dollar => {
21108                self.write("$");
21109                if let Some(idx) = f.index {
21110                    self.write(&idx.to_string());
21111                } else if let Some(ref name) = f.name {
21112                    // Session variable like $x or $query_id
21113                    self.write(name);
21114                }
21115            }
21116            ParameterStyle::DollarBrace => {
21117                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
21118                self.write("${");
21119                if let Some(ref name) = f.name {
21120                    self.write(name);
21121                }
21122                if let Some(ref expr) = f.expression {
21123                    self.write(":");
21124                    self.write(expr);
21125                }
21126                self.write("}");
21127            }
21128            ParameterStyle::Colon => {
21129                self.write(":");
21130                if let Some(idx) = f.index {
21131                    self.write(&idx.to_string());
21132                } else if let Some(ref name) = f.name {
21133                    self.write(name);
21134                }
21135            }
21136            ParameterStyle::At => {
21137                self.write("@");
21138                if let Some(ref name) = f.name {
21139                    if f.string_quoted {
21140                        self.write("'");
21141                        self.write(name);
21142                        self.write("'");
21143                    } else if f.quoted {
21144                        self.write("\"");
21145                        self.write(name);
21146                        self.write("\"");
21147                    } else {
21148                        self.write(name);
21149                    }
21150                }
21151            }
21152            ParameterStyle::DoubleAt => {
21153                self.write("@@");
21154                if let Some(ref name) = f.name {
21155                    self.write(name);
21156                }
21157            }
21158            ParameterStyle::DoubleDollar => {
21159                self.write("$$");
21160                if let Some(ref name) = f.name {
21161                    self.write(name);
21162                }
21163            }
21164            ParameterStyle::Percent => {
21165                if let Some(ref name) = f.name {
21166                    // %(name)s format
21167                    self.write("%(");
21168                    self.write(name);
21169                    self.write(")s");
21170                } else {
21171                    // %s format
21172                    self.write("%s");
21173                }
21174            }
21175            ParameterStyle::Brace => {
21176                // Spark/Databricks widget template variable: {name}
21177                // ClickHouse query parameter may include kind: {name: Type}
21178                self.write("{");
21179                if let Some(ref name) = f.name {
21180                    self.write(name);
21181                }
21182                if let Some(ref expr) = f.expression {
21183                    self.write(": ");
21184                    self.write(expr);
21185                }
21186                self.write("}");
21187            }
21188        }
21189        Ok(())
21190    }
21191
21192    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
21193        self.write("?");
21194        if let Some(idx) = f.index {
21195            self.write(&idx.to_string());
21196        }
21197        Ok(())
21198    }
21199
21200    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
21201        if f.is_block {
21202            self.write("/*");
21203            self.write(&f.text);
21204            self.write("*/");
21205        } else {
21206            self.write("--");
21207            self.write(&f.text);
21208        }
21209        Ok(())
21210    }
21211
21212    // Additional predicate generators
21213
21214    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
21215        self.generate_expression(&f.this)?;
21216        if f.not {
21217            self.write_space();
21218            self.write_keyword("NOT");
21219        }
21220        self.write_space();
21221        self.write_keyword("SIMILAR TO");
21222        self.write_space();
21223        self.generate_expression(&f.pattern)?;
21224        if let Some(ref escape) = f.escape {
21225            self.write_space();
21226            self.write_keyword("ESCAPE");
21227            self.write_space();
21228            self.generate_expression(escape)?;
21229        }
21230        Ok(())
21231    }
21232
21233    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
21234        self.generate_expression(&f.this)?;
21235        self.write_space();
21236        // Output comparison operator if present
21237        if let Some(op) = &f.op {
21238            match op {
21239                QuantifiedOp::Eq => self.write("="),
21240                QuantifiedOp::Neq => self.write("<>"),
21241                QuantifiedOp::Lt => self.write("<"),
21242                QuantifiedOp::Lte => self.write("<="),
21243                QuantifiedOp::Gt => self.write(">"),
21244                QuantifiedOp::Gte => self.write(">="),
21245            }
21246            self.write_space();
21247        }
21248        self.write_keyword(name);
21249
21250        // If the child is a Subquery, it provides its own parens — output with space
21251        if matches!(&f.subquery, Expression::Subquery(_)) {
21252            self.write_space();
21253            self.generate_expression(&f.subquery)?;
21254        } else {
21255            self.write("(");
21256
21257            let is_statement = matches!(
21258                &f.subquery,
21259                Expression::Select(_)
21260                    | Expression::Union(_)
21261                    | Expression::Intersect(_)
21262                    | Expression::Except(_)
21263            );
21264
21265            if self.config.pretty && is_statement {
21266                self.write_newline();
21267                self.indent_level += 1;
21268                self.write_indent();
21269            }
21270            self.generate_expression(&f.subquery)?;
21271            if self.config.pretty && is_statement {
21272                self.write_newline();
21273                self.indent_level -= 1;
21274                self.write_indent();
21275            }
21276            self.write(")");
21277        }
21278        Ok(())
21279    }
21280
21281    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
21282        // Check if this is a simple binary form (this OVERLAPS expression)
21283        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
21284            self.generate_expression(this)?;
21285            self.write_space();
21286            self.write_keyword("OVERLAPS");
21287            self.write_space();
21288            self.generate_expression(expr)?;
21289        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
21290            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
21291        {
21292            // Full ANSI form: (a, b) OVERLAPS (c, d)
21293            self.write("(");
21294            self.generate_expression(ls)?;
21295            self.write(", ");
21296            self.generate_expression(le)?;
21297            self.write(")");
21298            self.write_space();
21299            self.write_keyword("OVERLAPS");
21300            self.write_space();
21301            self.write("(");
21302            self.generate_expression(rs)?;
21303            self.write(", ");
21304            self.generate_expression(re)?;
21305            self.write(")");
21306        }
21307        Ok(())
21308    }
21309
21310    // Type conversion generators
21311
21312    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
21313        use crate::dialects::DialectType;
21314
21315        // SingleStore uses !:> syntax for try cast
21316        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
21317            self.generate_expression(&cast.this)?;
21318            self.write(" !:> ");
21319            self.generate_data_type(&cast.to)?;
21320            return Ok(());
21321        }
21322
21323        // Teradata uses TRYCAST (no underscore)
21324        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
21325            self.write_keyword("TRYCAST");
21326            self.write("(");
21327            self.generate_expression(&cast.this)?;
21328            self.write_space();
21329            self.write_keyword("AS");
21330            self.write_space();
21331            self.generate_data_type(&cast.to)?;
21332            self.write(")");
21333            return Ok(());
21334        }
21335
21336        // Dialects without TRY_CAST: generate as regular CAST
21337        let keyword = if matches!(
21338            self.config.dialect,
21339            Some(DialectType::Hive)
21340                | Some(DialectType::MySQL)
21341                | Some(DialectType::SQLite)
21342                | Some(DialectType::Oracle)
21343                | Some(DialectType::ClickHouse)
21344                | Some(DialectType::Redshift)
21345                | Some(DialectType::PostgreSQL)
21346                | Some(DialectType::StarRocks)
21347                | Some(DialectType::Doris)
21348        ) {
21349            "CAST"
21350        } else {
21351            "TRY_CAST"
21352        };
21353
21354        self.write_keyword(keyword);
21355        self.write("(");
21356        self.generate_expression(&cast.this)?;
21357        self.write_space();
21358        self.write_keyword("AS");
21359        self.write_space();
21360        self.generate_data_type(&cast.to)?;
21361
21362        // Output FORMAT clause if present
21363        if let Some(format) = &cast.format {
21364            self.write_space();
21365            self.write_keyword("FORMAT");
21366            self.write_space();
21367            self.generate_expression(format)?;
21368        }
21369
21370        self.write(")");
21371        Ok(())
21372    }
21373
21374    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
21375        self.write_keyword("SAFE_CAST");
21376        self.write("(");
21377        self.generate_expression(&cast.this)?;
21378        self.write_space();
21379        self.write_keyword("AS");
21380        self.write_space();
21381        self.generate_data_type(&cast.to)?;
21382
21383        // Output FORMAT clause if present
21384        if let Some(format) = &cast.format {
21385            self.write_space();
21386            self.write_keyword("FORMAT");
21387            self.write_space();
21388            self.generate_expression(format)?;
21389        }
21390
21391        self.write(")");
21392        Ok(())
21393    }
21394
21395    // Array/struct/map access generators
21396
21397    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
21398        // Wrap the base expression in parentheses when it uses arrow syntax (->)
21399        // which has lower precedence than bracket subscript ([]).
21400        // E.g., (t.v -> '$.a')[s.x] instead of t.v -> '$.a'[s.x]
21401        let needs_parens = matches!(&s.this, Expression::JsonExtract(ref f) if f.arrow_syntax);
21402        if needs_parens {
21403            self.write("(");
21404        }
21405        self.generate_expression(&s.this)?;
21406        if needs_parens {
21407            self.write(")");
21408        }
21409        self.write("[");
21410        self.generate_expression(&s.index)?;
21411        self.write("]");
21412        Ok(())
21413    }
21414
21415    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
21416        self.generate_expression(&d.this)?;
21417        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
21418        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
21419        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
21420            && matches!(
21421                &d.this,
21422                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
21423            );
21424        if use_colon {
21425            self.write(":");
21426        } else {
21427            self.write(".");
21428        }
21429        self.generate_identifier(&d.field)
21430    }
21431
21432    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
21433        self.generate_expression(&m.this)?;
21434        self.write(".");
21435        // Method names after a dot should not be quoted based on reserved keywords
21436        // Only quote if explicitly marked as quoted in the AST
21437        if m.method.quoted {
21438            let q = self.config.identifier_quote;
21439            self.write(&format!("{}{}{}", q, m.method.name, q));
21440        } else {
21441            self.write(&m.method.name);
21442        }
21443        self.write("(");
21444        for (i, arg) in m.args.iter().enumerate() {
21445            if i > 0 {
21446                self.write(", ");
21447            }
21448            self.generate_expression(arg)?;
21449        }
21450        self.write(")");
21451        Ok(())
21452    }
21453
21454    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
21455        // Check if we need to wrap the inner expression in parentheses
21456        // JSON arrow expressions have lower precedence than array subscript
21457        let needs_parens = matches!(
21458            &s.this,
21459            Expression::JsonExtract(f) if f.arrow_syntax
21460        ) || matches!(
21461            &s.this,
21462            Expression::JsonExtractScalar(f) if f.arrow_syntax
21463        );
21464
21465        if needs_parens {
21466            self.write("(");
21467        }
21468        self.generate_expression(&s.this)?;
21469        if needs_parens {
21470            self.write(")");
21471        }
21472        self.write("[");
21473        if let Some(start) = &s.start {
21474            self.generate_expression(start)?;
21475        }
21476        self.write(":");
21477        if let Some(end) = &s.end {
21478            self.generate_expression(end)?;
21479        }
21480        self.write("]");
21481        Ok(())
21482    }
21483
21484    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
21485        // Generate left expression, but skip trailing comments if they're already in left_comments
21486        // to avoid duplication (comments are captured as both expr.trailing_comments
21487        // and BinaryOp.left_comments during parsing)
21488        match &op.left {
21489            Expression::Column(col) => {
21490                // Generate column with trailing comments but skip them if they're
21491                // already captured in BinaryOp.left_comments to avoid duplication
21492                if let Some(table) = &col.table {
21493                    self.generate_identifier(table)?;
21494                    self.write(".");
21495                }
21496                self.generate_identifier(&col.name)?;
21497                // Oracle-style join marker (+)
21498                if col.join_mark && self.config.supports_column_join_marks {
21499                    self.write(" (+)");
21500                }
21501                // Output column trailing comments if they're not already in left_comments
21502                if op.left_comments.is_empty() {
21503                    for comment in &col.trailing_comments {
21504                        self.write_space();
21505                        self.write_formatted_comment(comment);
21506                    }
21507                }
21508            }
21509            Expression::Add(inner_op)
21510            | Expression::Sub(inner_op)
21511            | Expression::Mul(inner_op)
21512            | Expression::Div(inner_op)
21513            | Expression::Concat(inner_op) => {
21514                // Generate binary op without its trailing comments
21515                self.generate_binary_op_no_trailing(inner_op, match &op.left {
21516                    Expression::Add(_) => "+",
21517                    Expression::Sub(_) => "-",
21518                    Expression::Mul(_) => "*",
21519                    Expression::Div(_) => "/",
21520                    Expression::Concat(_) => "||",
21521                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
21522                })?;
21523            }
21524            _ => {
21525                self.generate_expression(&op.left)?;
21526            }
21527        }
21528        // Output comments after left operand
21529        for comment in &op.left_comments {
21530            self.write_space();
21531            self.write_formatted_comment(comment);
21532        }
21533        if self.config.pretty
21534            && matches!(self.config.dialect, Some(DialectType::Snowflake))
21535            && (operator == "AND" || operator == "OR")
21536        {
21537            self.write_newline();
21538            self.write_indent();
21539            self.write_keyword(operator);
21540        } else {
21541            self.write_space();
21542            if operator.chars().all(|c| c.is_alphabetic()) {
21543                self.write_keyword(operator);
21544            } else {
21545                self.write(operator);
21546            }
21547        }
21548        // Output comments after operator (before right operand)
21549        for comment in &op.operator_comments {
21550            self.write_space();
21551            self.write_formatted_comment(comment);
21552        }
21553        self.write_space();
21554        self.generate_expression(&op.right)?;
21555        // Output trailing comments after right operand
21556        for comment in &op.trailing_comments {
21557            self.write_space();
21558            self.write_formatted_comment(comment);
21559        }
21560        Ok(())
21561    }
21562
21563    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
21564        let keyword = connector.keyword();
21565        let Some(terms) = self.flatten_connector_terms(op, connector) else {
21566            return self.generate_binary_op(op, keyword);
21567        };
21568
21569        self.generate_expression(terms[0])?;
21570        for term in terms.iter().skip(1) {
21571            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
21572                self.write_newline();
21573                self.write_indent();
21574                self.write_keyword(keyword);
21575            } else {
21576                self.write_space();
21577                self.write_keyword(keyword);
21578            }
21579            self.write_space();
21580            self.generate_expression(term)?;
21581        }
21582
21583        Ok(())
21584    }
21585
21586    fn flatten_connector_terms<'a>(
21587        &self,
21588        root: &'a BinaryOp,
21589        connector: ConnectorOperator,
21590    ) -> Option<Vec<&'a Expression>> {
21591        if !root.left_comments.is_empty()
21592            || !root.operator_comments.is_empty()
21593            || !root.trailing_comments.is_empty()
21594        {
21595            return None;
21596        }
21597
21598        let mut terms = Vec::new();
21599        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
21600
21601        while let Some(expr) = stack.pop() {
21602            match (connector, expr) {
21603                (ConnectorOperator::And, Expression::And(inner))
21604                    if inner.left_comments.is_empty()
21605                        && inner.operator_comments.is_empty()
21606                        && inner.trailing_comments.is_empty() =>
21607                {
21608                    stack.push(&inner.right);
21609                    stack.push(&inner.left);
21610                }
21611                (ConnectorOperator::Or, Expression::Or(inner))
21612                    if inner.left_comments.is_empty()
21613                        && inner.operator_comments.is_empty()
21614                        && inner.trailing_comments.is_empty() =>
21615                {
21616                    stack.push(&inner.right);
21617                    stack.push(&inner.left);
21618                }
21619                _ => terms.push(expr),
21620            }
21621        }
21622
21623        if terms.len() > 1 {
21624            Some(terms)
21625        } else {
21626            None
21627        }
21628    }
21629
21630    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
21631    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
21632        self.generate_expression(&op.left)?;
21633        self.write_space();
21634        // Drill backtick-quotes ILIKE
21635        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
21636            self.write("`ILIKE`");
21637        } else {
21638            self.write_keyword(operator);
21639        }
21640        if let Some(quantifier) = &op.quantifier {
21641            self.write_space();
21642            self.write_keyword(quantifier);
21643            // Match Python sqlglot behavior:
21644            // ANY + Paren (single value): no space → ILIKE ANY('%a%')
21645            // ANY + Tuple (multiple values): space → LIKE ANY ('a', 'b')
21646            // ALL + anything: always space → LIKE ALL ('%a%'), LIKE ALL ('a', 'b')
21647            let is_any =
21648                quantifier.eq_ignore_ascii_case("ANY") || quantifier.eq_ignore_ascii_case("SOME");
21649            if !(is_any && matches!(&op.right, Expression::Paren(_))) {
21650                self.write_space();
21651            }
21652        } else {
21653            self.write_space();
21654        }
21655        self.generate_expression(&op.right)?;
21656        if let Some(escape) = &op.escape {
21657            self.write_space();
21658            self.write_keyword("ESCAPE");
21659            self.write_space();
21660            self.generate_expression(escape)?;
21661        }
21662        Ok(())
21663    }
21664
21665    /// Generate null-safe equality
21666    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
21667    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
21668        use crate::dialects::DialectType;
21669        self.generate_expression(&op.left)?;
21670        self.write_space();
21671        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
21672            self.write("<=>");
21673        } else {
21674            self.write_keyword("IS NOT DISTINCT FROM");
21675        }
21676        self.write_space();
21677        self.generate_expression(&op.right)?;
21678        Ok(())
21679    }
21680
21681    /// Generate IS DISTINCT FROM (null-safe inequality)
21682    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
21683        self.generate_expression(&op.left)?;
21684        self.write_space();
21685        self.write_keyword("IS DISTINCT FROM");
21686        self.write_space();
21687        self.generate_expression(&op.right)?;
21688        Ok(())
21689    }
21690
21691    /// Generate binary op without trailing comments (used when nested inside another binary op)
21692    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
21693        // Generate left expression, but skip trailing comments
21694        match &op.left {
21695            Expression::Column(col) => {
21696                if let Some(table) = &col.table {
21697                    self.generate_identifier(table)?;
21698                    self.write(".");
21699                }
21700                self.generate_identifier(&col.name)?;
21701                // Oracle-style join marker (+)
21702                if col.join_mark && self.config.supports_column_join_marks {
21703                    self.write(" (+)");
21704                }
21705            }
21706            Expression::Add(inner_op)
21707            | Expression::Sub(inner_op)
21708            | Expression::Mul(inner_op)
21709            | Expression::Div(inner_op)
21710            | Expression::Concat(inner_op) => {
21711                self.generate_binary_op_no_trailing(inner_op, match &op.left {
21712                    Expression::Add(_) => "+",
21713                    Expression::Sub(_) => "-",
21714                    Expression::Mul(_) => "*",
21715                    Expression::Div(_) => "/",
21716                    Expression::Concat(_) => "||",
21717                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
21718                })?;
21719            }
21720            _ => {
21721                self.generate_expression(&op.left)?;
21722            }
21723        }
21724        // Output left_comments
21725        for comment in &op.left_comments {
21726            self.write_space();
21727            self.write_formatted_comment(comment);
21728        }
21729        self.write_space();
21730        if operator.chars().all(|c| c.is_alphabetic()) {
21731            self.write_keyword(operator);
21732        } else {
21733            self.write(operator);
21734        }
21735        // Output operator_comments
21736        for comment in &op.operator_comments {
21737            self.write_space();
21738            self.write_formatted_comment(comment);
21739        }
21740        self.write_space();
21741        // Generate right expression, but skip trailing comments if it's a Column
21742        // (the parent's left_comments will output them)
21743        match &op.right {
21744            Expression::Column(col) => {
21745                if let Some(table) = &col.table {
21746                    self.generate_identifier(table)?;
21747                    self.write(".");
21748                }
21749                self.generate_identifier(&col.name)?;
21750                // Oracle-style join marker (+)
21751                if col.join_mark && self.config.supports_column_join_marks {
21752                    self.write(" (+)");
21753                }
21754            }
21755            _ => {
21756                self.generate_expression(&op.right)?;
21757            }
21758        }
21759        // Skip trailing_comments - parent will handle them via its left_comments
21760        Ok(())
21761    }
21762
21763    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
21764        if operator.chars().all(|c| c.is_alphabetic()) {
21765            self.write_keyword(operator);
21766            self.write_space();
21767        } else {
21768            self.write(operator);
21769            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
21770            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
21771                self.write_space();
21772            }
21773        }
21774        self.generate_expression(&op.this)
21775    }
21776
21777    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
21778        // Generic mode supports two styles for negated IN:
21779        // - Prefix: NOT a IN (...)
21780        // - Infix:  a NOT IN (...)
21781        let is_generic =
21782            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
21783        let use_prefix_not =
21784            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
21785        if use_prefix_not {
21786            self.write_keyword("NOT");
21787            self.write_space();
21788        }
21789        self.generate_expression(&in_expr.this)?;
21790        if in_expr.global {
21791            self.write_space();
21792            self.write_keyword("GLOBAL");
21793        }
21794        if in_expr.not && !use_prefix_not {
21795            self.write_space();
21796            self.write_keyword("NOT");
21797        }
21798        self.write_space();
21799        self.write_keyword("IN");
21800
21801        // BigQuery: IN UNNEST(expr)
21802        if let Some(unnest_expr) = &in_expr.unnest {
21803            self.write_space();
21804            self.write_keyword("UNNEST");
21805            self.write("(");
21806            self.generate_expression(unnest_expr)?;
21807            self.write(")");
21808            return Ok(());
21809        }
21810
21811        if let Some(query) = &in_expr.query {
21812            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
21813            // vs a subquery (col IN (SELECT ...))
21814            let is_bare = in_expr.expressions.is_empty()
21815                && !matches!(
21816                    query,
21817                    Expression::Select(_)
21818                        | Expression::Union(_)
21819                        | Expression::Intersect(_)
21820                        | Expression::Except(_)
21821                        | Expression::Subquery(_)
21822                );
21823            if is_bare {
21824                // Bare identifier: no parentheses
21825                self.write_space();
21826                self.generate_expression(query)?;
21827            } else {
21828                // Subquery: with parentheses
21829                self.write(" (");
21830                let is_statement = matches!(
21831                    query,
21832                    Expression::Select(_)
21833                        | Expression::Union(_)
21834                        | Expression::Intersect(_)
21835                        | Expression::Except(_)
21836                        | Expression::Subquery(_)
21837                );
21838                if self.config.pretty && is_statement {
21839                    self.write_newline();
21840                    self.indent_level += 1;
21841                    self.write_indent();
21842                }
21843                self.generate_expression(query)?;
21844                if self.config.pretty && is_statement {
21845                    self.write_newline();
21846                    self.indent_level -= 1;
21847                    self.write_indent();
21848                }
21849                self.write(")");
21850            }
21851        } else {
21852            // DuckDB: IN without parentheses for single expression that is NOT a literal
21853            // (array/list membership like 'red' IN tbl.flags)
21854            // ClickHouse: IN without parentheses for single non-array expressions
21855            let is_duckdb = matches!(
21856                self.config.dialect,
21857                Some(crate::dialects::DialectType::DuckDB)
21858            );
21859            let is_clickhouse = matches!(
21860                self.config.dialect,
21861                Some(crate::dialects::DialectType::ClickHouse)
21862            );
21863            let single_expr = in_expr.expressions.len() == 1;
21864            if is_clickhouse && single_expr {
21865                if let Expression::Array(arr) = &in_expr.expressions[0] {
21866                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
21867                    self.write(" (");
21868                    for (i, expr) in arr.expressions.iter().enumerate() {
21869                        if i > 0 {
21870                            self.write(", ");
21871                        }
21872                        self.generate_expression(expr)?;
21873                    }
21874                    self.write(")");
21875                } else {
21876                    self.write_space();
21877                    self.generate_expression(&in_expr.expressions[0])?;
21878                }
21879            } else {
21880                let is_bare_ref = single_expr
21881                    && matches!(
21882                        &in_expr.expressions[0],
21883                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
21884                    );
21885                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
21886                    // Bare field reference (no parens in source): IN identifier
21887                    // Also DuckDB: IN without parentheses for array/list membership
21888                    self.write_space();
21889                    self.generate_expression(&in_expr.expressions[0])?;
21890                } else {
21891                    // Standard IN (list)
21892                    self.write(" (");
21893                    for (i, expr) in in_expr.expressions.iter().enumerate() {
21894                        if i > 0 {
21895                            self.write(", ");
21896                        }
21897                        self.generate_expression(expr)?;
21898                    }
21899                    self.write(")");
21900                }
21901            }
21902        }
21903
21904        Ok(())
21905    }
21906
21907    fn generate_between(&mut self, between: &Between) -> Result<()> {
21908        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
21909        let use_prefix_not = between.not
21910            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
21911        if use_prefix_not {
21912            self.write_keyword("NOT");
21913            self.write_space();
21914        }
21915        self.generate_expression(&between.this)?;
21916        if between.not && !use_prefix_not {
21917            self.write_space();
21918            self.write_keyword("NOT");
21919        }
21920        self.write_space();
21921        self.write_keyword("BETWEEN");
21922        // Emit SYMMETRIC/ASYMMETRIC if present
21923        if let Some(sym) = between.symmetric {
21924            if sym {
21925                self.write(" SYMMETRIC");
21926            } else {
21927                self.write(" ASYMMETRIC");
21928            }
21929        }
21930        self.write_space();
21931        self.generate_expression(&between.low)?;
21932        self.write_space();
21933        self.write_keyword("AND");
21934        self.write_space();
21935        self.generate_expression(&between.high)
21936    }
21937
21938    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
21939        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
21940        let use_prefix_not = is_null.not
21941            && (self.config.dialect.is_none()
21942                || self.config.dialect == Some(DialectType::Generic)
21943                || is_null.postfix_form);
21944        if use_prefix_not {
21945            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
21946            self.write_keyword("NOT");
21947            self.write_space();
21948            self.generate_expression(&is_null.this)?;
21949            self.write_space();
21950            self.write_keyword("IS");
21951            self.write_space();
21952            self.write_keyword("NULL");
21953        } else {
21954            self.generate_expression(&is_null.this)?;
21955            self.write_space();
21956            self.write_keyword("IS");
21957            if is_null.not {
21958                self.write_space();
21959                self.write_keyword("NOT");
21960            }
21961            self.write_space();
21962            self.write_keyword("NULL");
21963        }
21964        Ok(())
21965    }
21966
21967    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
21968        self.generate_expression(&is_true.this)?;
21969        self.write_space();
21970        self.write_keyword("IS");
21971        if is_true.not {
21972            self.write_space();
21973            self.write_keyword("NOT");
21974        }
21975        self.write_space();
21976        self.write_keyword("TRUE");
21977        Ok(())
21978    }
21979
21980    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
21981        self.generate_expression(&is_false.this)?;
21982        self.write_space();
21983        self.write_keyword("IS");
21984        if is_false.not {
21985            self.write_space();
21986            self.write_keyword("NOT");
21987        }
21988        self.write_space();
21989        self.write_keyword("FALSE");
21990        Ok(())
21991    }
21992
21993    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
21994        self.generate_expression(&is_json.this)?;
21995        self.write_space();
21996        self.write_keyword("IS");
21997        if is_json.negated {
21998            self.write_space();
21999            self.write_keyword("NOT");
22000        }
22001        self.write_space();
22002        self.write_keyword("JSON");
22003
22004        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
22005        if let Some(ref json_type) = is_json.json_type {
22006            self.write_space();
22007            self.write_keyword(json_type);
22008        }
22009
22010        // Output key uniqueness constraint if specified
22011        match &is_json.unique_keys {
22012            Some(JsonUniqueKeys::With) => {
22013                self.write_space();
22014                self.write_keyword("WITH UNIQUE KEYS");
22015            }
22016            Some(JsonUniqueKeys::Without) => {
22017                self.write_space();
22018                self.write_keyword("WITHOUT UNIQUE KEYS");
22019            }
22020            Some(JsonUniqueKeys::Shorthand) => {
22021                self.write_space();
22022                self.write_keyword("UNIQUE KEYS");
22023            }
22024            None => {}
22025        }
22026
22027        Ok(())
22028    }
22029
22030    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
22031        self.generate_expression(&is_expr.left)?;
22032        self.write_space();
22033        self.write_keyword("IS");
22034        self.write_space();
22035        self.generate_expression(&is_expr.right)
22036    }
22037
22038    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
22039        if exists.not {
22040            self.write_keyword("NOT");
22041            self.write_space();
22042        }
22043        self.write_keyword("EXISTS");
22044        self.write("(");
22045        let is_statement = matches!(
22046            &exists.this,
22047            Expression::Select(_)
22048                | Expression::Union(_)
22049                | Expression::Intersect(_)
22050                | Expression::Except(_)
22051        );
22052        if self.config.pretty && is_statement {
22053            self.write_newline();
22054            self.indent_level += 1;
22055            self.write_indent();
22056            self.generate_expression(&exists.this)?;
22057            self.write_newline();
22058            self.indent_level -= 1;
22059            self.write_indent();
22060            self.write(")");
22061        } else {
22062            self.generate_expression(&exists.this)?;
22063            self.write(")");
22064        }
22065        Ok(())
22066    }
22067
22068    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
22069        self.generate_expression(&op.left)?;
22070        self.write_space();
22071        self.write_keyword("MEMBER OF");
22072        self.write("(");
22073        self.generate_expression(&op.right)?;
22074        self.write(")");
22075        Ok(())
22076    }
22077
22078    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
22079        if subquery.lateral {
22080            self.write_keyword("LATERAL");
22081            self.write_space();
22082        }
22083
22084        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
22085        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
22086        // to carry the LIMIT modifier without adding more parens
22087        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
22088            matches!(
22089                &p.this,
22090                Expression::Select(_)
22091                    | Expression::Union(_)
22092                    | Expression::Intersect(_)
22093                    | Expression::Except(_)
22094                    | Expression::Subquery(_)
22095            )
22096        } else {
22097            false
22098        };
22099
22100        // Check if inner expression is a statement for pretty formatting
22101        let is_statement = matches!(
22102            &subquery.this,
22103            Expression::Select(_)
22104                | Expression::Union(_)
22105                | Expression::Intersect(_)
22106                | Expression::Except(_)
22107                | Expression::Merge(_)
22108        );
22109
22110        if !skip_outer_parens {
22111            self.write("(");
22112            if self.config.pretty && is_statement {
22113                self.write_newline();
22114                self.indent_level += 1;
22115                self.write_indent();
22116            }
22117        }
22118        self.generate_expression(&subquery.this)?;
22119
22120        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
22121        if subquery.modifiers_inside {
22122            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
22123            if let Some(order_by) = &subquery.order_by {
22124                self.write_space();
22125                self.write_keyword("ORDER BY");
22126                self.write_space();
22127                for (i, ord) in order_by.expressions.iter().enumerate() {
22128                    if i > 0 {
22129                        self.write(", ");
22130                    }
22131                    self.generate_ordered(ord)?;
22132                }
22133            }
22134
22135            if let Some(limit) = &subquery.limit {
22136                self.write_space();
22137                self.write_keyword("LIMIT");
22138                self.write_space();
22139                self.generate_expression(&limit.this)?;
22140                if limit.percent {
22141                    self.write_space();
22142                    self.write_keyword("PERCENT");
22143                }
22144            }
22145
22146            if let Some(offset) = &subquery.offset {
22147                self.write_space();
22148                self.write_keyword("OFFSET");
22149                self.write_space();
22150                self.generate_expression(&offset.this)?;
22151            }
22152        }
22153
22154        if !skip_outer_parens {
22155            if self.config.pretty && is_statement {
22156                self.write_newline();
22157                self.indent_level -= 1;
22158                self.write_indent();
22159            }
22160            self.write(")");
22161        }
22162
22163        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
22164        if !subquery.modifiers_inside {
22165            if let Some(order_by) = &subquery.order_by {
22166                self.write_space();
22167                self.write_keyword("ORDER BY");
22168                self.write_space();
22169                for (i, ord) in order_by.expressions.iter().enumerate() {
22170                    if i > 0 {
22171                        self.write(", ");
22172                    }
22173                    self.generate_ordered(ord)?;
22174                }
22175            }
22176
22177            if let Some(limit) = &subquery.limit {
22178                self.write_space();
22179                self.write_keyword("LIMIT");
22180                self.write_space();
22181                self.generate_expression(&limit.this)?;
22182                if limit.percent {
22183                    self.write_space();
22184                    self.write_keyword("PERCENT");
22185                }
22186            }
22187
22188            if let Some(offset) = &subquery.offset {
22189                self.write_space();
22190                self.write_keyword("OFFSET");
22191                self.write_space();
22192                self.generate_expression(&offset.this)?;
22193            }
22194
22195            // Generate DISTRIBUTE BY (Hive/Spark)
22196            if let Some(distribute_by) = &subquery.distribute_by {
22197                self.write_space();
22198                self.write_keyword("DISTRIBUTE BY");
22199                self.write_space();
22200                for (i, expr) in distribute_by.expressions.iter().enumerate() {
22201                    if i > 0 {
22202                        self.write(", ");
22203                    }
22204                    self.generate_expression(expr)?;
22205                }
22206            }
22207
22208            // Generate SORT BY (Hive/Spark)
22209            if let Some(sort_by) = &subquery.sort_by {
22210                self.write_space();
22211                self.write_keyword("SORT BY");
22212                self.write_space();
22213                for (i, ord) in sort_by.expressions.iter().enumerate() {
22214                    if i > 0 {
22215                        self.write(", ");
22216                    }
22217                    self.generate_ordered(ord)?;
22218                }
22219            }
22220
22221            // Generate CLUSTER BY (Hive/Spark)
22222            if let Some(cluster_by) = &subquery.cluster_by {
22223                self.write_space();
22224                self.write_keyword("CLUSTER BY");
22225                self.write_space();
22226                for (i, ord) in cluster_by.expressions.iter().enumerate() {
22227                    if i > 0 {
22228                        self.write(", ");
22229                    }
22230                    self.generate_ordered(ord)?;
22231                }
22232            }
22233        }
22234
22235        if let Some(alias) = &subquery.alias {
22236            self.write_space();
22237            // Oracle doesn't use AS for subquery aliases
22238            let skip_as = matches!(
22239                self.config.dialect,
22240                Some(crate::dialects::DialectType::Oracle)
22241            );
22242            if !skip_as {
22243                self.write_keyword("AS");
22244                self.write_space();
22245            }
22246            self.generate_identifier(alias)?;
22247            if !subquery.column_aliases.is_empty() {
22248                self.write("(");
22249                for (i, col) in subquery.column_aliases.iter().enumerate() {
22250                    if i > 0 {
22251                        self.write(", ");
22252                    }
22253                    self.generate_identifier(col)?;
22254                }
22255                self.write(")");
22256            }
22257        }
22258        // Output trailing comments
22259        for comment in &subquery.trailing_comments {
22260            self.write(" ");
22261            self.write_formatted_comment(comment);
22262        }
22263        Ok(())
22264    }
22265
22266    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
22267        // Generate WITH clause if present
22268        if let Some(ref with) = pivot.with {
22269            self.generate_with(with)?;
22270            self.write_space();
22271        }
22272
22273        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
22274
22275        // Check for Redshift UNPIVOT in FROM clause:
22276        // UNPIVOT expr [AS val AT attr]
22277        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
22278        let is_redshift_unpivot = pivot.unpivot
22279            && pivot.expressions.is_empty()
22280            && pivot.fields.is_empty()
22281            && pivot.using.is_empty()
22282            && pivot.into.is_none()
22283            && !matches!(&pivot.this, Expression::Null(_));
22284
22285        if is_redshift_unpivot {
22286            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
22287            self.write_keyword("UNPIVOT");
22288            self.write_space();
22289            self.generate_expression(&pivot.this)?;
22290            // Alias - for Redshift it can be "val AT attr" format
22291            if let Some(alias) = &pivot.alias {
22292                self.write_space();
22293                self.write_keyword("AS");
22294                self.write_space();
22295                // The alias might contain " AT " for the attr part
22296                self.write(&alias.name);
22297            }
22298            return Ok(());
22299        }
22300
22301        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
22302        let is_simplified = !pivot.using.is_empty()
22303            || pivot.into.is_some()
22304            || (pivot.fields.is_empty()
22305                && !pivot.expressions.is_empty()
22306                && !matches!(&pivot.this, Expression::Null(_)));
22307
22308        if is_simplified {
22309            // DuckDB simplified syntax:
22310            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
22311            //   UNPIVOT table ON cols INTO NAME col VALUE col
22312            self.write_keyword(direction);
22313            self.write_space();
22314            self.generate_expression(&pivot.this)?;
22315
22316            if !pivot.expressions.is_empty() {
22317                self.write_space();
22318                self.write_keyword("ON");
22319                self.write_space();
22320                for (i, expr) in pivot.expressions.iter().enumerate() {
22321                    if i > 0 {
22322                        self.write(", ");
22323                    }
22324                    self.generate_expression(expr)?;
22325                }
22326            }
22327
22328            // INTO (for UNPIVOT)
22329            if let Some(into) = &pivot.into {
22330                self.write_space();
22331                self.write_keyword("INTO");
22332                self.write_space();
22333                self.generate_expression(into)?;
22334            }
22335
22336            // USING (for PIVOT)
22337            if !pivot.using.is_empty() {
22338                self.write_space();
22339                self.write_keyword("USING");
22340                self.write_space();
22341                for (i, expr) in pivot.using.iter().enumerate() {
22342                    if i > 0 {
22343                        self.write(", ");
22344                    }
22345                    self.generate_expression(expr)?;
22346                }
22347            }
22348
22349            // GROUP BY
22350            if let Some(group) = &pivot.group {
22351                self.write_space();
22352                self.generate_expression(group)?;
22353            }
22354        } else {
22355            // Standard syntax:
22356            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
22357            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
22358            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
22359            if !matches!(&pivot.this, Expression::Null(_)) {
22360                self.generate_expression(&pivot.this)?;
22361                self.write_space();
22362            }
22363            self.write_keyword(direction);
22364            self.write("(");
22365
22366            // Aggregation expressions
22367            for (i, expr) in pivot.expressions.iter().enumerate() {
22368                if i > 0 {
22369                    self.write(", ");
22370                }
22371                self.generate_expression(expr)?;
22372            }
22373
22374            // FOR...IN fields
22375            if !pivot.fields.is_empty() {
22376                if !pivot.expressions.is_empty() {
22377                    self.write_space();
22378                }
22379                self.write_keyword("FOR");
22380                self.write_space();
22381                for (i, field) in pivot.fields.iter().enumerate() {
22382                    if i > 0 {
22383                        self.write_space();
22384                    }
22385                    // field is an In expression: column IN (values)
22386                    self.generate_expression(field)?;
22387                }
22388            }
22389
22390            // DEFAULT ON NULL
22391            if let Some(default_val) = &pivot.default_on_null {
22392                self.write_space();
22393                self.write_keyword("DEFAULT ON NULL");
22394                self.write(" (");
22395                self.generate_expression(default_val)?;
22396                self.write(")");
22397            }
22398
22399            // GROUP BY inside PIVOT parens
22400            if let Some(group) = &pivot.group {
22401                self.write_space();
22402                self.generate_expression(group)?;
22403            }
22404
22405            self.write(")");
22406        }
22407
22408        // Alias
22409        if let Some(alias) = &pivot.alias {
22410            self.write_space();
22411            self.write_keyword("AS");
22412            self.write_space();
22413            self.generate_identifier(alias)?;
22414        }
22415
22416        Ok(())
22417    }
22418
22419    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
22420        self.generate_expression(&unpivot.this)?;
22421        self.write_space();
22422        self.write_keyword("UNPIVOT");
22423        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
22424        if let Some(include) = unpivot.include_nulls {
22425            self.write_space();
22426            if include {
22427                self.write_keyword("INCLUDE NULLS");
22428            } else {
22429                self.write_keyword("EXCLUDE NULLS");
22430            }
22431            self.write_space();
22432        }
22433        self.write("(");
22434        if unpivot.value_column_parenthesized {
22435            self.write("(");
22436        }
22437        self.generate_identifier(&unpivot.value_column)?;
22438        // Output additional value columns if present
22439        for extra_col in &unpivot.extra_value_columns {
22440            self.write(", ");
22441            self.generate_identifier(extra_col)?;
22442        }
22443        if unpivot.value_column_parenthesized {
22444            self.write(")");
22445        }
22446        self.write_space();
22447        self.write_keyword("FOR");
22448        self.write_space();
22449        self.generate_identifier(&unpivot.name_column)?;
22450        self.write_space();
22451        self.write_keyword("IN");
22452        self.write(" (");
22453        for (i, col) in unpivot.columns.iter().enumerate() {
22454            if i > 0 {
22455                self.write(", ");
22456            }
22457            self.generate_expression(col)?;
22458        }
22459        self.write("))");
22460        if let Some(alias) = &unpivot.alias {
22461            self.write_space();
22462            self.write_keyword("AS");
22463            self.write_space();
22464            self.generate_identifier(alias)?;
22465        }
22466        Ok(())
22467    }
22468
22469    fn generate_values(&mut self, values: &Values) -> Result<()> {
22470        self.write_keyword("VALUES");
22471        for (i, row) in values.expressions.iter().enumerate() {
22472            if i > 0 {
22473                self.write(",");
22474            }
22475            self.write(" (");
22476            for (j, expr) in row.expressions.iter().enumerate() {
22477                if j > 0 {
22478                    self.write(", ");
22479                }
22480                self.generate_expression(expr)?;
22481            }
22482            self.write(")");
22483        }
22484        if let Some(alias) = &values.alias {
22485            self.write_space();
22486            self.write_keyword("AS");
22487            self.write_space();
22488            self.generate_identifier(alias)?;
22489            if !values.column_aliases.is_empty() {
22490                self.write("(");
22491                for (i, col) in values.column_aliases.iter().enumerate() {
22492                    if i > 0 {
22493                        self.write(", ");
22494                    }
22495                    self.generate_identifier(col)?;
22496                }
22497                self.write(")");
22498            }
22499        }
22500        Ok(())
22501    }
22502
22503    fn generate_array(&mut self, arr: &Array) -> Result<()> {
22504        // Apply struct name inheritance for target dialects that need it
22505        let needs_inheritance = matches!(
22506            self.config.dialect,
22507            Some(DialectType::DuckDB)
22508                | Some(DialectType::Spark)
22509                | Some(DialectType::Databricks)
22510                | Some(DialectType::Hive)
22511                | Some(DialectType::Snowflake)
22512                | Some(DialectType::Presto)
22513                | Some(DialectType::Trino)
22514        );
22515        let propagated: Vec<Expression>;
22516        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
22517            propagated = Self::inherit_struct_field_names(&arr.expressions);
22518            &propagated
22519        } else {
22520            &arr.expressions
22521        };
22522
22523        // Generic mode: ARRAY(1, 2, 3) with parentheses
22524        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
22525        let use_parens =
22526            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
22527        if !self.config.array_bracket_only {
22528            self.write_keyword("ARRAY");
22529        }
22530        if use_parens {
22531            self.write("(");
22532        } else {
22533            self.write("[");
22534        }
22535        for (i, expr) in expressions.iter().enumerate() {
22536            if i > 0 {
22537                self.write(", ");
22538            }
22539            self.generate_expression(expr)?;
22540        }
22541        if use_parens {
22542            self.write(")");
22543        } else {
22544            self.write("]");
22545        }
22546        Ok(())
22547    }
22548
22549    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
22550        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
22551        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
22552        if tuple.expressions.len() == 2 {
22553            if let Expression::TableAlias(_) = &tuple.expressions[1] {
22554                // First element is the function/expression, second is the TableAlias
22555                self.generate_expression(&tuple.expressions[0])?;
22556                self.write_space();
22557                self.write_keyword("AS");
22558                self.write_space();
22559                self.generate_expression(&tuple.expressions[1])?;
22560                return Ok(());
22561            }
22562        }
22563
22564        // In pretty mode, format long tuples with each element on a new line
22565        // Only expand if total width exceeds threshold
22566        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
22567            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
22568            for expr in &tuple.expressions {
22569                expr_strings.push(self.generate_to_string(expr)?);
22570            }
22571            self.too_wide(&expr_strings)
22572        } else {
22573            false
22574        };
22575
22576        if expand_tuple {
22577            self.write("(");
22578            self.write_newline();
22579            self.indent_level += 1;
22580            for (i, expr) in tuple.expressions.iter().enumerate() {
22581                if i > 0 {
22582                    self.write(",");
22583                    self.write_newline();
22584                }
22585                self.write_indent();
22586                self.generate_expression(expr)?;
22587            }
22588            self.indent_level -= 1;
22589            self.write_newline();
22590            self.write_indent();
22591            self.write(")");
22592        } else {
22593            self.write("(");
22594            for (i, expr) in tuple.expressions.iter().enumerate() {
22595                if i > 0 {
22596                    self.write(", ");
22597                }
22598                self.generate_expression(expr)?;
22599            }
22600            self.write(")");
22601        }
22602        Ok(())
22603    }
22604
22605    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
22606        self.generate_expression(&pipe.this)?;
22607        self.write(" |> ");
22608        self.generate_expression(&pipe.expression)?;
22609        Ok(())
22610    }
22611
22612    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
22613        self.generate_expression(&ordered.this)?;
22614        if ordered.desc {
22615            self.write_space();
22616            self.write_keyword("DESC");
22617        } else if ordered.explicit_asc {
22618            self.write_space();
22619            self.write_keyword("ASC");
22620        }
22621        if let Some(nulls_first) = ordered.nulls_first {
22622            // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
22623            // for the dialect. Different dialects have different NULL ordering defaults:
22624            //
22625            // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
22626            //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
22627            //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
22628            //
22629            // nulls_are_small (Spark, Hive, BigQuery, most others):
22630            //   - ASC: NULLS FIRST is default
22631            //   - DESC: NULLS LAST is default
22632            //
22633            // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
22634            //   - NULLS LAST is always the default regardless of sort direction
22635            let is_asc = !ordered.desc;
22636            let is_nulls_are_large = matches!(
22637                self.config.dialect,
22638                Some(DialectType::Oracle)
22639                    | Some(DialectType::PostgreSQL)
22640                    | Some(DialectType::Redshift)
22641                    | Some(DialectType::Snowflake)
22642            );
22643            let is_nulls_are_last = matches!(
22644                self.config.dialect,
22645                Some(DialectType::Dremio)
22646                    | Some(DialectType::DuckDB)
22647                    | Some(DialectType::Presto)
22648                    | Some(DialectType::Trino)
22649                    | Some(DialectType::Athena)
22650                    | Some(DialectType::ClickHouse)
22651                    | Some(DialectType::Drill)
22652                    | Some(DialectType::Exasol)
22653            );
22654
22655            // Check if the NULLS ordering matches the default for this dialect
22656            let is_default_nulls = if is_nulls_are_large {
22657                // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
22658                (is_asc && !nulls_first) || (!is_asc && nulls_first)
22659            } else if is_nulls_are_last {
22660                // For nulls_are_last: NULLS LAST is always default
22661                !nulls_first
22662            } else {
22663                false
22664            };
22665
22666            if !is_default_nulls {
22667                self.write_space();
22668                self.write_keyword("NULLS");
22669                self.write_space();
22670                self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
22671            }
22672        }
22673        // WITH FILL clause (ClickHouse)
22674        if let Some(ref with_fill) = ordered.with_fill {
22675            self.write_space();
22676            self.generate_with_fill(with_fill)?;
22677        }
22678        Ok(())
22679    }
22680
22681    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
22682    fn write_clickhouse_type(&mut self, type_str: &str) {
22683        if self.clickhouse_nullable_depth < 0 {
22684            // Map key context: don't wrap in Nullable
22685            self.write(type_str);
22686        } else {
22687            self.write(&format!("Nullable({})", type_str));
22688        }
22689    }
22690
22691    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
22692        use crate::dialects::DialectType;
22693
22694        match dt {
22695            DataType::Boolean => {
22696                // Dialect-specific boolean type mappings
22697                match self.config.dialect {
22698                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
22699                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
22700                    Some(DialectType::Oracle) => {
22701                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
22702                        self.write_keyword("NUMBER(1)")
22703                    }
22704                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
22705                    _ => self.write_keyword("BOOLEAN"),
22706                }
22707            }
22708            DataType::TinyInt { length } => {
22709                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
22710                // Dremio maps TINYINT to INT
22711                // ClickHouse maps TINYINT to Int8
22712                match self.config.dialect {
22713                    Some(DialectType::PostgreSQL)
22714                    | Some(DialectType::Redshift)
22715                    | Some(DialectType::Oracle)
22716                    | Some(DialectType::Exasol) => {
22717                        self.write_keyword("SMALLINT");
22718                    }
22719                    Some(DialectType::Teradata) => {
22720                        // Teradata uses BYTEINT for smallest integer
22721                        self.write_keyword("BYTEINT");
22722                    }
22723                    Some(DialectType::Dremio) => {
22724                        // Dremio maps TINYINT to INT
22725                        self.write_keyword("INT");
22726                    }
22727                    Some(DialectType::ClickHouse) => {
22728                        self.write_clickhouse_type("Int8");
22729                    }
22730                    _ => {
22731                        self.write_keyword("TINYINT");
22732                    }
22733                }
22734                if let Some(n) = length {
22735                    if !matches!(
22736                        self.config.dialect,
22737                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
22738                    ) {
22739                        self.write(&format!("({})", n));
22740                    }
22741                }
22742            }
22743            DataType::SmallInt { length } => {
22744                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
22745                match self.config.dialect {
22746                    Some(DialectType::Dremio) => {
22747                        self.write_keyword("INT");
22748                    }
22749                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
22750                        self.write_keyword("INTEGER");
22751                    }
22752                    Some(DialectType::BigQuery) => {
22753                        self.write_keyword("INT64");
22754                    }
22755                    Some(DialectType::ClickHouse) => {
22756                        self.write_clickhouse_type("Int16");
22757                    }
22758                    _ => {
22759                        self.write_keyword("SMALLINT");
22760                        if let Some(n) = length {
22761                            self.write(&format!("({})", n));
22762                        }
22763                    }
22764                }
22765            }
22766            DataType::Int {
22767                length,
22768                integer_spelling: _,
22769            } => {
22770                // BigQuery uses INT64 for INT
22771                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
22772                    self.write_keyword("INT64");
22773                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22774                    self.write_clickhouse_type("Int32");
22775                } else {
22776                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
22777                    let use_integer = match self.config.dialect {
22778                        Some(DialectType::TSQL)
22779                        | Some(DialectType::Fabric)
22780                        | Some(DialectType::Presto)
22781                        | Some(DialectType::Trino)
22782                        | Some(DialectType::SQLite)
22783                        | Some(DialectType::Redshift) => true,
22784                        _ => false,
22785                    };
22786                    if use_integer {
22787                        self.write_keyword("INTEGER");
22788                    } else {
22789                        self.write_keyword("INT");
22790                    }
22791                    if let Some(n) = length {
22792                        self.write(&format!("({})", n));
22793                    }
22794                }
22795            }
22796            DataType::BigInt { length } => {
22797                // Dialect-specific bigint type mappings
22798                match self.config.dialect {
22799                    Some(DialectType::Oracle) => {
22800                        // Oracle doesn't have BIGINT, uses INT
22801                        self.write_keyword("INT");
22802                    }
22803                    Some(DialectType::ClickHouse) => {
22804                        self.write_clickhouse_type("Int64");
22805                    }
22806                    _ => {
22807                        self.write_keyword("BIGINT");
22808                        if let Some(n) = length {
22809                            self.write(&format!("({})", n));
22810                        }
22811                    }
22812                }
22813            }
22814            DataType::Float {
22815                precision,
22816                scale,
22817                real_spelling,
22818            } => {
22819                // Dialect-specific float type mappings
22820                // If real_spelling is true, preserve REAL; otherwise use dialect default
22821                // Spark/Hive don't support REAL, always use FLOAT
22822                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22823                    self.write_clickhouse_type("Float32");
22824                } else if *real_spelling
22825                    && !matches!(
22826                        self.config.dialect,
22827                        Some(DialectType::Spark)
22828                            | Some(DialectType::Databricks)
22829                            | Some(DialectType::Hive)
22830                            | Some(DialectType::Snowflake)
22831                            | Some(DialectType::MySQL)
22832                            | Some(DialectType::BigQuery)
22833                    )
22834                {
22835                    self.write_keyword("REAL")
22836                } else {
22837                    match self.config.dialect {
22838                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
22839                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
22840                        _ => self.write_keyword("FLOAT"),
22841                    }
22842                }
22843                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
22844                // Spark/Hive don't support FLOAT(precision)
22845                if !matches!(
22846                    self.config.dialect,
22847                    Some(DialectType::Spark)
22848                        | Some(DialectType::Databricks)
22849                        | Some(DialectType::Hive)
22850                        | Some(DialectType::Presto)
22851                        | Some(DialectType::Trino)
22852                ) {
22853                    if let Some(p) = precision {
22854                        self.write(&format!("({}", p));
22855                        if let Some(s) = scale {
22856                            self.write(&format!(", {})", s));
22857                        } else {
22858                            self.write(")");
22859                        }
22860                    }
22861                }
22862            }
22863            DataType::Double { precision, scale } => {
22864                // Dialect-specific double type mappings
22865                match self.config.dialect {
22866                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
22867                        self.write_keyword("FLOAT")
22868                    } // SQL Server/Fabric FLOAT is double
22869                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
22870                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
22871                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
22872                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
22873                    Some(DialectType::PostgreSQL)
22874                    | Some(DialectType::Redshift)
22875                    | Some(DialectType::Teradata)
22876                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
22877                    _ => self.write_keyword("DOUBLE"),
22878                }
22879                // MySQL supports DOUBLE(precision, scale)
22880                if let Some(p) = precision {
22881                    self.write(&format!("({}", p));
22882                    if let Some(s) = scale {
22883                        self.write(&format!(", {})", s));
22884                    } else {
22885                        self.write(")");
22886                    }
22887                }
22888            }
22889            DataType::Decimal { precision, scale } => {
22890                // Dialect-specific decimal type mappings
22891                match self.config.dialect {
22892                    Some(DialectType::ClickHouse) => {
22893                        self.write("Decimal");
22894                        if let Some(p) = precision {
22895                            self.write(&format!("({}", p));
22896                            if let Some(s) = scale {
22897                                self.write(&format!(", {}", s));
22898                            }
22899                            self.write(")");
22900                        }
22901                    }
22902                    Some(DialectType::Oracle) => {
22903                        // Oracle uses NUMBER instead of DECIMAL
22904                        self.write_keyword("NUMBER");
22905                        if let Some(p) = precision {
22906                            self.write(&format!("({}", p));
22907                            if let Some(s) = scale {
22908                                self.write(&format!(", {}", s));
22909                            }
22910                            self.write(")");
22911                        }
22912                    }
22913                    Some(DialectType::BigQuery) => {
22914                        // BigQuery uses NUMERIC instead of DECIMAL
22915                        self.write_keyword("NUMERIC");
22916                        if let Some(p) = precision {
22917                            self.write(&format!("({}", p));
22918                            if let Some(s) = scale {
22919                                self.write(&format!(", {}", s));
22920                            }
22921                            self.write(")");
22922                        }
22923                    }
22924                    _ => {
22925                        self.write_keyword("DECIMAL");
22926                        if let Some(p) = precision {
22927                            self.write(&format!("({}", p));
22928                            if let Some(s) = scale {
22929                                self.write(&format!(", {}", s));
22930                            }
22931                            self.write(")");
22932                        }
22933                    }
22934                }
22935            }
22936            DataType::Char { length } => {
22937                // Dialect-specific char type mappings
22938                match self.config.dialect {
22939                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
22940                        // DuckDB/SQLite maps CHAR to TEXT
22941                        self.write_keyword("TEXT");
22942                    }
22943                    Some(DialectType::Hive)
22944                    | Some(DialectType::Spark)
22945                    | Some(DialectType::Databricks) => {
22946                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
22947                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
22948                        if length.is_some()
22949                            && !matches!(self.config.dialect, Some(DialectType::Hive))
22950                        {
22951                            self.write_keyword("CHAR");
22952                            if let Some(n) = length {
22953                                self.write(&format!("({})", n));
22954                            }
22955                        } else {
22956                            self.write_keyword("STRING");
22957                        }
22958                    }
22959                    Some(DialectType::Dremio) => {
22960                        // Dremio maps CHAR to VARCHAR
22961                        self.write_keyword("VARCHAR");
22962                        if let Some(n) = length {
22963                            self.write(&format!("({})", n));
22964                        }
22965                    }
22966                    _ => {
22967                        self.write_keyword("CHAR");
22968                        if let Some(n) = length {
22969                            self.write(&format!("({})", n));
22970                        }
22971                    }
22972                }
22973            }
22974            DataType::VarChar {
22975                length,
22976                parenthesized_length,
22977            } => {
22978                // Dialect-specific varchar type mappings
22979                match self.config.dialect {
22980                    Some(DialectType::Oracle) => {
22981                        self.write_keyword("VARCHAR2");
22982                        if let Some(n) = length {
22983                            self.write(&format!("({})", n));
22984                        }
22985                    }
22986                    Some(DialectType::DuckDB) => {
22987                        // DuckDB maps VARCHAR to TEXT, preserving length
22988                        self.write_keyword("TEXT");
22989                        if let Some(n) = length {
22990                            self.write(&format!("({})", n));
22991                        }
22992                    }
22993                    Some(DialectType::SQLite) => {
22994                        // SQLite maps VARCHAR to TEXT, preserving length
22995                        self.write_keyword("TEXT");
22996                        if let Some(n) = length {
22997                            self.write(&format!("({})", n));
22998                        }
22999                    }
23000                    Some(DialectType::MySQL) if length.is_none() => {
23001                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
23002                        self.write_keyword("TEXT");
23003                    }
23004                    Some(DialectType::Hive)
23005                    | Some(DialectType::Spark)
23006                    | Some(DialectType::Databricks)
23007                        if length.is_none() =>
23008                    {
23009                        // Hive/Spark/Databricks: VARCHAR without length → STRING
23010                        self.write_keyword("STRING");
23011                    }
23012                    _ => {
23013                        self.write_keyword("VARCHAR");
23014                        if let Some(n) = length {
23015                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
23016                            if *parenthesized_length {
23017                                self.write(&format!("(({}))", n));
23018                            } else {
23019                                self.write(&format!("({})", n));
23020                            }
23021                        }
23022                    }
23023                }
23024            }
23025            DataType::Text => {
23026                // Dialect-specific text type mappings
23027                match self.config.dialect {
23028                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
23029                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23030                        self.write_keyword("VARCHAR(MAX)")
23031                    }
23032                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
23033                    Some(DialectType::Snowflake)
23034                    | Some(DialectType::Dremio)
23035                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
23036                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
23037                    Some(DialectType::Presto)
23038                    | Some(DialectType::Trino)
23039                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
23040                    Some(DialectType::Spark)
23041                    | Some(DialectType::Databricks)
23042                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
23043                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
23044                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
23045                        self.write_keyword("STRING")
23046                    }
23047                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
23048                    _ => self.write_keyword("TEXT"),
23049                }
23050            }
23051            DataType::TextWithLength { length } => {
23052                // TEXT(n) - dialect-specific type with length
23053                match self.config.dialect {
23054                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
23055                    Some(DialectType::Hive)
23056                    | Some(DialectType::Spark)
23057                    | Some(DialectType::Databricks) => {
23058                        self.write(&format!("VARCHAR({})", length));
23059                    }
23060                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
23061                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
23062                    Some(DialectType::Snowflake)
23063                    | Some(DialectType::Presto)
23064                    | Some(DialectType::Trino)
23065                    | Some(DialectType::Athena)
23066                    | Some(DialectType::Drill)
23067                    | Some(DialectType::Dremio) => {
23068                        self.write(&format!("VARCHAR({})", length));
23069                    }
23070                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23071                        self.write(&format!("VARCHAR({})", length))
23072                    }
23073                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
23074                        self.write(&format!("STRING({})", length))
23075                    }
23076                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
23077                    _ => self.write(&format!("TEXT({})", length)),
23078                }
23079            }
23080            DataType::String { length } => {
23081                // STRING type with optional length (BigQuery STRING(n))
23082                match self.config.dialect {
23083                    Some(DialectType::ClickHouse) => {
23084                        // ClickHouse uses String with specific casing
23085                        self.write("String");
23086                        if let Some(n) = length {
23087                            self.write(&format!("({})", n));
23088                        }
23089                    }
23090                    Some(DialectType::BigQuery)
23091                    | Some(DialectType::Hive)
23092                    | Some(DialectType::Spark)
23093                    | Some(DialectType::Databricks)
23094                    | Some(DialectType::StarRocks)
23095                    | Some(DialectType::Doris) => {
23096                        self.write_keyword("STRING");
23097                        if let Some(n) = length {
23098                            self.write(&format!("({})", n));
23099                        }
23100                    }
23101                    Some(DialectType::PostgreSQL) => {
23102                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
23103                        if let Some(n) = length {
23104                            self.write_keyword("VARCHAR");
23105                            self.write(&format!("({})", n));
23106                        } else {
23107                            self.write_keyword("TEXT");
23108                        }
23109                    }
23110                    Some(DialectType::Redshift) => {
23111                        // Redshift: STRING -> VARCHAR(MAX)
23112                        if let Some(n) = length {
23113                            self.write_keyword("VARCHAR");
23114                            self.write(&format!("({})", n));
23115                        } else {
23116                            self.write_keyword("VARCHAR(MAX)");
23117                        }
23118                    }
23119                    Some(DialectType::MySQL) => {
23120                        // MySQL doesn't have STRING - use VARCHAR or TEXT
23121                        if let Some(n) = length {
23122                            self.write_keyword("VARCHAR");
23123                            self.write(&format!("({})", n));
23124                        } else {
23125                            self.write_keyword("TEXT");
23126                        }
23127                    }
23128                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23129                        // TSQL: STRING -> VARCHAR(MAX)
23130                        if let Some(n) = length {
23131                            self.write_keyword("VARCHAR");
23132                            self.write(&format!("({})", n));
23133                        } else {
23134                            self.write_keyword("VARCHAR(MAX)");
23135                        }
23136                    }
23137                    Some(DialectType::Oracle) => {
23138                        // Oracle: STRING -> CLOB
23139                        self.write_keyword("CLOB");
23140                    }
23141                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
23142                        // DuckDB/Materialize uses TEXT for string types
23143                        self.write_keyword("TEXT");
23144                        if let Some(n) = length {
23145                            self.write(&format!("({})", n));
23146                        }
23147                    }
23148                    Some(DialectType::Presto)
23149                    | Some(DialectType::Trino)
23150                    | Some(DialectType::Drill)
23151                    | Some(DialectType::Dremio) => {
23152                        // Presto/Trino/Drill use VARCHAR for string types
23153                        self.write_keyword("VARCHAR");
23154                        if let Some(n) = length {
23155                            self.write(&format!("({})", n));
23156                        }
23157                    }
23158                    Some(DialectType::Snowflake) => {
23159                        // Snowflake: STRING stays as STRING (identity/DDL)
23160                        // CAST context STRING -> VARCHAR is handled in generate_cast
23161                        self.write_keyword("STRING");
23162                        if let Some(n) = length {
23163                            self.write(&format!("({})", n));
23164                        }
23165                    }
23166                    _ => {
23167                        // Default: output STRING with optional length
23168                        self.write_keyword("STRING");
23169                        if let Some(n) = length {
23170                            self.write(&format!("({})", n));
23171                        }
23172                    }
23173                }
23174            }
23175            DataType::Binary { length } => {
23176                // Dialect-specific binary type mappings
23177                match self.config.dialect {
23178                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
23179                        self.write_keyword("BYTEA");
23180                        if let Some(n) = length {
23181                            self.write(&format!("({})", n));
23182                        }
23183                    }
23184                    Some(DialectType::Redshift) => {
23185                        self.write_keyword("VARBYTE");
23186                        if let Some(n) = length {
23187                            self.write(&format!("({})", n));
23188                        }
23189                    }
23190                    Some(DialectType::DuckDB)
23191                    | Some(DialectType::SQLite)
23192                    | Some(DialectType::Oracle) => {
23193                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
23194                        self.write_keyword("BLOB");
23195                        if let Some(n) = length {
23196                            self.write(&format!("({})", n));
23197                        }
23198                    }
23199                    Some(DialectType::Presto)
23200                    | Some(DialectType::Trino)
23201                    | Some(DialectType::Athena)
23202                    | Some(DialectType::Drill)
23203                    | Some(DialectType::Dremio) => {
23204                        // These dialects map BINARY to VARBINARY
23205                        self.write_keyword("VARBINARY");
23206                        if let Some(n) = length {
23207                            self.write(&format!("({})", n));
23208                        }
23209                    }
23210                    Some(DialectType::ClickHouse) => {
23211                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
23212                        if self.clickhouse_nullable_depth < 0 {
23213                            self.write("BINARY");
23214                        } else {
23215                            self.write("Nullable(BINARY");
23216                        }
23217                        if let Some(n) = length {
23218                            self.write(&format!("({})", n));
23219                        }
23220                        if self.clickhouse_nullable_depth >= 0 {
23221                            self.write(")");
23222                        }
23223                    }
23224                    _ => {
23225                        self.write_keyword("BINARY");
23226                        if let Some(n) = length {
23227                            self.write(&format!("({})", n));
23228                        }
23229                    }
23230                }
23231            }
23232            DataType::VarBinary { length } => {
23233                // Dialect-specific varbinary type mappings
23234                match self.config.dialect {
23235                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
23236                        self.write_keyword("BYTEA");
23237                        if let Some(n) = length {
23238                            self.write(&format!("({})", n));
23239                        }
23240                    }
23241                    Some(DialectType::Redshift) => {
23242                        self.write_keyword("VARBYTE");
23243                        if let Some(n) = length {
23244                            self.write(&format!("({})", n));
23245                        }
23246                    }
23247                    Some(DialectType::DuckDB)
23248                    | Some(DialectType::SQLite)
23249                    | Some(DialectType::Oracle) => {
23250                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
23251                        self.write_keyword("BLOB");
23252                        if let Some(n) = length {
23253                            self.write(&format!("({})", n));
23254                        }
23255                    }
23256                    Some(DialectType::Exasol) => {
23257                        // Exasol maps VARBINARY to VARCHAR
23258                        self.write_keyword("VARCHAR");
23259                    }
23260                    Some(DialectType::Spark)
23261                    | Some(DialectType::Hive)
23262                    | Some(DialectType::Databricks) => {
23263                        // Spark/Hive use BINARY instead of VARBINARY
23264                        self.write_keyword("BINARY");
23265                        if let Some(n) = length {
23266                            self.write(&format!("({})", n));
23267                        }
23268                    }
23269                    Some(DialectType::ClickHouse) => {
23270                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
23271                        self.write_clickhouse_type("String");
23272                    }
23273                    _ => {
23274                        self.write_keyword("VARBINARY");
23275                        if let Some(n) = length {
23276                            self.write(&format!("({})", n));
23277                        }
23278                    }
23279                }
23280            }
23281            DataType::Blob => {
23282                // Dialect-specific blob type mappings
23283                match self.config.dialect {
23284                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
23285                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
23286                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23287                        self.write_keyword("VARBINARY")
23288                    }
23289                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
23290                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
23291                    Some(DialectType::Presto)
23292                    | Some(DialectType::Trino)
23293                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
23294                    Some(DialectType::DuckDB) => {
23295                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
23296                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
23297                        self.write_keyword("VARBINARY");
23298                    }
23299                    Some(DialectType::Spark)
23300                    | Some(DialectType::Databricks)
23301                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
23302                    Some(DialectType::ClickHouse) => {
23303                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
23304                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
23305                        // This matches Python sqlglot behavior.
23306                        self.write("Nullable(String)");
23307                    }
23308                    _ => self.write_keyword("BLOB"),
23309                }
23310            }
23311            DataType::Bit { length } => {
23312                // Dialect-specific bit type mappings
23313                match self.config.dialect {
23314                    Some(DialectType::Dremio)
23315                    | Some(DialectType::Spark)
23316                    | Some(DialectType::Databricks)
23317                    | Some(DialectType::Hive)
23318                    | Some(DialectType::Snowflake)
23319                    | Some(DialectType::BigQuery)
23320                    | Some(DialectType::Presto)
23321                    | Some(DialectType::Trino)
23322                    | Some(DialectType::ClickHouse)
23323                    | Some(DialectType::Redshift) => {
23324                        // These dialects don't support BIT type, use BOOLEAN
23325                        self.write_keyword("BOOLEAN");
23326                    }
23327                    _ => {
23328                        self.write_keyword("BIT");
23329                        if let Some(n) = length {
23330                            self.write(&format!("({})", n));
23331                        }
23332                    }
23333                }
23334            }
23335            DataType::VarBit { length } => {
23336                self.write_keyword("VARBIT");
23337                if let Some(n) = length {
23338                    self.write(&format!("({})", n));
23339                }
23340            }
23341            DataType::Date => self.write_keyword("DATE"),
23342            DataType::Time {
23343                precision,
23344                timezone,
23345            } => {
23346                if *timezone {
23347                    // Dialect-specific TIME WITH TIME ZONE output
23348                    match self.config.dialect {
23349                        Some(DialectType::DuckDB) => {
23350                            // DuckDB: TIMETZ (drops precision)
23351                            self.write_keyword("TIMETZ");
23352                        }
23353                        Some(DialectType::PostgreSQL) => {
23354                            // PostgreSQL: TIMETZ or TIMETZ(p)
23355                            self.write_keyword("TIMETZ");
23356                            if let Some(p) = precision {
23357                                self.write(&format!("({})", p));
23358                            }
23359                        }
23360                        _ => {
23361                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
23362                            self.write_keyword("TIME");
23363                            if let Some(p) = precision {
23364                                self.write(&format!("({})", p));
23365                            }
23366                            self.write_keyword(" WITH TIME ZONE");
23367                        }
23368                    }
23369                } else {
23370                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
23371                    if matches!(
23372                        self.config.dialect,
23373                        Some(DialectType::Spark)
23374                            | Some(DialectType::Databricks)
23375                            | Some(DialectType::Hive)
23376                    ) {
23377                        self.write_keyword("TIMESTAMP");
23378                    } else {
23379                        self.write_keyword("TIME");
23380                        if let Some(p) = precision {
23381                            self.write(&format!("({})", p));
23382                        }
23383                    }
23384                }
23385            }
23386            DataType::Timestamp {
23387                precision,
23388                timezone,
23389            } => {
23390                // Dialect-specific timestamp type mappings
23391                match self.config.dialect {
23392                    Some(DialectType::ClickHouse) => {
23393                        self.write("DateTime");
23394                        if let Some(p) = precision {
23395                            self.write(&format!("({})", p));
23396                        }
23397                    }
23398                    Some(DialectType::TSQL) => {
23399                        if *timezone {
23400                            self.write_keyword("DATETIMEOFFSET");
23401                        } else {
23402                            self.write_keyword("DATETIME2");
23403                        }
23404                        if let Some(p) = precision {
23405                            self.write(&format!("({})", p));
23406                        }
23407                    }
23408                    Some(DialectType::MySQL) => {
23409                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
23410                        self.write_keyword("TIMESTAMP");
23411                        if let Some(p) = precision {
23412                            self.write(&format!("({})", p));
23413                        }
23414                    }
23415                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
23416                        // Doris/StarRocks: TIMESTAMP -> DATETIME
23417                        self.write_keyword("DATETIME");
23418                        if let Some(p) = precision {
23419                            self.write(&format!("({})", p));
23420                        }
23421                    }
23422                    Some(DialectType::BigQuery) => {
23423                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
23424                        if *timezone {
23425                            self.write_keyword("TIMESTAMP");
23426                        } else {
23427                            self.write_keyword("DATETIME");
23428                        }
23429                    }
23430                    Some(DialectType::DuckDB) => {
23431                        // DuckDB: TIMESTAMPTZ shorthand
23432                        if *timezone {
23433                            self.write_keyword("TIMESTAMPTZ");
23434                        } else {
23435                            self.write_keyword("TIMESTAMP");
23436                            if let Some(p) = precision {
23437                                self.write(&format!("({})", p));
23438                            }
23439                        }
23440                    }
23441                    _ => {
23442                        if *timezone && !self.config.tz_to_with_time_zone {
23443                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
23444                            self.write_keyword("TIMESTAMPTZ");
23445                            if let Some(p) = precision {
23446                                self.write(&format!("({})", p));
23447                            }
23448                        } else {
23449                            self.write_keyword("TIMESTAMP");
23450                            if let Some(p) = precision {
23451                                self.write(&format!("({})", p));
23452                            }
23453                            if *timezone {
23454                                self.write_space();
23455                                self.write_keyword("WITH TIME ZONE");
23456                            }
23457                        }
23458                    }
23459                }
23460            }
23461            DataType::Interval { unit, to } => {
23462                self.write_keyword("INTERVAL");
23463                if let Some(u) = unit {
23464                    self.write_space();
23465                    self.write_keyword(u);
23466                }
23467                // Handle range intervals like DAY TO HOUR
23468                if let Some(t) = to {
23469                    self.write_space();
23470                    self.write_keyword("TO");
23471                    self.write_space();
23472                    self.write_keyword(t);
23473                }
23474            }
23475            DataType::Json => {
23476                // Dialect-specific JSON type mappings
23477                match self.config.dialect {
23478                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
23479                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
23480                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
23481                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
23482                    _ => self.write_keyword("JSON"),
23483                }
23484            }
23485            DataType::JsonB => {
23486                // JSONB is PostgreSQL specific, but Doris also supports it
23487                match self.config.dialect {
23488                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
23489                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
23490                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
23491                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
23492                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
23493                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
23494                }
23495            }
23496            DataType::Uuid => {
23497                // Dialect-specific UUID type mappings
23498                match self.config.dialect {
23499                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
23500                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
23501                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
23502                    Some(DialectType::BigQuery)
23503                    | Some(DialectType::Spark)
23504                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
23505                    _ => self.write_keyword("UUID"),
23506                }
23507            }
23508            DataType::Array {
23509                element_type,
23510                dimension,
23511            } => {
23512                // Dialect-specific array syntax
23513                match self.config.dialect {
23514                    Some(DialectType::PostgreSQL)
23515                    | Some(DialectType::Redshift)
23516                    | Some(DialectType::DuckDB) => {
23517                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
23518                        self.generate_data_type(element_type)?;
23519                        if let Some(dim) = dimension {
23520                            self.write(&format!("[{}]", dim));
23521                        } else {
23522                            self.write("[]");
23523                        }
23524                    }
23525                    Some(DialectType::BigQuery) => {
23526                        self.write_keyword("ARRAY<");
23527                        self.generate_data_type(element_type)?;
23528                        self.write(">");
23529                    }
23530                    Some(DialectType::Snowflake)
23531                    | Some(DialectType::Presto)
23532                    | Some(DialectType::Trino)
23533                    | Some(DialectType::ClickHouse) => {
23534                        // These dialects use Array(TYPE) parentheses syntax
23535                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23536                            self.write("Array(");
23537                        } else {
23538                            self.write_keyword("ARRAY(");
23539                        }
23540                        self.generate_data_type(element_type)?;
23541                        self.write(")");
23542                    }
23543                    Some(DialectType::TSQL)
23544                    | Some(DialectType::MySQL)
23545                    | Some(DialectType::Oracle) => {
23546                        // These dialects don't have native array types
23547                        // Fall back to JSON or use native workarounds
23548                        match self.config.dialect {
23549                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
23550                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
23551                            _ => self.write_keyword("JSON"),
23552                        }
23553                    }
23554                    _ => {
23555                        // Default: use angle bracket syntax (ARRAY<T>)
23556                        self.write_keyword("ARRAY<");
23557                        self.generate_data_type(element_type)?;
23558                        self.write(">");
23559                    }
23560                }
23561            }
23562            DataType::List { element_type } => {
23563                // Materialize: element_type LIST (postfix syntax)
23564                self.generate_data_type(element_type)?;
23565                self.write_keyword(" LIST");
23566            }
23567            DataType::Map {
23568                key_type,
23569                value_type,
23570            } => {
23571                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
23572                match self.config.dialect {
23573                    Some(DialectType::Materialize) => {
23574                        // Materialize: MAP[key_type => value_type]
23575                        self.write_keyword("MAP[");
23576                        self.generate_data_type(key_type)?;
23577                        self.write(" => ");
23578                        self.generate_data_type(value_type)?;
23579                        self.write("]");
23580                    }
23581                    Some(DialectType::Snowflake)
23582                    | Some(DialectType::RisingWave)
23583                    | Some(DialectType::DuckDB)
23584                    | Some(DialectType::Presto)
23585                    | Some(DialectType::Trino)
23586                    | Some(DialectType::Athena) => {
23587                        self.write_keyword("MAP(");
23588                        self.generate_data_type(key_type)?;
23589                        self.write(", ");
23590                        self.generate_data_type(value_type)?;
23591                        self.write(")");
23592                    }
23593                    Some(DialectType::ClickHouse) => {
23594                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
23595                        // Key types must NOT be wrapped in Nullable
23596                        self.write("Map(");
23597                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
23598                        self.generate_data_type(key_type)?;
23599                        self.clickhouse_nullable_depth = 0;
23600                        self.write(", ");
23601                        self.generate_data_type(value_type)?;
23602                        self.write(")");
23603                    }
23604                    _ => {
23605                        self.write_keyword("MAP<");
23606                        self.generate_data_type(key_type)?;
23607                        self.write(", ");
23608                        self.generate_data_type(value_type)?;
23609                        self.write(">");
23610                    }
23611                }
23612            }
23613            DataType::Vector {
23614                element_type,
23615                dimension,
23616            } => {
23617                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
23618                    // SingleStore format: VECTOR(dimension, type_alias)
23619                    self.write_keyword("VECTOR(");
23620                    if let Some(dim) = dimension {
23621                        self.write(&dim.to_string());
23622                    }
23623                    // Map type back to SingleStore alias
23624                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
23625                        DataType::TinyInt { .. } => Some("I8"),
23626                        DataType::SmallInt { .. } => Some("I16"),
23627                        DataType::Int { .. } => Some("I32"),
23628                        DataType::BigInt { .. } => Some("I64"),
23629                        DataType::Float { .. } => Some("F32"),
23630                        DataType::Double { .. } => Some("F64"),
23631                        _ => None,
23632                    });
23633                    if let Some(alias) = type_alias {
23634                        if dimension.is_some() {
23635                            self.write(", ");
23636                        }
23637                        self.write(alias);
23638                    }
23639                    self.write(")");
23640                } else {
23641                    // Snowflake format: VECTOR(type, dimension)
23642                    self.write_keyword("VECTOR(");
23643                    if let Some(ref et) = element_type {
23644                        self.generate_data_type(et)?;
23645                        if dimension.is_some() {
23646                            self.write(", ");
23647                        }
23648                    }
23649                    if let Some(dim) = dimension {
23650                        self.write(&dim.to_string());
23651                    }
23652                    self.write(")");
23653                }
23654            }
23655            DataType::Object { fields, modifier } => {
23656                self.write_keyword("OBJECT(");
23657                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
23658                    if i > 0 {
23659                        self.write(", ");
23660                    }
23661                    self.write(name);
23662                    self.write(" ");
23663                    self.generate_data_type(dt)?;
23664                    if *not_null {
23665                        self.write_keyword(" NOT NULL");
23666                    }
23667                }
23668                self.write(")");
23669                if let Some(mod_str) = modifier {
23670                    self.write(" ");
23671                    self.write_keyword(mod_str);
23672                }
23673            }
23674            DataType::Struct { fields, nested } => {
23675                // Dialect-specific struct type mappings
23676                match self.config.dialect {
23677                    Some(DialectType::Snowflake) => {
23678                        // Snowflake maps STRUCT to OBJECT
23679                        self.write_keyword("OBJECT(");
23680                        for (i, field) in fields.iter().enumerate() {
23681                            if i > 0 {
23682                                self.write(", ");
23683                            }
23684                            if !field.name.is_empty() {
23685                                self.write(&field.name);
23686                                self.write(" ");
23687                            }
23688                            self.generate_data_type(&field.data_type)?;
23689                        }
23690                        self.write(")");
23691                    }
23692                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
23693                        // Presto/Trino use ROW(name TYPE, ...) syntax
23694                        self.write_keyword("ROW(");
23695                        for (i, field) in fields.iter().enumerate() {
23696                            if i > 0 {
23697                                self.write(", ");
23698                            }
23699                            if !field.name.is_empty() {
23700                                self.write(&field.name);
23701                                self.write(" ");
23702                            }
23703                            self.generate_data_type(&field.data_type)?;
23704                        }
23705                        self.write(")");
23706                    }
23707                    Some(DialectType::DuckDB) => {
23708                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
23709                        self.write_keyword("STRUCT(");
23710                        for (i, field) in fields.iter().enumerate() {
23711                            if i > 0 {
23712                                self.write(", ");
23713                            }
23714                            if !field.name.is_empty() {
23715                                self.write(&field.name);
23716                                self.write(" ");
23717                            }
23718                            self.generate_data_type(&field.data_type)?;
23719                        }
23720                        self.write(")");
23721                    }
23722                    Some(DialectType::ClickHouse) => {
23723                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
23724                        self.write("Tuple(");
23725                        for (i, field) in fields.iter().enumerate() {
23726                            if i > 0 {
23727                                self.write(", ");
23728                            }
23729                            if !field.name.is_empty() {
23730                                self.write(&field.name);
23731                                self.write(" ");
23732                            }
23733                            self.generate_data_type(&field.data_type)?;
23734                        }
23735                        self.write(")");
23736                    }
23737                    Some(DialectType::SingleStore) => {
23738                        // SingleStore uses RECORD(name TYPE, ...) for struct types
23739                        self.write_keyword("RECORD(");
23740                        for (i, field) in fields.iter().enumerate() {
23741                            if i > 0 {
23742                                self.write(", ");
23743                            }
23744                            if !field.name.is_empty() {
23745                                self.write(&field.name);
23746                                self.write(" ");
23747                            }
23748                            self.generate_data_type(&field.data_type)?;
23749                        }
23750                        self.write(")");
23751                    }
23752                    _ => {
23753                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
23754                        let force_angle_brackets = matches!(
23755                            self.config.dialect,
23756                            Some(DialectType::Hive)
23757                                | Some(DialectType::Spark)
23758                                | Some(DialectType::Databricks)
23759                        );
23760                        if *nested && !force_angle_brackets {
23761                            self.write_keyword("STRUCT(");
23762                            for (i, field) in fields.iter().enumerate() {
23763                                if i > 0 {
23764                                    self.write(", ");
23765                                }
23766                                if !field.name.is_empty() {
23767                                    self.write(&field.name);
23768                                    self.write(" ");
23769                                }
23770                                self.generate_data_type(&field.data_type)?;
23771                            }
23772                            self.write(")");
23773                        } else {
23774                            self.write_keyword("STRUCT<");
23775                            for (i, field) in fields.iter().enumerate() {
23776                                if i > 0 {
23777                                    self.write(", ");
23778                                }
23779                                if !field.name.is_empty() {
23780                                    // Named field: name TYPE (with configurable separator for Hive)
23781                                    self.write(&field.name);
23782                                    self.write(self.config.struct_field_sep);
23783                                }
23784                                // For anonymous fields, just output the type
23785                                self.generate_data_type(&field.data_type)?;
23786                                // Spark/Databricks: Output COMMENT clause if present
23787                                if let Some(comment) = &field.comment {
23788                                    self.write(" COMMENT '");
23789                                    self.write(comment);
23790                                    self.write("'");
23791                                }
23792                                // BigQuery: Output OPTIONS clause if present
23793                                if !field.options.is_empty() {
23794                                    self.write(" ");
23795                                    self.generate_options_clause(&field.options)?;
23796                                }
23797                            }
23798                            self.write(">");
23799                        }
23800                    }
23801                }
23802            }
23803            DataType::Enum {
23804                values,
23805                assignments,
23806            } => {
23807                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
23808                // ClickHouse: Enum('hello' = 1, 'world' = 2)
23809                if self.config.dialect == Some(DialectType::ClickHouse) {
23810                    self.write("Enum(");
23811                } else {
23812                    self.write_keyword("ENUM(");
23813                }
23814                for (i, val) in values.iter().enumerate() {
23815                    if i > 0 {
23816                        self.write(", ");
23817                    }
23818                    self.write("'");
23819                    self.write(val);
23820                    self.write("'");
23821                    if let Some(Some(assignment)) = assignments.get(i) {
23822                        self.write(" = ");
23823                        self.write(assignment);
23824                    }
23825                }
23826                self.write(")");
23827            }
23828            DataType::Set { values } => {
23829                // MySQL SET type: SET('a', 'b', 'c')
23830                self.write_keyword("SET(");
23831                for (i, val) in values.iter().enumerate() {
23832                    if i > 0 {
23833                        self.write(", ");
23834                    }
23835                    self.write("'");
23836                    self.write(val);
23837                    self.write("'");
23838                }
23839                self.write(")");
23840            }
23841            DataType::Union { fields } => {
23842                // DuckDB UNION type: UNION(num INT, str TEXT)
23843                self.write_keyword("UNION(");
23844                for (i, (name, dt)) in fields.iter().enumerate() {
23845                    if i > 0 {
23846                        self.write(", ");
23847                    }
23848                    if !name.is_empty() {
23849                        self.write(name);
23850                        self.write(" ");
23851                    }
23852                    self.generate_data_type(dt)?;
23853                }
23854                self.write(")");
23855            }
23856            DataType::Nullable { inner } => {
23857                // ClickHouse: Nullable(T), other dialects: just the inner type
23858                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23859                    self.write("Nullable(");
23860                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
23861                    let saved_depth = self.clickhouse_nullable_depth;
23862                    self.clickhouse_nullable_depth = -1;
23863                    self.generate_data_type(inner)?;
23864                    self.clickhouse_nullable_depth = saved_depth;
23865                    self.write(")");
23866                } else {
23867                    // Map ClickHouse-specific custom type names to standard types
23868                    match inner.as_ref() {
23869                        DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME") => {
23870                            self.generate_data_type(&DataType::Timestamp {
23871                                precision: None,
23872                                timezone: false,
23873                            })?;
23874                        }
23875                        _ => {
23876                            self.generate_data_type(inner)?;
23877                        }
23878                    }
23879                }
23880            }
23881            DataType::Custom { name } => {
23882                // Handle dialect-specific type transformations
23883                let name_upper = name.to_ascii_uppercase();
23884                match self.config.dialect {
23885                    Some(DialectType::ClickHouse) => {
23886                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
23887                            (name_upper[..idx].to_string(), &name[idx..])
23888                        } else {
23889                            (name_upper.clone(), "")
23890                        };
23891                        let mapped = match base_upper.as_str() {
23892                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
23893                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
23894                            "DATETIME64" => "DateTime64",
23895                            "DATE32" => "Date32",
23896                            "INT" => "Int32",
23897                            "MEDIUMINT" => "Int32",
23898                            "INT8" => "Int8",
23899                            "INT16" => "Int16",
23900                            "INT32" => "Int32",
23901                            "INT64" => "Int64",
23902                            "INT128" => "Int128",
23903                            "INT256" => "Int256",
23904                            "UINT8" => "UInt8",
23905                            "UINT16" => "UInt16",
23906                            "UINT32" => "UInt32",
23907                            "UINT64" => "UInt64",
23908                            "UINT128" => "UInt128",
23909                            "UINT256" => "UInt256",
23910                            "FLOAT32" => "Float32",
23911                            "FLOAT64" => "Float64",
23912                            "DECIMAL32" => "Decimal32",
23913                            "DECIMAL64" => "Decimal64",
23914                            "DECIMAL128" => "Decimal128",
23915                            "DECIMAL256" => "Decimal256",
23916                            "ENUM" => "Enum",
23917                            "ENUM8" => "Enum8",
23918                            "ENUM16" => "Enum16",
23919                            "FIXEDSTRING" => "FixedString",
23920                            "NESTED" => "Nested",
23921                            "LOWCARDINALITY" => "LowCardinality",
23922                            "NULLABLE" => "Nullable",
23923                            "IPV4" => "IPv4",
23924                            "IPV6" => "IPv6",
23925                            "POINT" => "Point",
23926                            "RING" => "Ring",
23927                            "LINESTRING" => "LineString",
23928                            "MULTILINESTRING" => "MultiLineString",
23929                            "POLYGON" => "Polygon",
23930                            "MULTIPOLYGON" => "MultiPolygon",
23931                            "AGGREGATEFUNCTION" => "AggregateFunction",
23932                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
23933                            "DYNAMIC" => "Dynamic",
23934                            _ => "",
23935                        };
23936                        if mapped.is_empty() {
23937                            self.write(name);
23938                        } else {
23939                            self.write(mapped);
23940                            self.write(suffix);
23941                        }
23942                    }
23943                    Some(DialectType::MySQL)
23944                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
23945                    {
23946                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
23947                        self.write_keyword("TIMESTAMP");
23948                    }
23949                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
23950                        self.write_keyword("SQL_VARIANT");
23951                    }
23952                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
23953                        self.write_keyword("DECIMAL(38, 5)");
23954                    }
23955                    Some(DialectType::Exasol) => {
23956                        // Exasol type mappings for custom types
23957                        match name_upper.as_str() {
23958                            // Binary types → VARCHAR
23959                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
23960                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
23961                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
23962                            // Integer types
23963                            "MEDIUMINT" => self.write_keyword("INT"),
23964                            // Decimal types → DECIMAL
23965                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
23966                                self.write_keyword("DECIMAL")
23967                            }
23968                            // Timestamp types
23969                            "DATETIME" => self.write_keyword("TIMESTAMP"),
23970                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
23971                            _ => self.write(name),
23972                        }
23973                    }
23974                    Some(DialectType::Dremio) => {
23975                        // Dremio type mappings for custom types
23976                        match name_upper.as_str() {
23977                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
23978                            "ARRAY" => self.write_keyword("LIST"),
23979                            "NCHAR" => self.write_keyword("VARCHAR"),
23980                            _ => self.write(name),
23981                        }
23982                    }
23983                    // Map dialect-specific custom types to standard SQL types for other dialects
23984                    _ => {
23985                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
23986                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
23987                            (name_upper[..idx].to_string(), Some(&name[idx..]))
23988                        } else {
23989                            (name_upper.clone(), None)
23990                        };
23991
23992                        match base_upper.as_str() {
23993                            "INT64"
23994                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
23995                            {
23996                                self.write_keyword("BIGINT");
23997                            }
23998                            "FLOAT64"
23999                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24000                            {
24001                                self.write_keyword("DOUBLE");
24002                            }
24003                            "BOOL"
24004                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24005                            {
24006                                self.write_keyword("BOOLEAN");
24007                            }
24008                            "BYTES"
24009                                if matches!(
24010                                    self.config.dialect,
24011                                    Some(DialectType::Spark)
24012                                        | Some(DialectType::Hive)
24013                                        | Some(DialectType::Databricks)
24014                                ) =>
24015                            {
24016                                self.write_keyword("BINARY");
24017                            }
24018                            "BYTES"
24019                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
24020                            {
24021                                self.write_keyword("VARBINARY");
24022                            }
24023                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
24024                            "DATETIME2" | "SMALLDATETIME"
24025                                if !matches!(
24026                                    self.config.dialect,
24027                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24028                                ) =>
24029                            {
24030                                // PostgreSQL preserves precision, others don't
24031                                if matches!(
24032                                    self.config.dialect,
24033                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
24034                                ) {
24035                                    self.write_keyword("TIMESTAMP");
24036                                    if let Some(args) = _args_str {
24037                                        self.write(args);
24038                                    }
24039                                } else {
24040                                    self.write_keyword("TIMESTAMP");
24041                                }
24042                            }
24043                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
24044                            "DATETIMEOFFSET"
24045                                if !matches!(
24046                                    self.config.dialect,
24047                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24048                                ) =>
24049                            {
24050                                if matches!(
24051                                    self.config.dialect,
24052                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
24053                                ) {
24054                                    self.write_keyword("TIMESTAMPTZ");
24055                                    if let Some(args) = _args_str {
24056                                        self.write(args);
24057                                    }
24058                                } else {
24059                                    self.write_keyword("TIMESTAMPTZ");
24060                                }
24061                            }
24062                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
24063                            "UNIQUEIDENTIFIER"
24064                                if !matches!(
24065                                    self.config.dialect,
24066                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24067                                ) =>
24068                            {
24069                                match self.config.dialect {
24070                                    Some(DialectType::Spark)
24071                                    | Some(DialectType::Databricks)
24072                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
24073                                    _ => self.write_keyword("UUID"),
24074                                }
24075                            }
24076                            // TSQL BIT -> BOOLEAN for most dialects
24077                            "BIT"
24078                                if !matches!(
24079                                    self.config.dialect,
24080                                    Some(DialectType::TSQL)
24081                                        | Some(DialectType::Fabric)
24082                                        | Some(DialectType::PostgreSQL)
24083                                        | Some(DialectType::MySQL)
24084                                        | Some(DialectType::DuckDB)
24085                                ) =>
24086                            {
24087                                self.write_keyword("BOOLEAN");
24088                            }
24089                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
24090                            "NVARCHAR"
24091                                if !matches!(
24092                                    self.config.dialect,
24093                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24094                                ) =>
24095                            {
24096                                match self.config.dialect {
24097                                    Some(DialectType::Oracle) => {
24098                                        // Oracle: NVARCHAR -> NVARCHAR2
24099                                        self.write_keyword("NVARCHAR2");
24100                                        if let Some(args) = _args_str {
24101                                            self.write(args);
24102                                        }
24103                                    }
24104                                    Some(DialectType::BigQuery) => {
24105                                        // BigQuery: NVARCHAR -> STRING
24106                                        self.write_keyword("STRING");
24107                                    }
24108                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
24109                                        self.write_keyword("TEXT");
24110                                        if let Some(args) = _args_str {
24111                                            self.write(args);
24112                                        }
24113                                    }
24114                                    Some(DialectType::Hive) => {
24115                                        // Hive: NVARCHAR -> STRING
24116                                        self.write_keyword("STRING");
24117                                    }
24118                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
24119                                        if _args_str.is_some() {
24120                                            self.write_keyword("VARCHAR");
24121                                            self.write(_args_str.unwrap());
24122                                        } else {
24123                                            self.write_keyword("STRING");
24124                                        }
24125                                    }
24126                                    _ => {
24127                                        self.write_keyword("VARCHAR");
24128                                        if let Some(args) = _args_str {
24129                                            self.write(args);
24130                                        }
24131                                    }
24132                                }
24133                            }
24134                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
24135                            "NCHAR"
24136                                if !matches!(
24137                                    self.config.dialect,
24138                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
24139                                ) =>
24140                            {
24141                                match self.config.dialect {
24142                                    Some(DialectType::Oracle) => {
24143                                        // Oracle natively supports NCHAR
24144                                        self.write_keyword("NCHAR");
24145                                        if let Some(args) = _args_str {
24146                                            self.write(args);
24147                                        }
24148                                    }
24149                                    Some(DialectType::BigQuery) => {
24150                                        // BigQuery: NCHAR -> STRING
24151                                        self.write_keyword("STRING");
24152                                    }
24153                                    Some(DialectType::Hive) => {
24154                                        // Hive: NCHAR -> STRING
24155                                        self.write_keyword("STRING");
24156                                    }
24157                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
24158                                        self.write_keyword("TEXT");
24159                                        if let Some(args) = _args_str {
24160                                            self.write(args);
24161                                        }
24162                                    }
24163                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
24164                                        if _args_str.is_some() {
24165                                            self.write_keyword("CHAR");
24166                                            self.write(_args_str.unwrap());
24167                                        } else {
24168                                            self.write_keyword("STRING");
24169                                        }
24170                                    }
24171                                    _ => {
24172                                        self.write_keyword("CHAR");
24173                                        if let Some(args) = _args_str {
24174                                            self.write(args);
24175                                        }
24176                                    }
24177                                }
24178                            }
24179                            // MySQL text variant types -> map to appropriate target type
24180                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
24181                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
24182                                Some(DialectType::MySQL)
24183                                | Some(DialectType::SingleStore)
24184                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
24185                                Some(DialectType::Spark)
24186                                | Some(DialectType::Databricks)
24187                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
24188                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
24189                                Some(DialectType::Presto)
24190                                | Some(DialectType::Trino)
24191                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
24192                                Some(DialectType::Snowflake)
24193                                | Some(DialectType::Redshift)
24194                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
24195                                _ => self.write_keyword("TEXT"),
24196                            },
24197                            // MySQL blob variant types -> map to appropriate target type
24198                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
24199                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
24200                                Some(DialectType::MySQL)
24201                                | Some(DialectType::SingleStore)
24202                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
24203                                Some(DialectType::Spark)
24204                                | Some(DialectType::Databricks)
24205                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
24206                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
24207                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
24208                                Some(DialectType::Presto)
24209                                | Some(DialectType::Trino)
24210                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
24211                                Some(DialectType::Snowflake)
24212                                | Some(DialectType::Redshift)
24213                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
24214                                _ => self.write_keyword("BLOB"),
24215                            },
24216                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
24217                            "LONGVARCHAR" => match self.config.dialect {
24218                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
24219                                _ => self.write_keyword("VARCHAR"),
24220                            },
24221                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
24222                            "DATETIME" => {
24223                                match self.config.dialect {
24224                                    Some(DialectType::MySQL)
24225                                    | Some(DialectType::Doris)
24226                                    | Some(DialectType::StarRocks)
24227                                    | Some(DialectType::TSQL)
24228                                    | Some(DialectType::Fabric)
24229                                    | Some(DialectType::BigQuery)
24230                                    | Some(DialectType::SQLite)
24231                                    | Some(DialectType::Snowflake) => {
24232                                        self.write_keyword("DATETIME");
24233                                        if let Some(args) = _args_str {
24234                                            self.write(args);
24235                                        }
24236                                    }
24237                                    Some(_) => {
24238                                        // Only map to TIMESTAMP when we have a specific target dialect
24239                                        self.write_keyword("TIMESTAMP");
24240                                        if let Some(args) = _args_str {
24241                                            self.write(args);
24242                                        }
24243                                    }
24244                                    None => {
24245                                        // No dialect - preserve original
24246                                        self.write(name);
24247                                    }
24248                                }
24249                            }
24250                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
24251                            "VARCHAR2"
24252                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
24253                            {
24254                                match self.config.dialect {
24255                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
24256                                        self.write_keyword("TEXT");
24257                                    }
24258                                    Some(DialectType::Hive)
24259                                    | Some(DialectType::Spark)
24260                                    | Some(DialectType::Databricks)
24261                                    | Some(DialectType::BigQuery)
24262                                    | Some(DialectType::ClickHouse)
24263                                    | Some(DialectType::StarRocks)
24264                                    | Some(DialectType::Doris) => {
24265                                        self.write_keyword("STRING");
24266                                    }
24267                                    _ => {
24268                                        self.write_keyword("VARCHAR");
24269                                        if let Some(args) = _args_str {
24270                                            self.write(args);
24271                                        }
24272                                    }
24273                                }
24274                            }
24275                            "NVARCHAR2"
24276                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
24277                            {
24278                                match self.config.dialect {
24279                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
24280                                        self.write_keyword("TEXT");
24281                                    }
24282                                    Some(DialectType::Hive)
24283                                    | Some(DialectType::Spark)
24284                                    | Some(DialectType::Databricks)
24285                                    | Some(DialectType::BigQuery)
24286                                    | Some(DialectType::ClickHouse)
24287                                    | Some(DialectType::StarRocks)
24288                                    | Some(DialectType::Doris) => {
24289                                        self.write_keyword("STRING");
24290                                    }
24291                                    _ => {
24292                                        self.write_keyword("VARCHAR");
24293                                        if let Some(args) = _args_str {
24294                                            self.write(args);
24295                                        }
24296                                    }
24297                                }
24298                            }
24299                            _ => self.write(name),
24300                        }
24301                    }
24302                }
24303            }
24304            DataType::Geometry { subtype, srid } => {
24305                // Dialect-specific geometry type mappings
24306                match self.config.dialect {
24307                    Some(DialectType::MySQL) => {
24308                        // MySQL uses POINT SRID 4326 syntax for specific types
24309                        if let Some(sub) = subtype {
24310                            self.write_keyword(sub);
24311                            if let Some(s) = srid {
24312                                self.write(" SRID ");
24313                                self.write(&s.to_string());
24314                            }
24315                        } else {
24316                            self.write_keyword("GEOMETRY");
24317                        }
24318                    }
24319                    Some(DialectType::BigQuery) => {
24320                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
24321                        self.write_keyword("GEOGRAPHY");
24322                    }
24323                    Some(DialectType::Teradata) => {
24324                        // Teradata uses ST_GEOMETRY
24325                        self.write_keyword("ST_GEOMETRY");
24326                        if subtype.is_some() || srid.is_some() {
24327                            self.write("(");
24328                            if let Some(sub) = subtype {
24329                                self.write_keyword(sub);
24330                            }
24331                            if let Some(s) = srid {
24332                                if subtype.is_some() {
24333                                    self.write(", ");
24334                                }
24335                                self.write(&s.to_string());
24336                            }
24337                            self.write(")");
24338                        }
24339                    }
24340                    _ => {
24341                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
24342                        self.write_keyword("GEOMETRY");
24343                        if subtype.is_some() || srid.is_some() {
24344                            self.write("(");
24345                            if let Some(sub) = subtype {
24346                                self.write_keyword(sub);
24347                            }
24348                            if let Some(s) = srid {
24349                                if subtype.is_some() {
24350                                    self.write(", ");
24351                                }
24352                                self.write(&s.to_string());
24353                            }
24354                            self.write(")");
24355                        }
24356                    }
24357                }
24358            }
24359            DataType::Geography { subtype, srid } => {
24360                // Dialect-specific geography type mappings
24361                match self.config.dialect {
24362                    Some(DialectType::MySQL) => {
24363                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
24364                        if let Some(sub) = subtype {
24365                            self.write_keyword(sub);
24366                        } else {
24367                            self.write_keyword("GEOMETRY");
24368                        }
24369                        // Geography implies SRID 4326 (WGS84)
24370                        let effective_srid = srid.unwrap_or(4326);
24371                        self.write(" SRID ");
24372                        self.write(&effective_srid.to_string());
24373                    }
24374                    Some(DialectType::BigQuery) => {
24375                        // BigQuery uses simple GEOGRAPHY without parameters
24376                        self.write_keyword("GEOGRAPHY");
24377                    }
24378                    Some(DialectType::Snowflake) => {
24379                        // Snowflake uses GEOGRAPHY without parameters
24380                        self.write_keyword("GEOGRAPHY");
24381                    }
24382                    _ => {
24383                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
24384                        self.write_keyword("GEOGRAPHY");
24385                        if subtype.is_some() || srid.is_some() {
24386                            self.write("(");
24387                            if let Some(sub) = subtype {
24388                                self.write_keyword(sub);
24389                            }
24390                            if let Some(s) = srid {
24391                                if subtype.is_some() {
24392                                    self.write(", ");
24393                                }
24394                                self.write(&s.to_string());
24395                            }
24396                            self.write(")");
24397                        }
24398                    }
24399                }
24400            }
24401            DataType::CharacterSet { name } => {
24402                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
24403                self.write_keyword("CHAR CHARACTER SET ");
24404                self.write(name);
24405            }
24406            _ => self.write("UNKNOWN"),
24407        }
24408        Ok(())
24409    }
24410
24411    // === Helper methods ===
24412
24413    #[inline]
24414    fn write(&mut self, s: &str) {
24415        self.output.push_str(s);
24416    }
24417
24418    #[inline]
24419    fn write_space(&mut self) {
24420        self.output.push(' ');
24421    }
24422
24423    #[inline]
24424    fn write_keyword(&mut self, keyword: &str) {
24425        if self.config.uppercase_keywords {
24426            self.output.push_str(keyword);
24427        } else {
24428            for b in keyword.bytes() {
24429                self.output.push(b.to_ascii_lowercase() as char);
24430            }
24431        }
24432    }
24433
24434    /// Write a function name respecting the normalize_functions config setting
24435    fn write_func_name(&mut self, name: &str) {
24436        let normalized = self.normalize_func_name(name);
24437        self.output.push_str(normalized.as_ref());
24438    }
24439
24440    /// Convert strptime format string to Exasol format string
24441    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
24442    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
24443    fn convert_strptime_to_exasol_format(format: &str) -> String {
24444        let mut result = String::new();
24445        let chars: Vec<char> = format.chars().collect();
24446        let mut i = 0;
24447        while i < chars.len() {
24448            if chars[i] == '%' && i + 1 < chars.len() {
24449                let spec = chars[i + 1];
24450                let exasol_spec = match spec {
24451                    'Y' => "YYYY",
24452                    'y' => "YY",
24453                    'm' => "MM",
24454                    'd' => "DD",
24455                    'H' => "HH",
24456                    'M' => "MI",
24457                    'S' => "SS",
24458                    'a' => "DY",    // abbreviated weekday name
24459                    'A' => "DAY",   // full weekday name
24460                    'b' => "MON",   // abbreviated month name
24461                    'B' => "MONTH", // full month name
24462                    'I' => "H12",   // 12-hour format
24463                    'u' => "ID",    // ISO weekday (1-7)
24464                    'V' => "IW",    // ISO week number
24465                    'G' => "IYYY",  // ISO year
24466                    'W' => "UW",    // Week number (Monday as first day)
24467                    'U' => "UW",    // Week number (Sunday as first day)
24468                    'z' => "Z",     // timezone offset
24469                    _ => {
24470                        // Unknown specifier, keep as-is
24471                        result.push('%');
24472                        result.push(spec);
24473                        i += 2;
24474                        continue;
24475                    }
24476                };
24477                result.push_str(exasol_spec);
24478                i += 2;
24479            } else {
24480                result.push(chars[i]);
24481                i += 1;
24482            }
24483        }
24484        result
24485    }
24486
24487    /// Convert strptime format string to PostgreSQL/Redshift format string
24488    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
24489    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
24490    fn convert_strptime_to_postgres_format(format: &str) -> String {
24491        let mut result = String::new();
24492        let chars: Vec<char> = format.chars().collect();
24493        let mut i = 0;
24494        while i < chars.len() {
24495            if chars[i] == '%' && i + 1 < chars.len() {
24496                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
24497                if chars[i + 1] == '-' && i + 2 < chars.len() {
24498                    let spec = chars[i + 2];
24499                    let pg_spec = match spec {
24500                        'd' => "FMDD",
24501                        'm' => "FMMM",
24502                        'H' => "FMHH24",
24503                        'M' => "FMMI",
24504                        'S' => "FMSS",
24505                        _ => {
24506                            result.push('%');
24507                            result.push('-');
24508                            result.push(spec);
24509                            i += 3;
24510                            continue;
24511                        }
24512                    };
24513                    result.push_str(pg_spec);
24514                    i += 3;
24515                    continue;
24516                }
24517                let spec = chars[i + 1];
24518                let pg_spec = match spec {
24519                    'Y' => "YYYY",
24520                    'y' => "YY",
24521                    'm' => "MM",
24522                    'd' => "DD",
24523                    'H' => "HH24",
24524                    'I' => "HH12",
24525                    'M' => "MI",
24526                    'S' => "SS",
24527                    'f' => "US",      // microseconds
24528                    'u' => "D",       // day of week (1=Monday)
24529                    'j' => "DDD",     // day of year
24530                    'z' => "OF",      // UTC offset
24531                    'Z' => "TZ",      // timezone name
24532                    'A' => "TMDay",   // full weekday name
24533                    'a' => "TMDy",    // abbreviated weekday name
24534                    'b' => "TMMon",   // abbreviated month name
24535                    'B' => "TMMonth", // full month name
24536                    'U' => "WW",      // week number
24537                    _ => {
24538                        // Unknown specifier, keep as-is
24539                        result.push('%');
24540                        result.push(spec);
24541                        i += 2;
24542                        continue;
24543                    }
24544                };
24545                result.push_str(pg_spec);
24546                i += 2;
24547            } else {
24548                result.push(chars[i]);
24549                i += 1;
24550            }
24551        }
24552        result
24553    }
24554
24555    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
24556    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
24557        if self.config.limit_only_literals {
24558            if let Some(value) = Self::try_evaluate_constant(expr) {
24559                self.write(&value.to_string());
24560                return Ok(());
24561            }
24562        }
24563        self.generate_expression(expr)
24564    }
24565
24566    /// Format a comment with proper spacing.
24567    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
24568    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
24569    fn write_formatted_comment(&mut self, comment: &str) {
24570        // Normalize all comments to block comment format /* ... */
24571        // This matches Python sqlglot behavior which always outputs block comments
24572        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
24573            // Already block comment - extract inner content
24574            // Preserve internal whitespace, but ensure at least one space padding
24575            &comment[2..comment.len() - 2]
24576        } else if comment.starts_with("--") {
24577            // Line comment - extract content after --
24578            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
24579            &comment[2..]
24580        } else {
24581            // Raw content (no delimiters)
24582            comment
24583        };
24584        // Skip empty comments (e.g., bare "--" with no content)
24585        if content.trim().is_empty() {
24586            return;
24587        }
24588        // Escape nested block comment markers to prevent premature closure or unintended nesting.
24589        // This matches Python sqlglot's sanitize_comment behavior.
24590        let sanitized = content.replace("*/", "* /").replace("/*", "/ *");
24591        let content = &sanitized;
24592        // Ensure at least one space after /* and before */
24593        self.output.push_str("/*");
24594        if !content.starts_with(' ') {
24595            self.output.push(' ');
24596        }
24597        self.output.push_str(content);
24598        if !content.ends_with(' ') {
24599            self.output.push(' ');
24600        }
24601        self.output.push_str("*/");
24602    }
24603
24604    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
24605    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
24606    fn escape_block_for_single_quote(&self, block: &str) -> String {
24607        let escape_backslash = matches!(
24608            self.config.dialect,
24609            Some(crate::dialects::DialectType::Snowflake)
24610        );
24611        let mut escaped = String::with_capacity(block.len() + 4);
24612        for ch in block.chars() {
24613            if ch == '\'' {
24614                escaped.push('\\');
24615                escaped.push('\'');
24616            } else if escape_backslash && ch == '\\' {
24617                escaped.push('\\');
24618                escaped.push('\\');
24619            } else {
24620                escaped.push(ch);
24621            }
24622        }
24623        escaped
24624    }
24625
24626    fn write_newline(&mut self) {
24627        self.output.push('\n');
24628    }
24629
24630    fn write_indent(&mut self) {
24631        for _ in 0..self.indent_level {
24632            self.output.push_str(self.config.indent);
24633        }
24634    }
24635
24636    // === SQLGlot-style pretty printing helpers ===
24637
24638    /// Returns the separator string for pretty printing.
24639    /// Check if the total length of arguments exceeds max_text_width.
24640    /// Used for dynamic line breaking in expressions() formatting.
24641    fn too_wide(&self, args: &[String]) -> bool {
24642        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
24643    }
24644
24645    /// Generate an expression to a string using a temporary non-pretty generator.
24646    /// Useful for width calculations before deciding on formatting.
24647    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
24648        let config = GeneratorConfig {
24649            pretty: false,
24650            dialect: self.config.dialect,
24651            ..Default::default()
24652        };
24653        let mut gen = Generator::with_config(config);
24654        gen.generate_expression(expr)?;
24655        Ok(gen.output)
24656    }
24657
24658    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
24659    /// In pretty mode: newline + indented keyword + newline + indented condition
24660    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
24661        if self.config.pretty {
24662            self.write_newline();
24663            self.write_indent();
24664            self.write_keyword(keyword);
24665            self.write_newline();
24666            self.indent_level += 1;
24667            self.write_indent();
24668            self.generate_expression(condition)?;
24669            self.indent_level -= 1;
24670        } else {
24671            self.write_space();
24672            self.write_keyword(keyword);
24673            self.write_space();
24674            self.generate_expression(condition)?;
24675        }
24676        Ok(())
24677    }
24678
24679    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
24680    /// In pretty mode: each expression on new line with indentation
24681    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
24682        if exprs.is_empty() {
24683            return Ok(());
24684        }
24685
24686        if self.config.pretty {
24687            self.write_newline();
24688            self.write_indent();
24689            self.write_keyword(keyword);
24690            self.write_newline();
24691            self.indent_level += 1;
24692            for (i, expr) in exprs.iter().enumerate() {
24693                if i > 0 {
24694                    self.write(",");
24695                    self.write_newline();
24696                }
24697                self.write_indent();
24698                self.generate_expression(expr)?;
24699            }
24700            self.indent_level -= 1;
24701        } else {
24702            self.write_space();
24703            self.write_keyword(keyword);
24704            self.write_space();
24705            for (i, expr) in exprs.iter().enumerate() {
24706                if i > 0 {
24707                    self.write(", ");
24708                }
24709                self.generate_expression(expr)?;
24710            }
24711        }
24712        Ok(())
24713    }
24714
24715    /// Writes ORDER BY / SORT BY clause with Ordered expressions
24716    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
24717        if orderings.is_empty() {
24718            return Ok(());
24719        }
24720
24721        if self.config.pretty {
24722            self.write_newline();
24723            self.write_indent();
24724            self.write_keyword(keyword);
24725            self.write_newline();
24726            self.indent_level += 1;
24727            for (i, ordered) in orderings.iter().enumerate() {
24728                if i > 0 {
24729                    self.write(",");
24730                    self.write_newline();
24731                }
24732                self.write_indent();
24733                self.generate_ordered(ordered)?;
24734            }
24735            self.indent_level -= 1;
24736        } else {
24737            self.write_space();
24738            self.write_keyword(keyword);
24739            self.write_space();
24740            for (i, ordered) in orderings.iter().enumerate() {
24741                if i > 0 {
24742                    self.write(", ");
24743                }
24744                self.generate_ordered(ordered)?;
24745            }
24746        }
24747        Ok(())
24748    }
24749
24750    /// Writes WINDOW clause with named window definitions
24751    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
24752        if windows.is_empty() {
24753            return Ok(());
24754        }
24755
24756        if self.config.pretty {
24757            self.write_newline();
24758            self.write_indent();
24759            self.write_keyword("WINDOW");
24760            self.write_newline();
24761            self.indent_level += 1;
24762            for (i, named_window) in windows.iter().enumerate() {
24763                if i > 0 {
24764                    self.write(",");
24765                    self.write_newline();
24766                }
24767                self.write_indent();
24768                self.generate_identifier(&named_window.name)?;
24769                self.write_space();
24770                self.write_keyword("AS");
24771                self.write(" (");
24772                self.generate_over(&named_window.spec)?;
24773                self.write(")");
24774            }
24775            self.indent_level -= 1;
24776        } else {
24777            self.write_space();
24778            self.write_keyword("WINDOW");
24779            self.write_space();
24780            for (i, named_window) in windows.iter().enumerate() {
24781                if i > 0 {
24782                    self.write(", ");
24783                }
24784                self.generate_identifier(&named_window.name)?;
24785                self.write_space();
24786                self.write_keyword("AS");
24787                self.write(" (");
24788                self.generate_over(&named_window.spec)?;
24789                self.write(")");
24790            }
24791        }
24792        Ok(())
24793    }
24794
24795    // === BATCH-GENERATED STUB METHODS (481 variants) ===
24796    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
24797        // AI_AGG(this, expression)
24798        self.write_keyword("AI_AGG");
24799        self.write("(");
24800        self.generate_expression(&e.this)?;
24801        self.write(", ");
24802        self.generate_expression(&e.expression)?;
24803        self.write(")");
24804        Ok(())
24805    }
24806
24807    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
24808        // AI_CLASSIFY(input, [categories], [config])
24809        self.write_keyword("AI_CLASSIFY");
24810        self.write("(");
24811        self.generate_expression(&e.this)?;
24812        if let Some(categories) = &e.categories {
24813            self.write(", ");
24814            self.generate_expression(categories)?;
24815        }
24816        if let Some(config) = &e.config {
24817            self.write(", ");
24818            self.generate_expression(config)?;
24819        }
24820        self.write(")");
24821        Ok(())
24822    }
24823
24824    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
24825        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
24826        self.write_keyword("ADD");
24827        self.write_space();
24828        if e.exists {
24829            self.write_keyword("IF NOT EXISTS");
24830            self.write_space();
24831        }
24832        self.generate_expression(&e.this)?;
24833        if let Some(location) = &e.location {
24834            self.write_space();
24835            self.generate_expression(location)?;
24836        }
24837        Ok(())
24838    }
24839
24840    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
24841        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
24842        self.write_keyword("ALGORITHM");
24843        self.write("=");
24844        self.generate_expression(&e.this)?;
24845        Ok(())
24846    }
24847
24848    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
24849        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
24850        self.generate_expression(&e.this)?;
24851        self.write_space();
24852        self.write_keyword("AS");
24853        self.write(" (");
24854        for (i, expr) in e.expressions.iter().enumerate() {
24855            if i > 0 {
24856                self.write(", ");
24857            }
24858            self.generate_expression(expr)?;
24859        }
24860        self.write(")");
24861        Ok(())
24862    }
24863
24864    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
24865        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
24866        self.write_keyword("ALLOWED_VALUES");
24867        self.write_space();
24868        for (i, expr) in e.expressions.iter().enumerate() {
24869            if i > 0 {
24870                self.write(", ");
24871            }
24872            self.generate_expression(expr)?;
24873        }
24874        Ok(())
24875    }
24876
24877    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
24878        // Python: complex logic based on dtype, default, comment, visible, etc.
24879        self.write_keyword("ALTER COLUMN");
24880        self.write_space();
24881        self.generate_expression(&e.this)?;
24882
24883        if let Some(dtype) = &e.dtype {
24884            self.write_space();
24885            self.write_keyword("SET DATA TYPE");
24886            self.write_space();
24887            self.generate_expression(dtype)?;
24888            if let Some(collate) = &e.collate {
24889                self.write_space();
24890                self.write_keyword("COLLATE");
24891                self.write_space();
24892                self.generate_expression(collate)?;
24893            }
24894            if let Some(using) = &e.using {
24895                self.write_space();
24896                self.write_keyword("USING");
24897                self.write_space();
24898                self.generate_expression(using)?;
24899            }
24900        } else if let Some(default) = &e.default {
24901            self.write_space();
24902            self.write_keyword("SET DEFAULT");
24903            self.write_space();
24904            self.generate_expression(default)?;
24905        } else if let Some(comment) = &e.comment {
24906            self.write_space();
24907            self.write_keyword("COMMENT");
24908            self.write_space();
24909            self.generate_expression(comment)?;
24910        } else if let Some(drop) = &e.drop {
24911            self.write_space();
24912            self.write_keyword("DROP");
24913            self.write_space();
24914            self.generate_expression(drop)?;
24915        } else if let Some(visible) = &e.visible {
24916            self.write_space();
24917            self.generate_expression(visible)?;
24918        } else if let Some(rename_to) = &e.rename_to {
24919            self.write_space();
24920            self.write_keyword("RENAME TO");
24921            self.write_space();
24922            self.generate_expression(rename_to)?;
24923        } else if let Some(allow_null) = &e.allow_null {
24924            self.write_space();
24925            self.generate_expression(allow_null)?;
24926        }
24927        Ok(())
24928    }
24929
24930    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
24931        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
24932        self.write_keyword("ALTER SESSION");
24933        self.write_space();
24934        if e.unset.is_some() {
24935            self.write_keyword("UNSET");
24936        } else {
24937            self.write_keyword("SET");
24938        }
24939        self.write_space();
24940        for (i, expr) in e.expressions.iter().enumerate() {
24941            if i > 0 {
24942                self.write(", ");
24943            }
24944            self.generate_expression(expr)?;
24945        }
24946        Ok(())
24947    }
24948
24949    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
24950        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
24951        self.write_keyword("SET");
24952
24953        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
24954        if let Some(opt) = &e.option {
24955            self.write_space();
24956            self.generate_expression(opt)?;
24957        }
24958
24959        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
24960        // Check if expressions look like property assignments
24961        if !e.expressions.is_empty() {
24962            // Check if this looks like property assignments (for SET PROPERTIES)
24963            let is_properties = e
24964                .expressions
24965                .iter()
24966                .any(|expr| matches!(expr, Expression::Eq(_)));
24967            if is_properties && e.option.is_none() {
24968                self.write_space();
24969                self.write_keyword("PROPERTIES");
24970            }
24971            self.write_space();
24972            for (i, expr) in e.expressions.iter().enumerate() {
24973                if i > 0 {
24974                    self.write(", ");
24975                }
24976                self.generate_expression(expr)?;
24977            }
24978        }
24979
24980        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
24981        if let Some(file_format) = &e.file_format {
24982            self.write(" ");
24983            self.write_keyword("STAGE_FILE_FORMAT");
24984            self.write(" = (");
24985            self.generate_space_separated_properties(file_format)?;
24986            self.write(")");
24987        }
24988
24989        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
24990        if let Some(copy_options) = &e.copy_options {
24991            self.write(" ");
24992            self.write_keyword("STAGE_COPY_OPTIONS");
24993            self.write(" = (");
24994            self.generate_space_separated_properties(copy_options)?;
24995            self.write(")");
24996        }
24997
24998        // Generate TAG ...
24999        if let Some(tag) = &e.tag {
25000            self.write(" ");
25001            self.write_keyword("TAG");
25002            self.write(" ");
25003            self.generate_expression(tag)?;
25004        }
25005
25006        Ok(())
25007    }
25008
25009    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
25010    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
25011        match expr {
25012            Expression::Tuple(t) => {
25013                for (i, prop) in t.expressions.iter().enumerate() {
25014                    if i > 0 {
25015                        self.write(" ");
25016                    }
25017                    self.generate_expression(prop)?;
25018                }
25019            }
25020            _ => {
25021                self.generate_expression(expr)?;
25022            }
25023        }
25024        Ok(())
25025    }
25026
25027    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
25028        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
25029        self.write_keyword("ALTER");
25030        if e.compound.is_some() {
25031            self.write_space();
25032            self.write_keyword("COMPOUND");
25033        }
25034        self.write_space();
25035        self.write_keyword("SORTKEY");
25036        self.write_space();
25037        if let Some(this) = &e.this {
25038            self.generate_expression(this)?;
25039        } else if !e.expressions.is_empty() {
25040            self.write("(");
25041            for (i, expr) in e.expressions.iter().enumerate() {
25042                if i > 0 {
25043                    self.write(", ");
25044                }
25045                self.generate_expression(expr)?;
25046            }
25047            self.write(")");
25048        }
25049        Ok(())
25050    }
25051
25052    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
25053        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
25054        self.write_keyword("ANALYZE");
25055        if !e.options.is_empty() {
25056            self.write_space();
25057            for (i, opt) in e.options.iter().enumerate() {
25058                if i > 0 {
25059                    self.write_space();
25060                }
25061                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
25062                if let Expression::Identifier(id) = opt {
25063                    self.write_keyword(&id.name);
25064                } else {
25065                    self.generate_expression(opt)?;
25066                }
25067            }
25068        }
25069        if let Some(kind) = &e.kind {
25070            self.write_space();
25071            self.write_keyword(kind);
25072        }
25073        if let Some(this) = &e.this {
25074            self.write_space();
25075            self.generate_expression(this)?;
25076        }
25077        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
25078        if !e.columns.is_empty() {
25079            self.write("(");
25080            for (i, col) in e.columns.iter().enumerate() {
25081                if i > 0 {
25082                    self.write(", ");
25083                }
25084                self.write(col);
25085            }
25086            self.write(")");
25087        }
25088        if let Some(partition) = &e.partition {
25089            self.write_space();
25090            self.generate_expression(partition)?;
25091        }
25092        if let Some(mode) = &e.mode {
25093            self.write_space();
25094            self.generate_expression(mode)?;
25095        }
25096        if let Some(expression) = &e.expression {
25097            self.write_space();
25098            self.generate_expression(expression)?;
25099        }
25100        if !e.properties.is_empty() {
25101            self.write_space();
25102            self.write_keyword(self.config.with_properties_prefix);
25103            self.write(" (");
25104            for (i, prop) in e.properties.iter().enumerate() {
25105                if i > 0 {
25106                    self.write(", ");
25107                }
25108                self.generate_expression(prop)?;
25109            }
25110            self.write(")");
25111        }
25112        Ok(())
25113    }
25114
25115    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
25116        // Python: return f"DELETE{kind} STATISTICS"
25117        self.write_keyword("DELETE");
25118        if let Some(kind) = &e.kind {
25119            self.write_space();
25120            self.write_keyword(kind);
25121        }
25122        self.write_space();
25123        self.write_keyword("STATISTICS");
25124        Ok(())
25125    }
25126
25127    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
25128        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
25129        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
25130        if let Expression::Identifier(id) = e.this.as_ref() {
25131            self.write_keyword(&id.name);
25132        } else {
25133            self.generate_expression(&e.this)?;
25134        }
25135        self.write_space();
25136        self.write_keyword("HISTOGRAM ON");
25137        self.write_space();
25138        for (i, expr) in e.expressions.iter().enumerate() {
25139            if i > 0 {
25140                self.write(", ");
25141            }
25142            self.generate_expression(expr)?;
25143        }
25144        if let Some(expression) = &e.expression {
25145            self.write_space();
25146            self.generate_expression(expression)?;
25147        }
25148        if let Some(update_options) = &e.update_options {
25149            self.write_space();
25150            self.generate_expression(update_options)?;
25151            self.write_space();
25152            self.write_keyword("UPDATE");
25153        }
25154        Ok(())
25155    }
25156
25157    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
25158        // Python: return f"LIST CHAINED ROWS{inner_expression}"
25159        self.write_keyword("LIST CHAINED ROWS");
25160        if let Some(expression) = &e.expression {
25161            self.write_space();
25162            self.write_keyword("INTO");
25163            self.write_space();
25164            self.generate_expression(expression)?;
25165        }
25166        Ok(())
25167    }
25168
25169    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
25170        // Python: return f"SAMPLE {sample} {kind}"
25171        self.write_keyword("SAMPLE");
25172        self.write_space();
25173        if let Some(sample) = &e.sample {
25174            self.generate_expression(sample)?;
25175            self.write_space();
25176        }
25177        self.write_keyword(&e.kind);
25178        Ok(())
25179    }
25180
25181    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
25182        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
25183        self.write_keyword(&e.kind);
25184        if let Some(option) = &e.option {
25185            self.write_space();
25186            self.generate_expression(option)?;
25187        }
25188        self.write_space();
25189        self.write_keyword("STATISTICS");
25190        if let Some(this) = &e.this {
25191            self.write_space();
25192            self.generate_expression(this)?;
25193        }
25194        if !e.expressions.is_empty() {
25195            self.write_space();
25196            for (i, expr) in e.expressions.iter().enumerate() {
25197                if i > 0 {
25198                    self.write(", ");
25199                }
25200                self.generate_expression(expr)?;
25201            }
25202        }
25203        Ok(())
25204    }
25205
25206    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
25207        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
25208        self.write_keyword("VALIDATE");
25209        self.write_space();
25210        self.write_keyword(&e.kind);
25211        if let Some(this) = &e.this {
25212            self.write_space();
25213            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
25214            if let Expression::Identifier(id) = this.as_ref() {
25215                self.write_keyword(&id.name);
25216            } else {
25217                self.generate_expression(this)?;
25218            }
25219        }
25220        if let Some(expression) = &e.expression {
25221            self.write_space();
25222            self.write_keyword("INTO");
25223            self.write_space();
25224            self.generate_expression(expression)?;
25225        }
25226        Ok(())
25227    }
25228
25229    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
25230        // Python: return f"WITH {expressions}"
25231        self.write_keyword("WITH");
25232        self.write_space();
25233        for (i, expr) in e.expressions.iter().enumerate() {
25234            if i > 0 {
25235                self.write(", ");
25236            }
25237            self.generate_expression(expr)?;
25238        }
25239        Ok(())
25240    }
25241
25242    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
25243        // Anonymous represents a generic function call: FUNC_NAME(args...)
25244        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
25245        self.generate_expression(&e.this)?;
25246        self.write("(");
25247        for (i, arg) in e.expressions.iter().enumerate() {
25248            if i > 0 {
25249                self.write(", ");
25250            }
25251            self.generate_expression(arg)?;
25252        }
25253        self.write(")");
25254        Ok(())
25255    }
25256
25257    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
25258        // Same as Anonymous but for aggregate functions
25259        self.generate_expression(&e.this)?;
25260        self.write("(");
25261        for (i, arg) in e.expressions.iter().enumerate() {
25262            if i > 0 {
25263                self.write(", ");
25264            }
25265            self.generate_expression(arg)?;
25266        }
25267        self.write(")");
25268        Ok(())
25269    }
25270
25271    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
25272        // Python: return f"{this} APPLY({expr})"
25273        self.generate_expression(&e.this)?;
25274        self.write_space();
25275        self.write_keyword("APPLY");
25276        self.write("(");
25277        self.generate_expression(&e.expression)?;
25278        self.write(")");
25279        Ok(())
25280    }
25281
25282    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
25283        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
25284        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
25285        self.write("(");
25286        self.generate_expression(&e.this)?;
25287        if let Some(percentile) = &e.percentile {
25288            self.write(", ");
25289            self.generate_expression(percentile)?;
25290        }
25291        self.write(")");
25292        Ok(())
25293    }
25294
25295    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
25296        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
25297        self.write_keyword("APPROX_QUANTILE");
25298        self.write("(");
25299        self.generate_expression(&e.this)?;
25300        if let Some(quantile) = &e.quantile {
25301            self.write(", ");
25302            self.generate_expression(quantile)?;
25303        }
25304        if let Some(accuracy) = &e.accuracy {
25305            self.write(", ");
25306            self.generate_expression(accuracy)?;
25307        }
25308        if let Some(weight) = &e.weight {
25309            self.write(", ");
25310            self.generate_expression(weight)?;
25311        }
25312        self.write(")");
25313        Ok(())
25314    }
25315
25316    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
25317        // APPROX_QUANTILES(this, expression)
25318        self.write_keyword("APPROX_QUANTILES");
25319        self.write("(");
25320        self.generate_expression(&e.this)?;
25321        if let Some(expression) = &e.expression {
25322            self.write(", ");
25323            self.generate_expression(expression)?;
25324        }
25325        self.write(")");
25326        Ok(())
25327    }
25328
25329    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
25330        // APPROX_TOP_K(this[, expression][, counters])
25331        self.write_keyword("APPROX_TOP_K");
25332        self.write("(");
25333        self.generate_expression(&e.this)?;
25334        if let Some(expression) = &e.expression {
25335            self.write(", ");
25336            self.generate_expression(expression)?;
25337        }
25338        if let Some(counters) = &e.counters {
25339            self.write(", ");
25340            self.generate_expression(counters)?;
25341        }
25342        self.write(")");
25343        Ok(())
25344    }
25345
25346    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
25347        // APPROX_TOP_K_ACCUMULATE(this[, expression])
25348        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
25349        self.write("(");
25350        self.generate_expression(&e.this)?;
25351        if let Some(expression) = &e.expression {
25352            self.write(", ");
25353            self.generate_expression(expression)?;
25354        }
25355        self.write(")");
25356        Ok(())
25357    }
25358
25359    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
25360        // APPROX_TOP_K_COMBINE(this[, expression])
25361        self.write_keyword("APPROX_TOP_K_COMBINE");
25362        self.write("(");
25363        self.generate_expression(&e.this)?;
25364        if let Some(expression) = &e.expression {
25365            self.write(", ");
25366            self.generate_expression(expression)?;
25367        }
25368        self.write(")");
25369        Ok(())
25370    }
25371
25372    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
25373        // APPROX_TOP_K_ESTIMATE(this[, expression])
25374        self.write_keyword("APPROX_TOP_K_ESTIMATE");
25375        self.write("(");
25376        self.generate_expression(&e.this)?;
25377        if let Some(expression) = &e.expression {
25378            self.write(", ");
25379            self.generate_expression(expression)?;
25380        }
25381        self.write(")");
25382        Ok(())
25383    }
25384
25385    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
25386        // APPROX_TOP_SUM(this, expression[, count])
25387        self.write_keyword("APPROX_TOP_SUM");
25388        self.write("(");
25389        self.generate_expression(&e.this)?;
25390        self.write(", ");
25391        self.generate_expression(&e.expression)?;
25392        if let Some(count) = &e.count {
25393            self.write(", ");
25394            self.generate_expression(count)?;
25395        }
25396        self.write(")");
25397        Ok(())
25398    }
25399
25400    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
25401        // ARG_MAX(this, expression[, count])
25402        self.write_keyword("ARG_MAX");
25403        self.write("(");
25404        self.generate_expression(&e.this)?;
25405        self.write(", ");
25406        self.generate_expression(&e.expression)?;
25407        if let Some(count) = &e.count {
25408            self.write(", ");
25409            self.generate_expression(count)?;
25410        }
25411        self.write(")");
25412        Ok(())
25413    }
25414
25415    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
25416        // ARG_MIN(this, expression[, count])
25417        self.write_keyword("ARG_MIN");
25418        self.write("(");
25419        self.generate_expression(&e.this)?;
25420        self.write(", ");
25421        self.generate_expression(&e.expression)?;
25422        if let Some(count) = &e.count {
25423            self.write(", ");
25424            self.generate_expression(count)?;
25425        }
25426        self.write(")");
25427        Ok(())
25428    }
25429
25430    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
25431        // ARRAY_ALL(this, expression)
25432        self.write_keyword("ARRAY_ALL");
25433        self.write("(");
25434        self.generate_expression(&e.this)?;
25435        self.write(", ");
25436        self.generate_expression(&e.expression)?;
25437        self.write(")");
25438        Ok(())
25439    }
25440
25441    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
25442        // ARRAY_ANY(this, expression) - fallback implementation
25443        self.write_keyword("ARRAY_ANY");
25444        self.write("(");
25445        self.generate_expression(&e.this)?;
25446        self.write(", ");
25447        self.generate_expression(&e.expression)?;
25448        self.write(")");
25449        Ok(())
25450    }
25451
25452    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
25453        // ARRAY_CONSTRUCT_COMPACT(expressions...)
25454        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
25455        self.write("(");
25456        for (i, expr) in e.expressions.iter().enumerate() {
25457            if i > 0 {
25458                self.write(", ");
25459            }
25460            self.generate_expression(expr)?;
25461        }
25462        self.write(")");
25463        Ok(())
25464    }
25465
25466    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
25467        // ARRAY_SUM(this[, expression])
25468        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
25469            self.write("arraySum");
25470        } else {
25471            self.write_keyword("ARRAY_SUM");
25472        }
25473        self.write("(");
25474        self.generate_expression(&e.this)?;
25475        if let Some(expression) = &e.expression {
25476            self.write(", ");
25477            self.generate_expression(expression)?;
25478        }
25479        self.write(")");
25480        Ok(())
25481    }
25482
25483    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
25484        // Python: return f"{this} AT {index}"
25485        self.generate_expression(&e.this)?;
25486        self.write_space();
25487        self.write_keyword("AT");
25488        self.write_space();
25489        self.generate_expression(&e.expression)?;
25490        Ok(())
25491    }
25492
25493    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
25494        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
25495        self.write_keyword("ATTACH");
25496        if e.exists {
25497            self.write_space();
25498            self.write_keyword("IF NOT EXISTS");
25499        }
25500        self.write_space();
25501        self.generate_expression(&e.this)?;
25502        if !e.expressions.is_empty() {
25503            self.write(" (");
25504            for (i, expr) in e.expressions.iter().enumerate() {
25505                if i > 0 {
25506                    self.write(", ");
25507                }
25508                self.generate_expression(expr)?;
25509            }
25510            self.write(")");
25511        }
25512        Ok(())
25513    }
25514
25515    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
25516        // AttachOption: this [expression]
25517        // Python sqlglot: no equals sign, just space-separated
25518        self.generate_expression(&e.this)?;
25519        if let Some(expression) = &e.expression {
25520            self.write_space();
25521            self.generate_expression(expression)?;
25522        }
25523        Ok(())
25524    }
25525
25526    /// Generate the auto_increment keyword and options for a column definition.
25527    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
25528    /// GENERATED AS IDENTITY, etc.
25529    fn generate_auto_increment_keyword(
25530        &mut self,
25531        col: &crate::expressions::ColumnDef,
25532    ) -> Result<()> {
25533        use crate::dialects::DialectType;
25534        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
25535            self.write_keyword("IDENTITY");
25536            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25537                self.write("(");
25538                if let Some(ref start) = col.auto_increment_start {
25539                    self.generate_expression(start)?;
25540                } else {
25541                    self.write("0");
25542                }
25543                self.write(", ");
25544                if let Some(ref inc) = col.auto_increment_increment {
25545                    self.generate_expression(inc)?;
25546                } else {
25547                    self.write("1");
25548                }
25549                self.write(")");
25550            }
25551        } else if matches!(
25552            self.config.dialect,
25553            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
25554        ) {
25555            self.write_keyword("AUTOINCREMENT");
25556            if let Some(ref start) = col.auto_increment_start {
25557                self.write_space();
25558                self.write_keyword("START");
25559                self.write_space();
25560                self.generate_expression(start)?;
25561            }
25562            if let Some(ref inc) = col.auto_increment_increment {
25563                self.write_space();
25564                self.write_keyword("INCREMENT");
25565                self.write_space();
25566                self.generate_expression(inc)?;
25567            }
25568            if let Some(order) = col.auto_increment_order {
25569                self.write_space();
25570                if order {
25571                    self.write_keyword("ORDER");
25572                } else {
25573                    self.write_keyword("NOORDER");
25574                }
25575            }
25576        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
25577            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
25578            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25579                self.write(" (");
25580                let mut first = true;
25581                if let Some(ref start) = col.auto_increment_start {
25582                    self.write_keyword("START WITH");
25583                    self.write_space();
25584                    self.generate_expression(start)?;
25585                    first = false;
25586                }
25587                if let Some(ref inc) = col.auto_increment_increment {
25588                    if !first {
25589                        self.write_space();
25590                    }
25591                    self.write_keyword("INCREMENT BY");
25592                    self.write_space();
25593                    self.generate_expression(inc)?;
25594                }
25595                self.write(")");
25596            }
25597        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
25598            // IDENTITY(start, increment) -> GENERATED BY DEFAULT AS IDENTITY
25599            // Plain IDENTITY/AUTO_INCREMENT -> GENERATED ALWAYS AS IDENTITY
25600            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25601                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
25602            } else {
25603                self.write_keyword("GENERATED ALWAYS AS IDENTITY");
25604            }
25605            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25606                self.write(" (");
25607                let mut first = true;
25608                if let Some(ref start) = col.auto_increment_start {
25609                    self.write_keyword("START WITH");
25610                    self.write_space();
25611                    self.generate_expression(start)?;
25612                    first = false;
25613                }
25614                if let Some(ref inc) = col.auto_increment_increment {
25615                    if !first {
25616                        self.write_space();
25617                    }
25618                    self.write_keyword("INCREMENT BY");
25619                    self.write_space();
25620                    self.generate_expression(inc)?;
25621                }
25622                self.write(")");
25623            }
25624        } else if matches!(
25625            self.config.dialect,
25626            Some(DialectType::TSQL) | Some(DialectType::Fabric)
25627        ) {
25628            self.write_keyword("IDENTITY");
25629            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
25630                self.write("(");
25631                if let Some(ref start) = col.auto_increment_start {
25632                    self.generate_expression(start)?;
25633                } else {
25634                    self.write("0");
25635                }
25636                self.write(", ");
25637                if let Some(ref inc) = col.auto_increment_increment {
25638                    self.generate_expression(inc)?;
25639                } else {
25640                    self.write("1");
25641                }
25642                self.write(")");
25643            }
25644        } else {
25645            self.write_keyword("AUTO_INCREMENT");
25646            if let Some(ref start) = col.auto_increment_start {
25647                self.write_space();
25648                self.write_keyword("START");
25649                self.write_space();
25650                self.generate_expression(start)?;
25651            }
25652            if let Some(ref inc) = col.auto_increment_increment {
25653                self.write_space();
25654                self.write_keyword("INCREMENT");
25655                self.write_space();
25656                self.generate_expression(inc)?;
25657            }
25658            if let Some(order) = col.auto_increment_order {
25659                self.write_space();
25660                if order {
25661                    self.write_keyword("ORDER");
25662                } else {
25663                    self.write_keyword("NOORDER");
25664                }
25665            }
25666        }
25667        Ok(())
25668    }
25669
25670    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
25671        // AUTO_INCREMENT=value
25672        self.write_keyword("AUTO_INCREMENT");
25673        self.write("=");
25674        self.generate_expression(&e.this)?;
25675        Ok(())
25676    }
25677
25678    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
25679        // AUTO_REFRESH=value
25680        self.write_keyword("AUTO_REFRESH");
25681        self.write("=");
25682        self.generate_expression(&e.this)?;
25683        Ok(())
25684    }
25685
25686    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
25687        // BACKUP YES|NO (Redshift syntax uses space, not equals)
25688        self.write_keyword("BACKUP");
25689        self.write_space();
25690        self.generate_expression(&e.this)?;
25691        Ok(())
25692    }
25693
25694    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
25695        // BASE64_DECODE_BINARY(this[, alphabet])
25696        self.write_keyword("BASE64_DECODE_BINARY");
25697        self.write("(");
25698        self.generate_expression(&e.this)?;
25699        if let Some(alphabet) = &e.alphabet {
25700            self.write(", ");
25701            self.generate_expression(alphabet)?;
25702        }
25703        self.write(")");
25704        Ok(())
25705    }
25706
25707    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
25708        // BASE64_DECODE_STRING(this[, alphabet])
25709        self.write_keyword("BASE64_DECODE_STRING");
25710        self.write("(");
25711        self.generate_expression(&e.this)?;
25712        if let Some(alphabet) = &e.alphabet {
25713            self.write(", ");
25714            self.generate_expression(alphabet)?;
25715        }
25716        self.write(")");
25717        Ok(())
25718    }
25719
25720    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
25721        // BASE64_ENCODE(this[, max_line_length][, alphabet])
25722        self.write_keyword("BASE64_ENCODE");
25723        self.write("(");
25724        self.generate_expression(&e.this)?;
25725        if let Some(max_line_length) = &e.max_line_length {
25726            self.write(", ");
25727            self.generate_expression(max_line_length)?;
25728        }
25729        if let Some(alphabet) = &e.alphabet {
25730            self.write(", ");
25731            self.generate_expression(alphabet)?;
25732        }
25733        self.write(")");
25734        Ok(())
25735    }
25736
25737    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
25738        // BLOCKCOMPRESSION=... (complex Teradata property)
25739        self.write_keyword("BLOCKCOMPRESSION");
25740        self.write("=");
25741        if let Some(autotemp) = &e.autotemp {
25742            self.write_keyword("AUTOTEMP");
25743            self.write("(");
25744            self.generate_expression(autotemp)?;
25745            self.write(")");
25746        }
25747        if let Some(always) = &e.always {
25748            self.generate_expression(always)?;
25749        }
25750        if let Some(default) = &e.default {
25751            self.generate_expression(default)?;
25752        }
25753        if let Some(manual) = &e.manual {
25754            self.generate_expression(manual)?;
25755        }
25756        if let Some(never) = &e.never {
25757            self.generate_expression(never)?;
25758        }
25759        Ok(())
25760    }
25761
25762    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
25763        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
25764        self.write("((");
25765        self.generate_expression(&e.this)?;
25766        self.write(") ");
25767        self.write_keyword("AND");
25768        self.write(" (");
25769        self.generate_expression(&e.expression)?;
25770        self.write("))");
25771        Ok(())
25772    }
25773
25774    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
25775        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
25776        self.write("((");
25777        self.generate_expression(&e.this)?;
25778        self.write(") ");
25779        self.write_keyword("OR");
25780        self.write(" (");
25781        self.generate_expression(&e.expression)?;
25782        self.write("))");
25783        Ok(())
25784    }
25785
25786    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
25787        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
25788        self.write_keyword("BUILD");
25789        self.write_space();
25790        self.generate_expression(&e.this)?;
25791        Ok(())
25792    }
25793
25794    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
25795        // Byte string literal like B'...' or X'...'
25796        self.generate_expression(&e.this)?;
25797        Ok(())
25798    }
25799
25800    fn generate_case_specific_column_constraint(
25801        &mut self,
25802        e: &CaseSpecificColumnConstraint,
25803    ) -> Result<()> {
25804        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
25805        if e.not_.is_some() {
25806            self.write_keyword("NOT");
25807            self.write_space();
25808        }
25809        self.write_keyword("CASESPECIFIC");
25810        Ok(())
25811    }
25812
25813    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
25814        // Cast to string type (dialect-specific)
25815        self.write_keyword("CAST");
25816        self.write("(");
25817        self.generate_expression(&e.this)?;
25818        if self.config.dialect == Some(DialectType::ClickHouse) {
25819            // ClickHouse: CAST(expr, 'type_string')
25820            self.write(", ");
25821        } else {
25822            self.write_space();
25823            self.write_keyword("AS");
25824            self.write_space();
25825        }
25826        if let Some(to) = &e.to {
25827            self.generate_expression(to)?;
25828        }
25829        self.write(")");
25830        Ok(())
25831    }
25832
25833    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
25834        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
25835        // Python: f"CHANGES ({information}){at_before}{end}"
25836        self.write_keyword("CHANGES");
25837        self.write(" (");
25838        if let Some(information) = &e.information {
25839            self.write_keyword("INFORMATION");
25840            self.write(" => ");
25841            self.generate_expression(information)?;
25842        }
25843        self.write(")");
25844        // at_before and end are HistoricalData expressions that generate their own keywords
25845        if let Some(at_before) = &e.at_before {
25846            self.write(" ");
25847            self.generate_expression(at_before)?;
25848        }
25849        if let Some(end) = &e.end {
25850            self.write(" ");
25851            self.generate_expression(end)?;
25852        }
25853        Ok(())
25854    }
25855
25856    fn generate_character_set_column_constraint(
25857        &mut self,
25858        e: &CharacterSetColumnConstraint,
25859    ) -> Result<()> {
25860        // CHARACTER SET charset_name
25861        self.write_keyword("CHARACTER SET");
25862        self.write_space();
25863        self.generate_expression(&e.this)?;
25864        Ok(())
25865    }
25866
25867    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
25868        // [DEFAULT] CHARACTER SET=value
25869        if e.default.is_some() {
25870            self.write_keyword("DEFAULT");
25871            self.write_space();
25872        }
25873        self.write_keyword("CHARACTER SET");
25874        self.write("=");
25875        self.generate_expression(&e.this)?;
25876        Ok(())
25877    }
25878
25879    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
25880        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
25881        self.write_keyword("CHECK");
25882        self.write(" (");
25883        self.generate_expression(&e.this)?;
25884        self.write(")");
25885        if e.enforced.is_some() {
25886            self.write_space();
25887            self.write_keyword("ENFORCED");
25888        }
25889        Ok(())
25890    }
25891
25892    fn generate_assume_column_constraint(&mut self, e: &AssumeColumnConstraint) -> Result<()> {
25893        // Python: return f"ASSUME ({self.sql(e, 'this')})"
25894        self.write_keyword("ASSUME");
25895        self.write(" (");
25896        self.generate_expression(&e.this)?;
25897        self.write(")");
25898        Ok(())
25899    }
25900
25901    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
25902        // CHECK_JSON(this)
25903        self.write_keyword("CHECK_JSON");
25904        self.write("(");
25905        self.generate_expression(&e.this)?;
25906        self.write(")");
25907        Ok(())
25908    }
25909
25910    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
25911        // CHECK_XML(this)
25912        self.write_keyword("CHECK_XML");
25913        self.write("(");
25914        self.generate_expression(&e.this)?;
25915        self.write(")");
25916        Ok(())
25917    }
25918
25919    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
25920        // CHECKSUM=[ON|OFF|DEFAULT]
25921        self.write_keyword("CHECKSUM");
25922        self.write("=");
25923        if e.on.is_some() {
25924            self.write_keyword("ON");
25925        } else if e.default.is_some() {
25926            self.write_keyword("DEFAULT");
25927        } else {
25928            self.write_keyword("OFF");
25929        }
25930        Ok(())
25931    }
25932
25933    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
25934        // Python: return f"{shallow}{keyword} {this}"
25935        if e.shallow.is_some() {
25936            self.write_keyword("SHALLOW");
25937            self.write_space();
25938        }
25939        if e.copy.is_some() {
25940            self.write_keyword("COPY");
25941        } else {
25942            self.write_keyword("CLONE");
25943        }
25944        self.write_space();
25945        self.generate_expression(&e.this)?;
25946        Ok(())
25947    }
25948
25949    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
25950        // CLUSTER BY (expressions)
25951        self.write_keyword("CLUSTER BY");
25952        self.write(" (");
25953        for (i, ord) in e.expressions.iter().enumerate() {
25954            if i > 0 {
25955                self.write(", ");
25956            }
25957            self.generate_ordered(ord)?;
25958        }
25959        self.write(")");
25960        Ok(())
25961    }
25962
25963    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
25964        // BigQuery table property: CLUSTER BY col1, col2
25965        self.write_keyword("CLUSTER BY");
25966        self.write_space();
25967        for (i, col) in e.columns.iter().enumerate() {
25968            if i > 0 {
25969                self.write(", ");
25970            }
25971            self.generate_identifier(col)?;
25972        }
25973        Ok(())
25974    }
25975
25976    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
25977        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
25978        self.write_keyword("CLUSTERED BY");
25979        self.write(" (");
25980        for (i, expr) in e.expressions.iter().enumerate() {
25981            if i > 0 {
25982                self.write(", ");
25983            }
25984            self.generate_expression(expr)?;
25985        }
25986        self.write(")");
25987        if let Some(sorted_by) = &e.sorted_by {
25988            self.write_space();
25989            self.write_keyword("SORTED BY");
25990            self.write(" (");
25991            // Unwrap Tuple to avoid double parentheses
25992            if let Expression::Tuple(t) = sorted_by.as_ref() {
25993                for (i, expr) in t.expressions.iter().enumerate() {
25994                    if i > 0 {
25995                        self.write(", ");
25996                    }
25997                    self.generate_expression(expr)?;
25998                }
25999            } else {
26000                self.generate_expression(sorted_by)?;
26001            }
26002            self.write(")");
26003        }
26004        if let Some(buckets) = &e.buckets {
26005            self.write_space();
26006            self.write_keyword("INTO");
26007            self.write_space();
26008            self.generate_expression(buckets)?;
26009            self.write_space();
26010            self.write_keyword("BUCKETS");
26011        }
26012        Ok(())
26013    }
26014
26015    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
26016        // [DEFAULT] COLLATE [=] value
26017        // BigQuery uses space: DEFAULT COLLATE 'en'
26018        // Others use equals: COLLATE='en'
26019        if e.default.is_some() {
26020            self.write_keyword("DEFAULT");
26021            self.write_space();
26022        }
26023        self.write_keyword("COLLATE");
26024        // BigQuery uses space between COLLATE and value
26025        match self.config.dialect {
26026            Some(DialectType::BigQuery) => self.write_space(),
26027            _ => self.write("="),
26028        }
26029        self.generate_expression(&e.this)?;
26030        Ok(())
26031    }
26032
26033    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
26034        // ColumnConstraint is an enum
26035        match e {
26036            ColumnConstraint::NotNull => {
26037                self.write_keyword("NOT NULL");
26038            }
26039            ColumnConstraint::Null => {
26040                self.write_keyword("NULL");
26041            }
26042            ColumnConstraint::Unique => {
26043                self.write_keyword("UNIQUE");
26044            }
26045            ColumnConstraint::PrimaryKey => {
26046                self.write_keyword("PRIMARY KEY");
26047            }
26048            ColumnConstraint::Default(expr) => {
26049                self.write_keyword("DEFAULT");
26050                self.write_space();
26051                self.generate_expression(expr)?;
26052            }
26053            ColumnConstraint::Check(expr) => {
26054                self.write_keyword("CHECK");
26055                self.write(" (");
26056                self.generate_expression(expr)?;
26057                self.write(")");
26058            }
26059            ColumnConstraint::References(fk_ref) => {
26060                if fk_ref.has_foreign_key_keywords {
26061                    self.write_keyword("FOREIGN KEY");
26062                    self.write_space();
26063                }
26064                self.write_keyword("REFERENCES");
26065                self.write_space();
26066                self.generate_table(&fk_ref.table)?;
26067                if !fk_ref.columns.is_empty() {
26068                    self.write(" (");
26069                    for (i, col) in fk_ref.columns.iter().enumerate() {
26070                        if i > 0 {
26071                            self.write(", ");
26072                        }
26073                        self.generate_identifier(col)?;
26074                    }
26075                    self.write(")");
26076                }
26077            }
26078            ColumnConstraint::GeneratedAsIdentity(gen) => {
26079                self.write_keyword("GENERATED");
26080                self.write_space();
26081                if gen.always {
26082                    self.write_keyword("ALWAYS");
26083                } else {
26084                    self.write_keyword("BY DEFAULT");
26085                    if gen.on_null {
26086                        self.write_space();
26087                        self.write_keyword("ON NULL");
26088                    }
26089                }
26090                self.write_space();
26091                self.write_keyword("AS IDENTITY");
26092            }
26093            ColumnConstraint::Collate(collation) => {
26094                self.write_keyword("COLLATE");
26095                self.write_space();
26096                self.generate_identifier(collation)?;
26097            }
26098            ColumnConstraint::Comment(comment) => {
26099                self.write_keyword("COMMENT");
26100                self.write(" '");
26101                self.write(comment);
26102                self.write("'");
26103            }
26104            ColumnConstraint::ComputedColumn(cc) => {
26105                self.generate_computed_column_inline(cc)?;
26106            }
26107            ColumnConstraint::GeneratedAsRow(gar) => {
26108                self.generate_generated_as_row_inline(gar)?;
26109            }
26110            ColumnConstraint::Tags(tags) => {
26111                self.write_keyword("TAG");
26112                self.write(" (");
26113                for (i, expr) in tags.expressions.iter().enumerate() {
26114                    if i > 0 {
26115                        self.write(", ");
26116                    }
26117                    self.generate_expression(expr)?;
26118                }
26119                self.write(")");
26120            }
26121            ColumnConstraint::Path(path_expr) => {
26122                self.write_keyword("PATH");
26123                self.write_space();
26124                self.generate_expression(path_expr)?;
26125            }
26126        }
26127        Ok(())
26128    }
26129
26130    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
26131        // ColumnPosition is an enum
26132        match e {
26133            ColumnPosition::First => {
26134                self.write_keyword("FIRST");
26135            }
26136            ColumnPosition::After(ident) => {
26137                self.write_keyword("AFTER");
26138                self.write_space();
26139                self.generate_identifier(ident)?;
26140            }
26141        }
26142        Ok(())
26143    }
26144
26145    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
26146        // column(prefix)
26147        self.generate_expression(&e.this)?;
26148        self.write("(");
26149        self.generate_expression(&e.expression)?;
26150        self.write(")");
26151        Ok(())
26152    }
26153
26154    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
26155        // If unpack is true, this came from * COLUMNS(pattern)
26156        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
26157        if let Some(ref unpack) = e.unpack {
26158            if let Expression::Boolean(b) = unpack.as_ref() {
26159                if b.value {
26160                    self.write("*");
26161                }
26162            }
26163        }
26164        self.write_keyword("COLUMNS");
26165        self.write("(");
26166        self.generate_expression(&e.this)?;
26167        self.write(")");
26168        Ok(())
26169    }
26170
26171    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
26172        // Combined aggregate: FUNC(args) combined
26173        self.generate_expression(&e.this)?;
26174        self.write("(");
26175        for (i, expr) in e.expressions.iter().enumerate() {
26176            if i > 0 {
26177                self.write(", ");
26178            }
26179            self.generate_expression(expr)?;
26180        }
26181        self.write(")");
26182        Ok(())
26183    }
26184
26185    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
26186        // Combined parameterized aggregate: FUNC(params)(expressions)
26187        self.generate_expression(&e.this)?;
26188        self.write("(");
26189        for (i, param) in e.params.iter().enumerate() {
26190            if i > 0 {
26191                self.write(", ");
26192            }
26193            self.generate_expression(param)?;
26194        }
26195        self.write(")(");
26196        for (i, expr) in e.expressions.iter().enumerate() {
26197            if i > 0 {
26198                self.write(", ");
26199            }
26200            self.generate_expression(expr)?;
26201        }
26202        self.write(")");
26203        Ok(())
26204    }
26205
26206    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
26207        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
26208        self.write_keyword("COMMIT");
26209
26210        // TSQL always uses COMMIT TRANSACTION
26211        if e.this.is_none()
26212            && matches!(
26213                self.config.dialect,
26214                Some(DialectType::TSQL) | Some(DialectType::Fabric)
26215            )
26216        {
26217            self.write_space();
26218            self.write_keyword("TRANSACTION");
26219        }
26220
26221        // Check if this has TRANSACTION keyword or transaction name
26222        if let Some(this) = &e.this {
26223            // Check if it's just the "TRANSACTION" marker or an actual transaction name
26224            let is_transaction_marker = matches!(
26225                this.as_ref(),
26226                Expression::Identifier(id) if id.name == "TRANSACTION"
26227            );
26228
26229            self.write_space();
26230            self.write_keyword("TRANSACTION");
26231
26232            // If it's a real transaction name, output it
26233            if !is_transaction_marker {
26234                self.write_space();
26235                self.generate_expression(this)?;
26236            }
26237        }
26238
26239        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
26240        if let Some(durability) = &e.durability {
26241            self.write_space();
26242            self.write_keyword("WITH");
26243            self.write(" (");
26244            self.write_keyword("DELAYED_DURABILITY");
26245            self.write(" = ");
26246            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
26247                self.write_keyword("ON");
26248            } else {
26249                self.write_keyword("OFF");
26250            }
26251            self.write(")");
26252        }
26253
26254        // Output AND [NO] CHAIN
26255        if let Some(chain) = &e.chain {
26256            self.write_space();
26257            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
26258                self.write_keyword("AND NO CHAIN");
26259            } else {
26260                self.write_keyword("AND CHAIN");
26261            }
26262        }
26263        Ok(())
26264    }
26265
26266    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
26267        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
26268        self.write("[");
26269        self.generate_expression(&e.this)?;
26270        self.write_space();
26271        self.write_keyword("FOR");
26272        self.write_space();
26273        self.generate_expression(&e.expression)?;
26274        // Handle optional position variable (for enumerate-like syntax)
26275        if let Some(pos) = &e.position {
26276            self.write(", ");
26277            self.generate_expression(pos)?;
26278        }
26279        if let Some(iterator) = &e.iterator {
26280            self.write_space();
26281            self.write_keyword("IN");
26282            self.write_space();
26283            self.generate_expression(iterator)?;
26284        }
26285        if let Some(condition) = &e.condition {
26286            self.write_space();
26287            self.write_keyword("IF");
26288            self.write_space();
26289            self.generate_expression(condition)?;
26290        }
26291        self.write("]");
26292        Ok(())
26293    }
26294
26295    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
26296        // COMPRESS(this[, method])
26297        self.write_keyword("COMPRESS");
26298        self.write("(");
26299        self.generate_expression(&e.this)?;
26300        if let Some(method) = &e.method {
26301            self.write(", '");
26302            self.write(method);
26303            self.write("'");
26304        }
26305        self.write(")");
26306        Ok(())
26307    }
26308
26309    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
26310        // Python: return f"COMPRESS {this}"
26311        self.write_keyword("COMPRESS");
26312        if let Some(this) = &e.this {
26313            self.write_space();
26314            self.generate_expression(this)?;
26315        }
26316        Ok(())
26317    }
26318
26319    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
26320        // Python: return f"AS {this}{persisted}"
26321        self.write_keyword("AS");
26322        self.write_space();
26323        self.generate_expression(&e.this)?;
26324        if e.not_null.is_some() {
26325            self.write_space();
26326            self.write_keyword("PERSISTED NOT NULL");
26327        } else if e.persisted.is_some() {
26328            self.write_space();
26329            self.write_keyword("PERSISTED");
26330        }
26331        Ok(())
26332    }
26333
26334    /// Generate a ComputedColumn constraint inline within a column definition.
26335    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
26336    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
26337    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
26338        let computed_expr = if matches!(
26339            self.config.dialect,
26340            Some(DialectType::TSQL) | Some(DialectType::Fabric)
26341        ) {
26342            match &*cc.expression {
26343                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
26344                {
26345                    let wrapped = Expression::Cast(Box::new(Cast {
26346                        this: y.this.clone(),
26347                        to: DataType::Date,
26348                        trailing_comments: Vec::new(),
26349                        double_colon_syntax: false,
26350                        format: None,
26351                        default: None,
26352                        inferred_type: None,
26353                    }));
26354                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
26355                }
26356                Expression::Function(f)
26357                    if f.name.eq_ignore_ascii_case("YEAR")
26358                        && f.args.len() == 1
26359                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
26360                {
26361                    let wrapped = Expression::Cast(Box::new(Cast {
26362                        this: f.args[0].clone(),
26363                        to: DataType::Date,
26364                        trailing_comments: Vec::new(),
26365                        double_colon_syntax: false,
26366                        format: None,
26367                        default: None,
26368                        inferred_type: None,
26369                    }));
26370                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
26371                }
26372                _ => *cc.expression.clone(),
26373            }
26374        } else {
26375            *cc.expression.clone()
26376        };
26377
26378        match cc.persistence_kind.as_deref() {
26379            Some("STORED") | Some("VIRTUAL") => {
26380                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
26381                self.write_keyword("GENERATED ALWAYS AS");
26382                self.write(" (");
26383                self.generate_expression(&computed_expr)?;
26384                self.write(")");
26385                self.write_space();
26386                if cc.persisted {
26387                    self.write_keyword("STORED");
26388                } else {
26389                    self.write_keyword("VIRTUAL");
26390                }
26391            }
26392            Some("PERSISTED") => {
26393                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
26394                self.write_keyword("AS");
26395                self.write(" (");
26396                self.generate_expression(&computed_expr)?;
26397                self.write(")");
26398                self.write_space();
26399                self.write_keyword("PERSISTED");
26400                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
26401                if let Some(ref dt) = cc.data_type {
26402                    self.write_space();
26403                    self.generate_data_type(dt)?;
26404                }
26405                if cc.not_null {
26406                    self.write_space();
26407                    self.write_keyword("NOT NULL");
26408                }
26409            }
26410            _ => {
26411                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
26412                // TSQL computed column without PERSISTED: AS (expr)
26413                if matches!(
26414                    self.config.dialect,
26415                    Some(DialectType::Spark)
26416                        | Some(DialectType::Databricks)
26417                        | Some(DialectType::Hive)
26418                ) {
26419                    self.write_keyword("GENERATED ALWAYS AS");
26420                    self.write(" (");
26421                    self.generate_expression(&computed_expr)?;
26422                    self.write(")");
26423                } else if matches!(
26424                    self.config.dialect,
26425                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
26426                ) {
26427                    self.write_keyword("AS");
26428                    let omit_parens = matches!(computed_expr, Expression::Year(_))
26429                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
26430                    if omit_parens {
26431                        self.write_space();
26432                        self.generate_expression(&computed_expr)?;
26433                    } else {
26434                        self.write(" (");
26435                        self.generate_expression(&computed_expr)?;
26436                        self.write(")");
26437                    }
26438                } else {
26439                    self.write_keyword("AS");
26440                    self.write(" (");
26441                    self.generate_expression(&computed_expr)?;
26442                    self.write(")");
26443                }
26444            }
26445        }
26446        Ok(())
26447    }
26448
26449    /// Generate a GeneratedAsRow constraint inline within a column definition.
26450    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
26451    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
26452        self.write_keyword("GENERATED ALWAYS AS ROW ");
26453        if gar.start {
26454            self.write_keyword("START");
26455        } else {
26456            self.write_keyword("END");
26457        }
26458        if gar.hidden {
26459            self.write_space();
26460            self.write_keyword("HIDDEN");
26461        }
26462        Ok(())
26463    }
26464
26465    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
26466    fn generate_system_versioning_content(
26467        &mut self,
26468        e: &WithSystemVersioningProperty,
26469    ) -> Result<()> {
26470        let mut parts = Vec::new();
26471
26472        if let Some(this) = &e.this {
26473            let mut s = String::from("HISTORY_TABLE=");
26474            let mut gen = Generator::with_arc_config(self.config.clone());
26475            gen.generate_expression(this)?;
26476            s.push_str(&gen.output);
26477            parts.push(s);
26478        }
26479
26480        if let Some(data_consistency) = &e.data_consistency {
26481            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
26482            let mut gen = Generator::with_arc_config(self.config.clone());
26483            gen.generate_expression(data_consistency)?;
26484            s.push_str(&gen.output);
26485            parts.push(s);
26486        }
26487
26488        if let Some(retention_period) = &e.retention_period {
26489            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
26490            let mut gen = Generator::with_arc_config(self.config.clone());
26491            gen.generate_expression(retention_period)?;
26492            s.push_str(&gen.output);
26493            parts.push(s);
26494        }
26495
26496        self.write_keyword("SYSTEM_VERSIONING");
26497        self.write("=");
26498
26499        if !parts.is_empty() {
26500            self.write_keyword("ON");
26501            self.write("(");
26502            self.write(&parts.join(", "));
26503            self.write(")");
26504        } else if e.on.is_some() {
26505            self.write_keyword("ON");
26506        } else {
26507            self.write_keyword("OFF");
26508        }
26509
26510        Ok(())
26511    }
26512
26513    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
26514        // Conditional INSERT for multi-table inserts
26515        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
26516        if e.else_.is_some() {
26517            self.write_keyword("ELSE");
26518            self.write_space();
26519        } else if let Some(expression) = &e.expression {
26520            self.write_keyword("WHEN");
26521            self.write_space();
26522            self.generate_expression(expression)?;
26523            self.write_space();
26524            self.write_keyword("THEN");
26525            self.write_space();
26526        }
26527
26528        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
26529        // without the "INSERT " prefix
26530        if let Expression::Insert(insert) = e.this.as_ref() {
26531            self.write_keyword("INTO");
26532            self.write_space();
26533            self.generate_table(&insert.table)?;
26534
26535            // Optional column list
26536            if !insert.columns.is_empty() {
26537                self.write(" (");
26538                for (i, col) in insert.columns.iter().enumerate() {
26539                    if i > 0 {
26540                        self.write(", ");
26541                    }
26542                    self.generate_identifier(col)?;
26543                }
26544                self.write(")");
26545            }
26546
26547            // Optional VALUES clause
26548            if !insert.values.is_empty() {
26549                self.write_space();
26550                self.write_keyword("VALUES");
26551                for (row_idx, row) in insert.values.iter().enumerate() {
26552                    if row_idx > 0 {
26553                        self.write(", ");
26554                    }
26555                    self.write(" (");
26556                    for (i, val) in row.iter().enumerate() {
26557                        if i > 0 {
26558                            self.write(", ");
26559                        }
26560                        self.generate_expression(val)?;
26561                    }
26562                    self.write(")");
26563                }
26564            }
26565        } else {
26566            // Fallback for non-Insert expressions
26567            self.generate_expression(&e.this)?;
26568        }
26569        Ok(())
26570    }
26571
26572    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
26573        // Python: return f"CONSTRAINT {this} {expressions}"
26574        self.write_keyword("CONSTRAINT");
26575        self.write_space();
26576        self.generate_expression(&e.this)?;
26577        if !e.expressions.is_empty() {
26578            self.write_space();
26579            for (i, expr) in e.expressions.iter().enumerate() {
26580                if i > 0 {
26581                    self.write_space();
26582                }
26583                self.generate_expression(expr)?;
26584            }
26585        }
26586        Ok(())
26587    }
26588
26589    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
26590        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
26591        self.write_keyword("CONVERT_TIMEZONE");
26592        self.write("(");
26593        let mut first = true;
26594        if let Some(source_tz) = &e.source_tz {
26595            self.generate_expression(source_tz)?;
26596            first = false;
26597        }
26598        if let Some(target_tz) = &e.target_tz {
26599            if !first {
26600                self.write(", ");
26601            }
26602            self.generate_expression(target_tz)?;
26603            first = false;
26604        }
26605        if let Some(timestamp) = &e.timestamp {
26606            if !first {
26607                self.write(", ");
26608            }
26609            self.generate_expression(timestamp)?;
26610        }
26611        self.write(")");
26612        Ok(())
26613    }
26614
26615    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
26616        // CONVERT(this USING dest)
26617        self.write_keyword("CONVERT");
26618        self.write("(");
26619        self.generate_expression(&e.this)?;
26620        if let Some(dest) = &e.dest {
26621            self.write_space();
26622            self.write_keyword("USING");
26623            self.write_space();
26624            self.generate_expression(dest)?;
26625        }
26626        self.write(")");
26627        Ok(())
26628    }
26629
26630    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
26631        self.write_keyword("COPY");
26632        if e.is_into {
26633            self.write_space();
26634            self.write_keyword("INTO");
26635        }
26636        self.write_space();
26637
26638        // Generate target table or query (or stage for COPY INTO @stage)
26639        if let Expression::Literal(lit) = &e.this {
26640            if let Literal::String(s) = lit.as_ref() {
26641                if s.starts_with('@') {
26642                    self.write(s);
26643                } else {
26644                    self.generate_expression(&e.this)?;
26645                }
26646            }
26647        } else {
26648            self.generate_expression(&e.this)?;
26649        }
26650
26651        // FROM or TO based on kind
26652        if e.kind {
26653            // kind=true means FROM (loading into table)
26654            if self.config.pretty {
26655                self.write_newline();
26656            } else {
26657                self.write_space();
26658            }
26659            self.write_keyword("FROM");
26660            self.write_space();
26661        } else if !e.files.is_empty() {
26662            // kind=false means TO (exporting)
26663            if self.config.pretty {
26664                self.write_newline();
26665            } else {
26666                self.write_space();
26667            }
26668            self.write_keyword("TO");
26669            self.write_space();
26670        }
26671
26672        // Generate source/destination files
26673        for (i, file) in e.files.iter().enumerate() {
26674            if i > 0 {
26675                self.write_space();
26676            }
26677            // For stage references (strings starting with @), output without quotes
26678            if let Expression::Literal(lit) = file {
26679                if let Literal::String(s) = lit.as_ref() {
26680                    if s.starts_with('@') {
26681                        self.write(s);
26682                    } else {
26683                        self.generate_expression(file)?;
26684                    }
26685                }
26686            } else if let Expression::Identifier(id) = file {
26687                // Backtick-quoted file path (Databricks style: `s3://link`)
26688                if id.quoted {
26689                    self.write("`");
26690                    self.write(&id.name);
26691                    self.write("`");
26692                } else {
26693                    self.generate_expression(file)?;
26694                }
26695            } else {
26696                self.generate_expression(file)?;
26697            }
26698        }
26699
26700        // Generate credentials if present (Snowflake style - not wrapped in WITH)
26701        if !e.with_wrapped {
26702            if let Some(ref creds) = e.credentials {
26703                if let Some(ref storage) = creds.storage {
26704                    if self.config.pretty {
26705                        self.write_newline();
26706                    } else {
26707                        self.write_space();
26708                    }
26709                    self.write_keyword("STORAGE_INTEGRATION");
26710                    self.write(" = ");
26711                    self.write(storage);
26712                }
26713                if creds.credentials.is_empty() {
26714                    // Empty credentials: CREDENTIALS = ()
26715                    if self.config.pretty {
26716                        self.write_newline();
26717                    } else {
26718                        self.write_space();
26719                    }
26720                    self.write_keyword("CREDENTIALS");
26721                    self.write(" = ()");
26722                } else {
26723                    if self.config.pretty {
26724                        self.write_newline();
26725                    } else {
26726                        self.write_space();
26727                    }
26728                    self.write_keyword("CREDENTIALS");
26729                    // Check if this is Redshift-style (single value with empty key)
26730                    // vs Snowflake-style (multiple key=value pairs)
26731                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
26732                        // Redshift style: CREDENTIALS 'value'
26733                        self.write(" '");
26734                        self.write(&creds.credentials[0].1);
26735                        self.write("'");
26736                    } else {
26737                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
26738                        self.write(" = (");
26739                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
26740                            if i > 0 {
26741                                self.write_space();
26742                            }
26743                            self.write(k);
26744                            self.write("='");
26745                            self.write(v);
26746                            self.write("'");
26747                        }
26748                        self.write(")");
26749                    }
26750                }
26751                if let Some(ref encryption) = creds.encryption {
26752                    self.write_space();
26753                    self.write_keyword("ENCRYPTION");
26754                    self.write(" = ");
26755                    self.write(encryption);
26756                }
26757            }
26758        }
26759
26760        // Generate parameters
26761        if !e.params.is_empty() {
26762            if e.with_wrapped {
26763                // DuckDB/PostgreSQL/TSQL WITH (...) format
26764                self.write_space();
26765                self.write_keyword("WITH");
26766                self.write(" (");
26767                for (i, param) in e.params.iter().enumerate() {
26768                    if i > 0 {
26769                        self.write(", ");
26770                    }
26771                    self.generate_copy_param_with_format(param)?;
26772                }
26773                self.write(")");
26774            } else {
26775                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
26776                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
26777                // For Snowflake: KEY = VALUE
26778                for param in &e.params {
26779                    if self.config.pretty {
26780                        self.write_newline();
26781                    } else {
26782                        self.write_space();
26783                    }
26784                    // Preserve original case of parameter name (important for Redshift COPY options)
26785                    self.write(&param.name);
26786                    if let Some(ref value) = param.value {
26787                        // Use = only if it was present in the original (param.eq)
26788                        if param.eq {
26789                            self.write(" = ");
26790                        } else {
26791                            self.write(" ");
26792                        }
26793                        if !param.values.is_empty() {
26794                            self.write("(");
26795                            for (i, v) in param.values.iter().enumerate() {
26796                                if i > 0 {
26797                                    self.write_space();
26798                                }
26799                                self.generate_copy_nested_param(v)?;
26800                            }
26801                            self.write(")");
26802                        } else {
26803                            // For COPY parameter values, output identifiers without quoting
26804                            self.generate_copy_param_value(value)?;
26805                        }
26806                    } else if !param.values.is_empty() {
26807                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
26808                        if param.eq {
26809                            self.write(" = (");
26810                        } else {
26811                            self.write(" (");
26812                        }
26813                        // Determine separator for values inside parentheses:
26814                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
26815                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
26816                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
26817                        let is_key_value_pairs = param
26818                            .values
26819                            .first()
26820                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
26821                        let sep = if is_key_value_pairs && param.eq {
26822                            " "
26823                        } else {
26824                            ", "
26825                        };
26826                        for (i, v) in param.values.iter().enumerate() {
26827                            if i > 0 {
26828                                self.write(sep);
26829                            }
26830                            self.generate_copy_nested_param(v)?;
26831                        }
26832                        self.write(")");
26833                    }
26834                }
26835            }
26836        }
26837
26838        Ok(())
26839    }
26840
26841    /// Generate a COPY parameter in WITH (...) format
26842    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
26843    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
26844        self.write_keyword(&param.name);
26845        if !param.values.is_empty() {
26846            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
26847            self.write(" = (");
26848            for (i, v) in param.values.iter().enumerate() {
26849                if i > 0 {
26850                    self.write(", ");
26851                }
26852                self.generate_copy_nested_param(v)?;
26853            }
26854            self.write(")");
26855        } else if let Some(ref value) = param.value {
26856            if param.eq {
26857                self.write(" = ");
26858            } else {
26859                self.write(" ");
26860            }
26861            self.generate_expression(value)?;
26862        }
26863        Ok(())
26864    }
26865
26866    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
26867    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
26868        match expr {
26869            Expression::Eq(eq) => {
26870                // Generate key
26871                match &eq.left {
26872                    Expression::Column(c) => self.write(&c.name.name),
26873                    _ => self.generate_expression(&eq.left)?,
26874                }
26875                self.write("=");
26876                // Generate value
26877                match &eq.right {
26878                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
26879                        let Literal::String(s) = lit.as_ref() else {
26880                            unreachable!()
26881                        };
26882                        self.write("'");
26883                        self.write(s);
26884                        self.write("'");
26885                    }
26886                    Expression::Tuple(t) => {
26887                        // For lists like NULL_IF=('', 'str1')
26888                        self.write("(");
26889                        if self.config.pretty {
26890                            self.write_newline();
26891                            self.indent_level += 1;
26892                            for (i, item) in t.expressions.iter().enumerate() {
26893                                if i > 0 {
26894                                    self.write(", ");
26895                                }
26896                                self.write_indent();
26897                                self.generate_expression(item)?;
26898                            }
26899                            self.write_newline();
26900                            self.indent_level -= 1;
26901                        } else {
26902                            for (i, item) in t.expressions.iter().enumerate() {
26903                                if i > 0 {
26904                                    self.write(", ");
26905                                }
26906                                self.generate_expression(item)?;
26907                            }
26908                        }
26909                        self.write(")");
26910                    }
26911                    _ => self.generate_expression(&eq.right)?,
26912                }
26913                Ok(())
26914            }
26915            Expression::Column(c) => {
26916                // Standalone keyword like COMPRESSION
26917                self.write(&c.name.name);
26918                Ok(())
26919            }
26920            _ => self.generate_expression(expr),
26921        }
26922    }
26923
26924    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
26925    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
26926    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
26927        match expr {
26928            Expression::Column(c) => {
26929                // Output identifier, preserving quotes if originally quoted
26930                if c.name.quoted {
26931                    self.write("\"");
26932                    self.write(&c.name.name);
26933                    self.write("\"");
26934                } else {
26935                    self.write(&c.name.name);
26936                }
26937                Ok(())
26938            }
26939            Expression::Identifier(id) => {
26940                // Output identifier, preserving quotes if originally quoted
26941                if id.quoted {
26942                    self.write("\"");
26943                    self.write(&id.name);
26944                    self.write("\"");
26945                } else {
26946                    self.write(&id.name);
26947                }
26948                Ok(())
26949            }
26950            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
26951                let Literal::String(s) = lit.as_ref() else {
26952                    unreachable!()
26953                };
26954                // Output string with quotes
26955                self.write("'");
26956                self.write(s);
26957                self.write("'");
26958                Ok(())
26959            }
26960            _ => self.generate_expression(expr),
26961        }
26962    }
26963
26964    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
26965        self.write_keyword(&e.name);
26966        if let Some(ref value) = e.value {
26967            if e.eq {
26968                self.write(" = ");
26969            } else {
26970                self.write(" ");
26971            }
26972            self.generate_expression(value)?;
26973        }
26974        if !e.values.is_empty() {
26975            if e.eq {
26976                self.write(" = ");
26977            } else {
26978                self.write(" ");
26979            }
26980            self.write("(");
26981            for (i, v) in e.values.iter().enumerate() {
26982                if i > 0 {
26983                    self.write(", ");
26984                }
26985                self.generate_expression(v)?;
26986            }
26987            self.write(")");
26988        }
26989        Ok(())
26990    }
26991
26992    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
26993        // CORR(this, expression)
26994        self.write_keyword("CORR");
26995        self.write("(");
26996        self.generate_expression(&e.this)?;
26997        self.write(", ");
26998        self.generate_expression(&e.expression)?;
26999        self.write(")");
27000        Ok(())
27001    }
27002
27003    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
27004        // COSINE_DISTANCE(this, expression)
27005        self.write_keyword("COSINE_DISTANCE");
27006        self.write("(");
27007        self.generate_expression(&e.this)?;
27008        self.write(", ");
27009        self.generate_expression(&e.expression)?;
27010        self.write(")");
27011        Ok(())
27012    }
27013
27014    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
27015        // COVAR_POP(this, expression)
27016        self.write_keyword("COVAR_POP");
27017        self.write("(");
27018        self.generate_expression(&e.this)?;
27019        self.write(", ");
27020        self.generate_expression(&e.expression)?;
27021        self.write(")");
27022        Ok(())
27023    }
27024
27025    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
27026        // COVAR_SAMP(this, expression)
27027        self.write_keyword("COVAR_SAMP");
27028        self.write("(");
27029        self.generate_expression(&e.this)?;
27030        self.write(", ");
27031        self.generate_expression(&e.expression)?;
27032        self.write(")");
27033        Ok(())
27034    }
27035
27036    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
27037        // CREDENTIALS (key1='value1', key2='value2')
27038        self.write_keyword("CREDENTIALS");
27039        self.write(" (");
27040        for (i, (key, value)) in e.credentials.iter().enumerate() {
27041            if i > 0 {
27042                self.write(", ");
27043            }
27044            self.write(key);
27045            self.write("='");
27046            self.write(value);
27047            self.write("'");
27048        }
27049        self.write(")");
27050        Ok(())
27051    }
27052
27053    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
27054        // CREDENTIALS=(expressions)
27055        self.write_keyword("CREDENTIALS");
27056        self.write("=(");
27057        for (i, expr) in e.expressions.iter().enumerate() {
27058            if i > 0 {
27059                self.write(", ");
27060            }
27061            self.generate_expression(expr)?;
27062        }
27063        self.write(")");
27064        Ok(())
27065    }
27066
27067    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
27068        use crate::dialects::DialectType;
27069
27070        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
27071        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
27072        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
27073            self.generate_expression(&e.this)?;
27074            self.write_space();
27075            self.write_keyword("AS");
27076            self.write_space();
27077            self.generate_identifier(&e.alias)?;
27078            return Ok(());
27079        }
27080        self.write(&e.alias.name);
27081
27082        // BigQuery doesn't support column aliases in CTE definitions
27083        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
27084
27085        if !e.columns.is_empty() && !skip_cte_columns {
27086            self.write("(");
27087            for (i, col) in e.columns.iter().enumerate() {
27088                if i > 0 {
27089                    self.write(", ");
27090                }
27091                self.write(&col.name);
27092            }
27093            self.write(")");
27094        }
27095        // USING KEY (columns) for DuckDB recursive CTEs
27096        if !e.key_expressions.is_empty() {
27097            self.write_space();
27098            self.write_keyword("USING KEY");
27099            self.write(" (");
27100            for (i, key) in e.key_expressions.iter().enumerate() {
27101                if i > 0 {
27102                    self.write(", ");
27103                }
27104                self.write(&key.name);
27105            }
27106            self.write(")");
27107        }
27108        self.write_space();
27109        self.write_keyword("AS");
27110        self.write_space();
27111        if let Some(materialized) = e.materialized {
27112            if materialized {
27113                self.write_keyword("MATERIALIZED");
27114            } else {
27115                self.write_keyword("NOT MATERIALIZED");
27116            }
27117            self.write_space();
27118        }
27119        self.write("(");
27120        self.generate_expression(&e.this)?;
27121        self.write(")");
27122        Ok(())
27123    }
27124
27125    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
27126        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
27127        if e.expressions.is_empty() {
27128            self.write_keyword("WITH CUBE");
27129        } else {
27130            self.write_keyword("CUBE");
27131            self.write("(");
27132            for (i, expr) in e.expressions.iter().enumerate() {
27133                if i > 0 {
27134                    self.write(", ");
27135                }
27136                self.generate_expression(expr)?;
27137            }
27138            self.write(")");
27139        }
27140        Ok(())
27141    }
27142
27143    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
27144        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
27145        self.write_keyword("CURRENT_DATETIME");
27146        if let Some(this) = &e.this {
27147            self.write("(");
27148            self.generate_expression(this)?;
27149            self.write(")");
27150        }
27151        Ok(())
27152    }
27153
27154    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
27155        // CURRENT_SCHEMA - no arguments
27156        self.write_keyword("CURRENT_SCHEMA");
27157        Ok(())
27158    }
27159
27160    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
27161        // CURRENT_SCHEMAS(include_implicit)
27162        self.write_keyword("CURRENT_SCHEMAS");
27163        self.write("(");
27164        // Snowflake: drop the argument (CURRENT_SCHEMAS() takes no args)
27165        if !matches!(
27166            self.config.dialect,
27167            Some(crate::dialects::DialectType::Snowflake)
27168        ) {
27169            if let Some(this) = &e.this {
27170                self.generate_expression(this)?;
27171            }
27172        }
27173        self.write(")");
27174        Ok(())
27175    }
27176
27177    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
27178        // CURRENT_USER or CURRENT_USER()
27179        self.write_keyword("CURRENT_USER");
27180        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
27181        let needs_parens = e.this.is_some()
27182            || matches!(
27183                self.config.dialect,
27184                Some(DialectType::Snowflake)
27185                    | Some(DialectType::Spark)
27186                    | Some(DialectType::Hive)
27187                    | Some(DialectType::DuckDB)
27188                    | Some(DialectType::BigQuery)
27189                    | Some(DialectType::MySQL)
27190                    | Some(DialectType::Databricks)
27191            );
27192        if needs_parens {
27193            self.write("()");
27194        }
27195        Ok(())
27196    }
27197
27198    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
27199        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
27200        if self.config.dialect == Some(DialectType::Solr) {
27201            self.generate_expression(&e.this)?;
27202            self.write(" ");
27203            self.write_keyword("OR");
27204            self.write(" ");
27205            self.generate_expression(&e.expression)?;
27206        } else if self.config.dialect == Some(DialectType::MySQL) {
27207            self.generate_mysql_concat_from_dpipe(e)?;
27208        } else {
27209            // String concatenation: this || expression
27210            self.generate_expression(&e.this)?;
27211            self.write(" || ");
27212            self.generate_expression(&e.expression)?;
27213        }
27214        Ok(())
27215    }
27216
27217    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
27218        // DATABLOCKSIZE=... (Teradata)
27219        self.write_keyword("DATABLOCKSIZE");
27220        self.write("=");
27221        if let Some(size) = e.size {
27222            self.write(&size.to_string());
27223            if let Some(units) = &e.units {
27224                self.write_space();
27225                self.generate_expression(units)?;
27226            }
27227        } else if e.minimum.is_some() {
27228            self.write_keyword("MINIMUM");
27229        } else if e.maximum.is_some() {
27230            self.write_keyword("MAXIMUM");
27231        } else if e.default.is_some() {
27232            self.write_keyword("DEFAULT");
27233        }
27234        Ok(())
27235    }
27236
27237    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
27238        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
27239        self.write_keyword("DATA_DELETION");
27240        self.write("=");
27241
27242        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
27243        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
27244
27245        if is_on {
27246            self.write_keyword("ON");
27247            if has_options {
27248                self.write("(");
27249                let mut first = true;
27250                if let Some(filter_column) = &e.filter_column {
27251                    self.write_keyword("FILTER_COLUMN");
27252                    self.write("=");
27253                    self.generate_expression(filter_column)?;
27254                    first = false;
27255                }
27256                if let Some(retention_period) = &e.retention_period {
27257                    if !first {
27258                        self.write(", ");
27259                    }
27260                    self.write_keyword("RETENTION_PERIOD");
27261                    self.write("=");
27262                    self.generate_expression(retention_period)?;
27263                }
27264                self.write(")");
27265            }
27266        } else {
27267            self.write_keyword("OFF");
27268        }
27269        Ok(())
27270    }
27271
27272    /// Generate a Date function expression
27273    /// For Exasol: {d'value'} -> TO_DATE('value')
27274    /// For other dialects: DATE('value')
27275    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
27276        use crate::dialects::DialectType;
27277        use crate::expressions::Literal;
27278
27279        match self.config.dialect {
27280            // Exasol uses TO_DATE for Date expressions
27281            Some(DialectType::Exasol) => {
27282                self.write_keyword("TO_DATE");
27283                self.write("(");
27284                // Extract the string value from the expression if it's a string literal
27285                match &e.this {
27286                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
27287                        let Literal::String(s) = lit.as_ref() else {
27288                            unreachable!()
27289                        };
27290                        self.write("'");
27291                        self.write(s);
27292                        self.write("'");
27293                    }
27294                    _ => {
27295                        self.generate_expression(&e.this)?;
27296                    }
27297                }
27298                self.write(")");
27299            }
27300            // Standard: DATE(value)
27301            _ => {
27302                self.write_keyword("DATE");
27303                self.write("(");
27304                self.generate_expression(&e.this)?;
27305                self.write(")");
27306            }
27307        }
27308        Ok(())
27309    }
27310
27311    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
27312        // DATE_BIN(interval, timestamp[, origin])
27313        self.write_keyword("DATE_BIN");
27314        self.write("(");
27315        self.generate_expression(&e.this)?;
27316        self.write(", ");
27317        self.generate_expression(&e.expression)?;
27318        if let Some(origin) = &e.origin {
27319            self.write(", ");
27320            self.generate_expression(origin)?;
27321        }
27322        self.write(")");
27323        Ok(())
27324    }
27325
27326    fn generate_date_format_column_constraint(
27327        &mut self,
27328        e: &DateFormatColumnConstraint,
27329    ) -> Result<()> {
27330        // FORMAT 'format_string' (Teradata)
27331        self.write_keyword("FORMAT");
27332        self.write_space();
27333        self.generate_expression(&e.this)?;
27334        Ok(())
27335    }
27336
27337    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
27338        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
27339        self.write_keyword("DATE_FROM_PARTS");
27340        self.write("(");
27341        let mut first = true;
27342        if let Some(year) = &e.year {
27343            self.generate_expression(year)?;
27344            first = false;
27345        }
27346        if let Some(month) = &e.month {
27347            if !first {
27348                self.write(", ");
27349            }
27350            self.generate_expression(month)?;
27351            first = false;
27352        }
27353        if let Some(day) = &e.day {
27354            if !first {
27355                self.write(", ");
27356            }
27357            self.generate_expression(day)?;
27358        }
27359        self.write(")");
27360        Ok(())
27361    }
27362
27363    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
27364        // DATETIME(this) or DATETIME(this, expression)
27365        self.write_keyword("DATETIME");
27366        self.write("(");
27367        self.generate_expression(&e.this)?;
27368        if let Some(expr) = &e.expression {
27369            self.write(", ");
27370            self.generate_expression(expr)?;
27371        }
27372        self.write(")");
27373        Ok(())
27374    }
27375
27376    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
27377        // DATETIME_ADD(this, expression, unit)
27378        self.write_keyword("DATETIME_ADD");
27379        self.write("(");
27380        self.generate_expression(&e.this)?;
27381        self.write(", ");
27382        self.generate_expression(&e.expression)?;
27383        if let Some(unit) = &e.unit {
27384            self.write(", ");
27385            self.write_keyword(unit);
27386        }
27387        self.write(")");
27388        Ok(())
27389    }
27390
27391    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
27392        // DATETIME_DIFF(this, expression, unit)
27393        self.write_keyword("DATETIME_DIFF");
27394        self.write("(");
27395        self.generate_expression(&e.this)?;
27396        self.write(", ");
27397        self.generate_expression(&e.expression)?;
27398        if let Some(unit) = &e.unit {
27399            self.write(", ");
27400            self.write_keyword(unit);
27401        }
27402        self.write(")");
27403        Ok(())
27404    }
27405
27406    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
27407        // DATETIME_SUB(this, expression, unit)
27408        self.write_keyword("DATETIME_SUB");
27409        self.write("(");
27410        self.generate_expression(&e.this)?;
27411        self.write(", ");
27412        self.generate_expression(&e.expression)?;
27413        if let Some(unit) = &e.unit {
27414            self.write(", ");
27415            self.write_keyword(unit);
27416        }
27417        self.write(")");
27418        Ok(())
27419    }
27420
27421    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
27422        // DATETIME_TRUNC(this, unit, zone)
27423        self.write_keyword("DATETIME_TRUNC");
27424        self.write("(");
27425        self.generate_expression(&e.this)?;
27426        self.write(", ");
27427        self.write_keyword(&e.unit);
27428        if let Some(zone) = &e.zone {
27429            self.write(", ");
27430            self.generate_expression(zone)?;
27431        }
27432        self.write(")");
27433        Ok(())
27434    }
27435
27436    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
27437        // DAYNAME(this)
27438        self.write_keyword("DAYNAME");
27439        self.write("(");
27440        self.generate_expression(&e.this)?;
27441        self.write(")");
27442        Ok(())
27443    }
27444
27445    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
27446        // DECLARE [OR REPLACE] var1 AS type1, var2 AS type2, ...
27447        self.write_keyword("DECLARE");
27448        self.write_space();
27449        if e.replace {
27450            self.write_keyword("OR");
27451            self.write_space();
27452            self.write_keyword("REPLACE");
27453            self.write_space();
27454        }
27455        for (i, expr) in e.expressions.iter().enumerate() {
27456            if i > 0 {
27457                self.write(", ");
27458            }
27459            self.generate_expression(expr)?;
27460        }
27461        Ok(())
27462    }
27463
27464    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
27465        use crate::dialects::DialectType;
27466
27467        // variable TYPE [DEFAULT default]
27468        self.generate_expression(&e.this)?;
27469        // BigQuery multi-variable: DECLARE X, Y, Z INT64
27470        for name in &e.additional_names {
27471            self.write(", ");
27472            self.generate_expression(name)?;
27473        }
27474        if let Some(kind) = &e.kind {
27475            self.write_space();
27476            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
27477            // TSQL: Always includes AS (normalization)
27478            // Others: Include AS if present in original
27479            match self.config.dialect {
27480                Some(DialectType::BigQuery) => {
27481                    self.write(kind);
27482                }
27483                Some(DialectType::TSQL) => {
27484                    // TSQL DECLARE: no AS keyword (sqlglot convention)
27485                    // Normalize INT to INTEGER for simple declarations
27486                    // Complex TABLE declarations (with CLUSTERED/INDEX) are preserved as-is
27487                    let is_complex_table = kind.starts_with("TABLE")
27488                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
27489                    if is_complex_table {
27490                        self.write(kind);
27491                    } else if kind == "INT" {
27492                        self.write("INTEGER");
27493                    } else if kind.starts_with("TABLE") {
27494                        // Normalize INT to INTEGER inside simple TABLE column definitions
27495                        let normalized = kind
27496                            .replace(" INT ", " INTEGER ")
27497                            .replace(" INT,", " INTEGER,")
27498                            .replace(" INT)", " INTEGER)")
27499                            .replace("(INT ", "(INTEGER ");
27500                        self.write(&normalized);
27501                    } else {
27502                        self.write(kind);
27503                    }
27504                }
27505                _ => {
27506                    if e.has_as {
27507                        self.write_keyword("AS");
27508                        self.write_space();
27509                    }
27510                    self.write(kind);
27511                }
27512            }
27513        }
27514        if let Some(default) = &e.default {
27515            // BigQuery uses DEFAULT, others use =
27516            match self.config.dialect {
27517                Some(DialectType::BigQuery) => {
27518                    self.write_space();
27519                    self.write_keyword("DEFAULT");
27520                    self.write_space();
27521                }
27522                _ => {
27523                    self.write(" = ");
27524                }
27525            }
27526            self.generate_expression(default)?;
27527        }
27528        Ok(())
27529    }
27530
27531    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
27532        // DECODE(expr, search1, result1, search2, result2, ..., default)
27533        self.write_keyword("DECODE");
27534        self.write("(");
27535        for (i, expr) in e.expressions.iter().enumerate() {
27536            if i > 0 {
27537                self.write(", ");
27538            }
27539            self.generate_expression(expr)?;
27540        }
27541        self.write(")");
27542        Ok(())
27543    }
27544
27545    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
27546        // DECOMPRESS(expr, 'method')
27547        self.write_keyword("DECOMPRESS");
27548        self.write("(");
27549        self.generate_expression(&e.this)?;
27550        self.write(", '");
27551        self.write(&e.method);
27552        self.write("')");
27553        Ok(())
27554    }
27555
27556    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
27557        // DECOMPRESS(expr, 'method')
27558        self.write_keyword("DECOMPRESS");
27559        self.write("(");
27560        self.generate_expression(&e.this)?;
27561        self.write(", '");
27562        self.write(&e.method);
27563        self.write("')");
27564        Ok(())
27565    }
27566
27567    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
27568        // DECRYPT(value, passphrase [, aad [, algorithm]])
27569        self.write_keyword("DECRYPT");
27570        self.write("(");
27571        self.generate_expression(&e.this)?;
27572        if let Some(passphrase) = &e.passphrase {
27573            self.write(", ");
27574            self.generate_expression(passphrase)?;
27575        }
27576        if let Some(aad) = &e.aad {
27577            self.write(", ");
27578            self.generate_expression(aad)?;
27579        }
27580        if let Some(method) = &e.encryption_method {
27581            self.write(", ");
27582            self.generate_expression(method)?;
27583        }
27584        self.write(")");
27585        Ok(())
27586    }
27587
27588    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
27589        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
27590        self.write_keyword("DECRYPT_RAW");
27591        self.write("(");
27592        self.generate_expression(&e.this)?;
27593        if let Some(key) = &e.key {
27594            self.write(", ");
27595            self.generate_expression(key)?;
27596        }
27597        if let Some(iv) = &e.iv {
27598            self.write(", ");
27599            self.generate_expression(iv)?;
27600        }
27601        if let Some(aad) = &e.aad {
27602            self.write(", ");
27603            self.generate_expression(aad)?;
27604        }
27605        if let Some(method) = &e.encryption_method {
27606            self.write(", ");
27607            self.generate_expression(method)?;
27608        }
27609        self.write(")");
27610        Ok(())
27611    }
27612
27613    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
27614        // DEFINER = user
27615        self.write_keyword("DEFINER");
27616        self.write(" = ");
27617        self.generate_expression(&e.this)?;
27618        Ok(())
27619    }
27620
27621    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
27622        // Python: DETACH[DATABASE IF EXISTS] this
27623        self.write_keyword("DETACH");
27624        if e.exists {
27625            self.write_keyword(" DATABASE IF EXISTS");
27626        }
27627        self.write_space();
27628        self.generate_expression(&e.this)?;
27629        Ok(())
27630    }
27631
27632    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
27633        let property_name = match e.this.as_ref() {
27634            Expression::Identifier(id) => id.name.as_str(),
27635            Expression::Var(v) => v.this.as_str(),
27636            _ => "DICTIONARY",
27637        };
27638        self.write_keyword(property_name);
27639        self.write("(");
27640        self.write(&e.kind);
27641        if let Some(settings) = &e.settings {
27642            self.write("(");
27643            if let Expression::Tuple(t) = settings.as_ref() {
27644                if self.config.pretty && !t.expressions.is_empty() {
27645                    self.write_newline();
27646                    self.indent_level += 1;
27647                    for (i, pair) in t.expressions.iter().enumerate() {
27648                        if i > 0 {
27649                            self.write(",");
27650                            self.write_newline();
27651                        }
27652                        self.write_indent();
27653                        if let Expression::Tuple(pair_tuple) = pair {
27654                            if let Some(k) = pair_tuple.expressions.first() {
27655                                self.generate_expression(k)?;
27656                            }
27657                            if let Some(v) = pair_tuple.expressions.get(1) {
27658                                self.write(" ");
27659                                self.generate_expression(v)?;
27660                            }
27661                        } else {
27662                            self.generate_expression(pair)?;
27663                        }
27664                    }
27665                    self.indent_level -= 1;
27666                    self.write_newline();
27667                    self.write_indent();
27668                } else {
27669                    for (i, pair) in t.expressions.iter().enumerate() {
27670                        if i > 0 {
27671                            // ClickHouse dict properties are space-separated, not comma-separated
27672                            self.write(" ");
27673                        }
27674                        if let Expression::Tuple(pair_tuple) = pair {
27675                            if let Some(k) = pair_tuple.expressions.first() {
27676                                self.generate_expression(k)?;
27677                            }
27678                            if let Some(v) = pair_tuple.expressions.get(1) {
27679                                self.write(" ");
27680                                self.generate_expression(v)?;
27681                            }
27682                        } else {
27683                            self.generate_expression(pair)?;
27684                        }
27685                    }
27686                }
27687            } else {
27688                self.generate_expression(settings)?;
27689            }
27690            self.write(")");
27691        } else {
27692            // No settings but kind had parens (e.g., SOURCE(NULL()), LAYOUT(FLAT()))
27693            self.write("()");
27694        }
27695        self.write(")");
27696        Ok(())
27697    }
27698
27699    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
27700        let property_name = match e.this.as_ref() {
27701            Expression::Identifier(id) => id.name.as_str(),
27702            Expression::Var(v) => v.this.as_str(),
27703            _ => "RANGE",
27704        };
27705        self.write_keyword(property_name);
27706        self.write("(");
27707        if let Some(min) = &e.min {
27708            self.write_keyword("MIN");
27709            self.write_space();
27710            self.generate_expression(min)?;
27711        }
27712        if let Some(max) = &e.max {
27713            self.write_space();
27714            self.write_keyword("MAX");
27715            self.write_space();
27716            self.generate_expression(max)?;
27717        }
27718        self.write(")");
27719        Ok(())
27720    }
27721
27722    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
27723        // Python: {local}DIRECTORY {this}{row_format}
27724        if e.local.is_some() {
27725            self.write_keyword("LOCAL ");
27726        }
27727        self.write_keyword("DIRECTORY");
27728        self.write_space();
27729        self.generate_expression(&e.this)?;
27730        if let Some(row_format) = &e.row_format {
27731            self.write_space();
27732            self.generate_expression(row_format)?;
27733        }
27734        Ok(())
27735    }
27736
27737    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
27738        // Redshift: DISTKEY(column)
27739        self.write_keyword("DISTKEY");
27740        self.write("(");
27741        self.generate_expression(&e.this)?;
27742        self.write(")");
27743        Ok(())
27744    }
27745
27746    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
27747        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
27748        self.write_keyword("DISTSTYLE");
27749        self.write_space();
27750        self.generate_expression(&e.this)?;
27751        Ok(())
27752    }
27753
27754    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
27755        // Python: "DISTRIBUTE BY" expressions
27756        self.write_keyword("DISTRIBUTE BY");
27757        self.write_space();
27758        for (i, expr) in e.expressions.iter().enumerate() {
27759            if i > 0 {
27760                self.write(", ");
27761            }
27762            self.generate_expression(expr)?;
27763        }
27764        Ok(())
27765    }
27766
27767    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
27768        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
27769        self.write_keyword("DISTRIBUTED BY");
27770        self.write_space();
27771        self.write(&e.kind);
27772        if !e.expressions.is_empty() {
27773            self.write(" (");
27774            for (i, expr) in e.expressions.iter().enumerate() {
27775                if i > 0 {
27776                    self.write(", ");
27777                }
27778                self.generate_expression(expr)?;
27779            }
27780            self.write(")");
27781        }
27782        if let Some(buckets) = &e.buckets {
27783            self.write_space();
27784            self.write_keyword("BUCKETS");
27785            self.write_space();
27786            self.generate_expression(buckets)?;
27787        }
27788        if let Some(order) = &e.order {
27789            self.write_space();
27790            self.generate_expression(order)?;
27791        }
27792        Ok(())
27793    }
27794
27795    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
27796        // DOT_PRODUCT(vector1, vector2)
27797        self.write_keyword("DOT_PRODUCT");
27798        self.write("(");
27799        self.generate_expression(&e.this)?;
27800        self.write(", ");
27801        self.generate_expression(&e.expression)?;
27802        self.write(")");
27803        Ok(())
27804    }
27805
27806    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
27807        // Python: DROP{IF EXISTS }expressions
27808        self.write_keyword("DROP");
27809        if e.exists {
27810            self.write_keyword(" IF EXISTS ");
27811        } else {
27812            self.write_space();
27813        }
27814        for (i, expr) in e.expressions.iter().enumerate() {
27815            if i > 0 {
27816                self.write(", ");
27817            }
27818            self.generate_expression(expr)?;
27819        }
27820        Ok(())
27821    }
27822
27823    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
27824        // Python: DUPLICATE KEY (expressions)
27825        self.write_keyword("DUPLICATE KEY");
27826        self.write(" (");
27827        for (i, expr) in e.expressions.iter().enumerate() {
27828            if i > 0 {
27829                self.write(", ");
27830            }
27831            self.generate_expression(expr)?;
27832        }
27833        self.write(")");
27834        Ok(())
27835    }
27836
27837    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
27838        // ELT(index, str1, str2, ...)
27839        self.write_keyword("ELT");
27840        self.write("(");
27841        self.generate_expression(&e.this)?;
27842        for expr in &e.expressions {
27843            self.write(", ");
27844            self.generate_expression(expr)?;
27845        }
27846        self.write(")");
27847        Ok(())
27848    }
27849
27850    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
27851        // ENCODE(string, charset)
27852        self.write_keyword("ENCODE");
27853        self.write("(");
27854        self.generate_expression(&e.this)?;
27855        if let Some(charset) = &e.charset {
27856            self.write(", ");
27857            self.generate_expression(charset)?;
27858        }
27859        self.write(")");
27860        Ok(())
27861    }
27862
27863    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
27864        // Python: [KEY ]ENCODE this [properties]
27865        if e.key.is_some() {
27866            self.write_keyword("KEY ");
27867        }
27868        self.write_keyword("ENCODE");
27869        self.write_space();
27870        self.generate_expression(&e.this)?;
27871        if !e.properties.is_empty() {
27872            self.write(" (");
27873            for (i, prop) in e.properties.iter().enumerate() {
27874                if i > 0 {
27875                    self.write(", ");
27876                }
27877                self.generate_expression(prop)?;
27878            }
27879            self.write(")");
27880        }
27881        Ok(())
27882    }
27883
27884    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
27885        // ENCRYPT(value, passphrase [, aad [, algorithm]])
27886        self.write_keyword("ENCRYPT");
27887        self.write("(");
27888        self.generate_expression(&e.this)?;
27889        if let Some(passphrase) = &e.passphrase {
27890            self.write(", ");
27891            self.generate_expression(passphrase)?;
27892        }
27893        if let Some(aad) = &e.aad {
27894            self.write(", ");
27895            self.generate_expression(aad)?;
27896        }
27897        if let Some(method) = &e.encryption_method {
27898            self.write(", ");
27899            self.generate_expression(method)?;
27900        }
27901        self.write(")");
27902        Ok(())
27903    }
27904
27905    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
27906        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
27907        self.write_keyword("ENCRYPT_RAW");
27908        self.write("(");
27909        self.generate_expression(&e.this)?;
27910        if let Some(key) = &e.key {
27911            self.write(", ");
27912            self.generate_expression(key)?;
27913        }
27914        if let Some(iv) = &e.iv {
27915            self.write(", ");
27916            self.generate_expression(iv)?;
27917        }
27918        if let Some(aad) = &e.aad {
27919            self.write(", ");
27920            self.generate_expression(aad)?;
27921        }
27922        if let Some(method) = &e.encryption_method {
27923            self.write(", ");
27924            self.generate_expression(method)?;
27925        }
27926        self.write(")");
27927        Ok(())
27928    }
27929
27930    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
27931        // MySQL: ENGINE = InnoDB
27932        self.write_keyword("ENGINE");
27933        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
27934            self.write("=");
27935        } else {
27936            self.write(" = ");
27937        }
27938        self.generate_expression(&e.this)?;
27939        Ok(())
27940    }
27941
27942    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
27943        // ENVIRONMENT (expressions)
27944        self.write_keyword("ENVIRONMENT");
27945        self.write(" (");
27946        for (i, expr) in e.expressions.iter().enumerate() {
27947            if i > 0 {
27948                self.write(", ");
27949            }
27950            self.generate_expression(expr)?;
27951        }
27952        self.write(")");
27953        Ok(())
27954    }
27955
27956    fn generate_ephemeral_column_constraint(
27957        &mut self,
27958        e: &EphemeralColumnConstraint,
27959    ) -> Result<()> {
27960        // MySQL: EPHEMERAL [expr]
27961        self.write_keyword("EPHEMERAL");
27962        if let Some(this) = &e.this {
27963            self.write_space();
27964            self.generate_expression(this)?;
27965        }
27966        Ok(())
27967    }
27968
27969    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
27970        // Snowflake: EQUAL_NULL(a, b)
27971        self.write_keyword("EQUAL_NULL");
27972        self.write("(");
27973        self.generate_expression(&e.this)?;
27974        self.write(", ");
27975        self.generate_expression(&e.expression)?;
27976        self.write(")");
27977        Ok(())
27978    }
27979
27980    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
27981        use crate::dialects::DialectType;
27982
27983        // PostgreSQL uses <-> operator syntax
27984        match self.config.dialect {
27985            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
27986                self.generate_expression(&e.this)?;
27987                self.write(" <-> ");
27988                self.generate_expression(&e.expression)?;
27989            }
27990            _ => {
27991                // Other dialects use EUCLIDEAN_DISTANCE function
27992                self.write_keyword("EUCLIDEAN_DISTANCE");
27993                self.write("(");
27994                self.generate_expression(&e.this)?;
27995                self.write(", ");
27996                self.generate_expression(&e.expression)?;
27997                self.write(")");
27998            }
27999        }
28000        Ok(())
28001    }
28002
28003    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
28004        // EXECUTE AS CALLER|OWNER|user
28005        self.write_keyword("EXECUTE AS");
28006        self.write_space();
28007        self.generate_expression(&e.this)?;
28008        Ok(())
28009    }
28010
28011    fn generate_export(&mut self, e: &Export) -> Result<()> {
28012        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
28013        self.write_keyword("EXPORT DATA");
28014        if let Some(connection) = &e.connection {
28015            self.write_space();
28016            self.write_keyword("WITH CONNECTION");
28017            self.write_space();
28018            self.generate_expression(connection)?;
28019        }
28020        if !e.options.is_empty() {
28021            self.write_space();
28022            self.generate_options_clause(&e.options)?;
28023        }
28024        self.write_space();
28025        self.write_keyword("AS");
28026        self.write_space();
28027        self.generate_expression(&e.this)?;
28028        Ok(())
28029    }
28030
28031    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
28032        // EXTERNAL [this]
28033        self.write_keyword("EXTERNAL");
28034        if let Some(this) = &e.this {
28035            self.write_space();
28036            self.generate_expression(this)?;
28037        }
28038        Ok(())
28039    }
28040
28041    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
28042        // Python: {no}FALLBACK{protection}
28043        if e.no.is_some() {
28044            self.write_keyword("NO ");
28045        }
28046        self.write_keyword("FALLBACK");
28047        if e.protection.is_some() {
28048            self.write_keyword(" PROTECTION");
28049        }
28050        Ok(())
28051    }
28052
28053    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
28054        // BigQuery: FARM_FINGERPRINT(value)
28055        self.write_keyword("FARM_FINGERPRINT");
28056        self.write("(");
28057        for (i, expr) in e.expressions.iter().enumerate() {
28058            if i > 0 {
28059                self.write(", ");
28060            }
28061            self.generate_expression(expr)?;
28062        }
28063        self.write(")");
28064        Ok(())
28065    }
28066
28067    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
28068        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
28069        self.write_keyword("FEATURES_AT_TIME");
28070        self.write("(");
28071        self.generate_expression(&e.this)?;
28072        if let Some(time) = &e.time {
28073            self.write(", ");
28074            self.generate_expression(time)?;
28075        }
28076        if let Some(num_rows) = &e.num_rows {
28077            self.write(", ");
28078            self.generate_expression(num_rows)?;
28079        }
28080        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
28081            self.write(", ");
28082            self.generate_expression(ignore_nulls)?;
28083        }
28084        self.write(")");
28085        Ok(())
28086    }
28087
28088    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
28089        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
28090        let use_limit = !e.percent
28091            && !e.with_ties
28092            && e.count.is_some()
28093            && matches!(
28094                self.config.dialect,
28095                Some(DialectType::Spark)
28096                    | Some(DialectType::Hive)
28097                    | Some(DialectType::DuckDB)
28098                    | Some(DialectType::SQLite)
28099                    | Some(DialectType::MySQL)
28100                    | Some(DialectType::BigQuery)
28101                    | Some(DialectType::Databricks)
28102                    | Some(DialectType::StarRocks)
28103                    | Some(DialectType::Doris)
28104                    | Some(DialectType::Athena)
28105                    | Some(DialectType::ClickHouse)
28106            );
28107
28108        if use_limit {
28109            self.write_keyword("LIMIT");
28110            self.write_space();
28111            self.generate_expression(e.count.as_ref().unwrap())?;
28112            return Ok(());
28113        }
28114
28115        // Python: FETCH direction count limit_options
28116        self.write_keyword("FETCH");
28117        if !e.direction.is_empty() {
28118            self.write_space();
28119            self.write_keyword(&e.direction);
28120        }
28121        if let Some(count) = &e.count {
28122            self.write_space();
28123            self.generate_expression(count)?;
28124        }
28125        // Generate PERCENT, ROWS, WITH TIES/ONLY
28126        if e.percent {
28127            self.write_keyword(" PERCENT");
28128        }
28129        if e.rows {
28130            self.write_keyword(" ROWS");
28131        }
28132        if e.with_ties {
28133            self.write_keyword(" WITH TIES");
28134        } else if e.rows {
28135            self.write_keyword(" ONLY");
28136        } else {
28137            self.write_keyword(" ROWS ONLY");
28138        }
28139        Ok(())
28140    }
28141
28142    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
28143        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
28144        // For Spark/Databricks without hive_format: USING this
28145        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
28146        if e.hive_format.is_some() {
28147            // Hive format: STORED AS ...
28148            self.write_keyword("STORED AS");
28149            self.write_space();
28150            if let Some(this) = &e.this {
28151                // Uppercase the format name (e.g., parquet -> PARQUET)
28152                if let Expression::Identifier(id) = this.as_ref() {
28153                    self.write_keyword(&id.name.to_ascii_uppercase());
28154                } else {
28155                    self.generate_expression(this)?;
28156                }
28157            }
28158        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
28159            // Hive: STORED AS format
28160            self.write_keyword("STORED AS");
28161            self.write_space();
28162            if let Some(this) = &e.this {
28163                if let Expression::Identifier(id) = this.as_ref() {
28164                    self.write_keyword(&id.name.to_ascii_uppercase());
28165                } else {
28166                    self.generate_expression(this)?;
28167                }
28168            }
28169        } else if matches!(
28170            self.config.dialect,
28171            Some(DialectType::Spark) | Some(DialectType::Databricks)
28172        ) {
28173            // Spark/Databricks: USING format (e.g., USING DELTA)
28174            self.write_keyword("USING");
28175            self.write_space();
28176            if let Some(this) = &e.this {
28177                self.generate_expression(this)?;
28178            }
28179        } else {
28180            // Snowflake/standard format
28181            self.write_keyword("FILE_FORMAT");
28182            self.write(" = ");
28183            if let Some(this) = &e.this {
28184                self.generate_expression(this)?;
28185            } else if !e.expressions.is_empty() {
28186                self.write("(");
28187                for (i, expr) in e.expressions.iter().enumerate() {
28188                    if i > 0 {
28189                        self.write(", ");
28190                    }
28191                    self.generate_expression(expr)?;
28192                }
28193                self.write(")");
28194            }
28195        }
28196        Ok(())
28197    }
28198
28199    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
28200        // agg_func FILTER(WHERE condition)
28201        self.generate_expression(&e.this)?;
28202        self.write_space();
28203        self.write_keyword("FILTER");
28204        self.write("(");
28205        self.write_keyword("WHERE");
28206        self.write_space();
28207        self.generate_expression(&e.expression)?;
28208        self.write(")");
28209        Ok(())
28210    }
28211
28212    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
28213        // FLOAT64(this) or FLOAT64(this, expression)
28214        self.write_keyword("FLOAT64");
28215        self.write("(");
28216        self.generate_expression(&e.this)?;
28217        if let Some(expr) = &e.expression {
28218            self.write(", ");
28219            self.generate_expression(expr)?;
28220        }
28221        self.write(")");
28222        Ok(())
28223    }
28224
28225    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
28226        // FOR this DO expression
28227        self.write_keyword("FOR");
28228        self.write_space();
28229        self.generate_expression(&e.this)?;
28230        self.write_space();
28231        self.write_keyword("DO");
28232        self.write_space();
28233        self.generate_expression(&e.expression)?;
28234        Ok(())
28235    }
28236
28237    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
28238        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
28239        self.write_keyword("FOREIGN KEY");
28240        if !e.expressions.is_empty() {
28241            self.write(" (");
28242            for (i, expr) in e.expressions.iter().enumerate() {
28243                if i > 0 {
28244                    self.write(", ");
28245                }
28246                self.generate_expression(expr)?;
28247            }
28248            self.write(")");
28249        }
28250        if let Some(reference) = &e.reference {
28251            self.write_space();
28252            self.generate_expression(reference)?;
28253        }
28254        if let Some(delete) = &e.delete {
28255            self.write_space();
28256            self.write_keyword("ON DELETE");
28257            self.write_space();
28258            self.generate_expression(delete)?;
28259        }
28260        if let Some(update) = &e.update {
28261            self.write_space();
28262            self.write_keyword("ON UPDATE");
28263            self.write_space();
28264            self.generate_expression(update)?;
28265        }
28266        if !e.options.is_empty() {
28267            self.write_space();
28268            for (i, opt) in e.options.iter().enumerate() {
28269                if i > 0 {
28270                    self.write_space();
28271                }
28272                self.generate_expression(opt)?;
28273            }
28274        }
28275        Ok(())
28276    }
28277
28278    fn generate_format(&mut self, e: &Format) -> Result<()> {
28279        // FORMAT(this, expressions...)
28280        self.write_keyword("FORMAT");
28281        self.write("(");
28282        self.generate_expression(&e.this)?;
28283        for expr in &e.expressions {
28284            self.write(", ");
28285            self.generate_expression(expr)?;
28286        }
28287        self.write(")");
28288        Ok(())
28289    }
28290
28291    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
28292        // Teradata: column (FORMAT 'format_string')
28293        self.generate_expression(&e.this)?;
28294        self.write(" (");
28295        self.write_keyword("FORMAT");
28296        self.write(" '");
28297        self.write(&e.format);
28298        self.write("')");
28299        Ok(())
28300    }
28301
28302    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
28303        // Python: FREESPACE=this[PERCENT]
28304        self.write_keyword("FREESPACE");
28305        self.write("=");
28306        self.generate_expression(&e.this)?;
28307        if e.percent.is_some() {
28308            self.write_keyword(" PERCENT");
28309        }
28310        Ok(())
28311    }
28312
28313    fn generate_from(&mut self, e: &From) -> Result<()> {
28314        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
28315        self.write_keyword("FROM");
28316        self.write_space();
28317
28318        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
28319        // But keep commas when TABLESAMPLE is present
28320        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
28321        use crate::dialects::DialectType;
28322        let has_tablesample = e
28323            .expressions
28324            .iter()
28325            .any(|expr| matches!(expr, Expression::TableSample(_)));
28326        let is_cross_join_dialect = matches!(
28327            self.config.dialect,
28328            Some(DialectType::BigQuery)
28329                | Some(DialectType::Hive)
28330                | Some(DialectType::Spark)
28331                | Some(DialectType::Databricks)
28332                | Some(DialectType::SQLite)
28333                | Some(DialectType::ClickHouse)
28334        );
28335        let source_is_same_as_target2 = self.config.source_dialect.is_some()
28336            && self.config.source_dialect == self.config.dialect;
28337        let source_is_cross_join_dialect2 = matches!(
28338            self.config.source_dialect,
28339            Some(DialectType::BigQuery)
28340                | Some(DialectType::Hive)
28341                | Some(DialectType::Spark)
28342                | Some(DialectType::Databricks)
28343                | Some(DialectType::SQLite)
28344                | Some(DialectType::ClickHouse)
28345        );
28346        let use_cross_join = !has_tablesample
28347            && is_cross_join_dialect
28348            && (source_is_same_as_target2
28349                || source_is_cross_join_dialect2
28350                || self.config.source_dialect.is_none());
28351
28352        // Snowflake wraps standalone VALUES in FROM clause with parentheses
28353        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
28354
28355        for (i, expr) in e.expressions.iter().enumerate() {
28356            if i > 0 {
28357                if use_cross_join {
28358                    self.write(" CROSS JOIN ");
28359                } else {
28360                    self.write(", ");
28361                }
28362            }
28363            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
28364                self.write("(");
28365                self.generate_expression(expr)?;
28366                self.write(")");
28367            } else {
28368                self.generate_expression(expr)?;
28369            }
28370            // Output leading comments that were on the table name before FROM
28371            // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
28372            let leading = Self::extract_table_leading_comments(expr);
28373            for comment in &leading {
28374                self.write_space();
28375                self.write_formatted_comment(comment);
28376            }
28377        }
28378        Ok(())
28379    }
28380
28381    /// Extract leading_comments from a table expression (possibly wrapped in PIVOT/UNPIVOT)
28382    fn extract_table_leading_comments(expr: &Expression) -> Vec<String> {
28383        match expr {
28384            Expression::Table(t) => t.leading_comments.clone(),
28385            Expression::Pivot(p) => {
28386                if let Expression::Table(t) = &p.this {
28387                    t.leading_comments.clone()
28388                } else {
28389                    Vec::new()
28390                }
28391            }
28392            _ => Vec::new(),
28393        }
28394    }
28395
28396    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
28397        // FROM_BASE(this, expression) - convert from base N
28398        self.write_keyword("FROM_BASE");
28399        self.write("(");
28400        self.generate_expression(&e.this)?;
28401        self.write(", ");
28402        self.generate_expression(&e.expression)?;
28403        self.write(")");
28404        Ok(())
28405    }
28406
28407    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
28408        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
28409        self.generate_expression(&e.this)?;
28410        if let Some(zone) = &e.zone {
28411            self.write_space();
28412            self.write_keyword("AT TIME ZONE");
28413            self.write_space();
28414            self.generate_expression(zone)?;
28415            self.write_space();
28416            self.write_keyword("AT TIME ZONE");
28417            self.write(" 'UTC'");
28418        }
28419        Ok(())
28420    }
28421
28422    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
28423        // GAP_FILL(this, ts_column, bucket_width, ...)
28424        self.write_keyword("GAP_FILL");
28425        self.write("(");
28426        self.generate_expression(&e.this)?;
28427        if let Some(ts_column) = &e.ts_column {
28428            self.write(", ");
28429            self.generate_expression(ts_column)?;
28430        }
28431        if let Some(bucket_width) = &e.bucket_width {
28432            self.write(", ");
28433            self.generate_expression(bucket_width)?;
28434        }
28435        if let Some(partitioning_columns) = &e.partitioning_columns {
28436            self.write(", ");
28437            self.generate_expression(partitioning_columns)?;
28438        }
28439        if let Some(value_columns) = &e.value_columns {
28440            self.write(", ");
28441            self.generate_expression(value_columns)?;
28442        }
28443        self.write(")");
28444        Ok(())
28445    }
28446
28447    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
28448        // GENERATE_DATE_ARRAY(start, end, step)
28449        self.write_keyword("GENERATE_DATE_ARRAY");
28450        self.write("(");
28451        let mut first = true;
28452        if let Some(start) = &e.start {
28453            self.generate_expression(start)?;
28454            first = false;
28455        }
28456        if let Some(end) = &e.end {
28457            if !first {
28458                self.write(", ");
28459            }
28460            self.generate_expression(end)?;
28461            first = false;
28462        }
28463        if let Some(step) = &e.step {
28464            if !first {
28465                self.write(", ");
28466            }
28467            self.generate_expression(step)?;
28468        }
28469        self.write(")");
28470        Ok(())
28471    }
28472
28473    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
28474        // ML.GENERATE_EMBEDDING(model, content, params)
28475        self.write_keyword("ML.GENERATE_EMBEDDING");
28476        self.write("(");
28477        self.generate_expression(&e.this)?;
28478        self.write(", ");
28479        self.generate_expression(&e.expression)?;
28480        if let Some(params) = &e.params_struct {
28481            self.write(", ");
28482            self.generate_expression(params)?;
28483        }
28484        self.write(")");
28485        Ok(())
28486    }
28487
28488    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
28489        // Dialect-specific function name
28490        let fn_name = match self.config.dialect {
28491            Some(DialectType::Presto)
28492            | Some(DialectType::Trino)
28493            | Some(DialectType::Athena)
28494            | Some(DialectType::Spark)
28495            | Some(DialectType::Databricks)
28496            | Some(DialectType::Hive) => "SEQUENCE",
28497            _ => "GENERATE_SERIES",
28498        };
28499        self.write_keyword(fn_name);
28500        self.write("(");
28501        let mut first = true;
28502        if let Some(start) = &e.start {
28503            self.generate_expression(start)?;
28504            first = false;
28505        }
28506        if let Some(end) = &e.end {
28507            if !first {
28508                self.write(", ");
28509            }
28510            self.generate_expression(end)?;
28511            first = false;
28512        }
28513        if let Some(step) = &e.step {
28514            if !first {
28515                self.write(", ");
28516            }
28517            // For Presto/Trino: convert WEEK intervals to DAY multiples
28518            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
28519            if matches!(
28520                self.config.dialect,
28521                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
28522            ) {
28523                if let Some(converted) = self.convert_week_interval_to_day(step) {
28524                    self.generate_expression(&converted)?;
28525                } else {
28526                    self.generate_expression(step)?;
28527                }
28528            } else {
28529                self.generate_expression(step)?;
28530            }
28531        }
28532        self.write(")");
28533        Ok(())
28534    }
28535
28536    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
28537    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
28538    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
28539        use crate::expressions::*;
28540        if let Expression::Interval(ref iv) = expr {
28541            // Check for structured WEEK unit
28542            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
28543                unit: IntervalUnit::Week,
28544                ..
28545            }) = &iv.unit
28546            {
28547                // Value is in iv.this
28548                let count = match &iv.this {
28549                    Some(Expression::Literal(lit)) => match lit.as_ref() {
28550                        Literal::String(s) | Literal::Number(s) => s.clone(),
28551                        _ => return None,
28552                    },
28553                    _ => return None,
28554                };
28555                (true, count)
28556            } else if iv.unit.is_none() {
28557                // Check for string-encoded interval like "1 WEEK"
28558                if let Some(Expression::Literal(lit)) = &iv.this {
28559                    if let Literal::String(s) = lit.as_ref() {
28560                        let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
28561                        if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
28562                            (true, parts[0].to_string())
28563                        } else {
28564                            (false, String::new())
28565                        }
28566                    } else {
28567                        (false, String::new())
28568                    }
28569                } else {
28570                    (false, String::new())
28571                }
28572            } else {
28573                (false, String::new())
28574            };
28575
28576            if is_week {
28577                // Build: (N * INTERVAL '7' DAY)
28578                let count_expr = Expression::Literal(Box::new(Literal::Number(count_str)));
28579                let day_interval = Expression::Interval(Box::new(Interval {
28580                    this: Some(Expression::Literal(Box::new(Literal::String(
28581                        "7".to_string(),
28582                    )))),
28583                    unit: Some(IntervalUnitSpec::Simple {
28584                        unit: IntervalUnit::Day,
28585                        use_plural: false,
28586                    }),
28587                }));
28588                let mul = Expression::Mul(Box::new(BinaryOp {
28589                    left: count_expr,
28590                    right: day_interval,
28591                    left_comments: vec![],
28592                    operator_comments: vec![],
28593                    trailing_comments: vec![],
28594                    inferred_type: None,
28595                }));
28596                return Some(Expression::Paren(Box::new(Paren {
28597                    this: mul,
28598                    trailing_comments: vec![],
28599                })));
28600            }
28601        }
28602        None
28603    }
28604
28605    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
28606        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
28607        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
28608        self.write("(");
28609        let mut first = true;
28610        if let Some(start) = &e.start {
28611            self.generate_expression(start)?;
28612            first = false;
28613        }
28614        if let Some(end) = &e.end {
28615            if !first {
28616                self.write(", ");
28617            }
28618            self.generate_expression(end)?;
28619            first = false;
28620        }
28621        if let Some(step) = &e.step {
28622            if !first {
28623                self.write(", ");
28624            }
28625            self.generate_expression(step)?;
28626        }
28627        self.write(")");
28628        Ok(())
28629    }
28630
28631    fn generate_generated_as_identity_column_constraint(
28632        &mut self,
28633        e: &GeneratedAsIdentityColumnConstraint,
28634    ) -> Result<()> {
28635        use crate::dialects::DialectType;
28636
28637        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
28638        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
28639            self.write_keyword("AUTOINCREMENT");
28640            if let Some(start) = &e.start {
28641                self.write_keyword(" START ");
28642                self.generate_expression(start)?;
28643            }
28644            if let Some(increment) = &e.increment {
28645                self.write_keyword(" INCREMENT ");
28646                self.generate_expression(increment)?;
28647            }
28648            return Ok(());
28649        }
28650
28651        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
28652        self.write_keyword("GENERATED");
28653        if let Some(this) = &e.this {
28654            // Check if it's a truthy boolean expression
28655            if let Expression::Boolean(b) = this.as_ref() {
28656                if b.value {
28657                    self.write_keyword(" ALWAYS");
28658                } else {
28659                    self.write_keyword(" BY DEFAULT");
28660                    if e.on_null.is_some() {
28661                        self.write_keyword(" ON NULL");
28662                    }
28663                }
28664            } else {
28665                self.write_keyword(" ALWAYS");
28666            }
28667        }
28668        self.write_keyword(" AS IDENTITY");
28669        // Add sequence options if any
28670        let has_options = e.start.is_some()
28671            || e.increment.is_some()
28672            || e.minvalue.is_some()
28673            || e.maxvalue.is_some();
28674        if has_options {
28675            self.write(" (");
28676            let mut first = true;
28677            if let Some(start) = &e.start {
28678                self.write_keyword("START WITH ");
28679                self.generate_expression(start)?;
28680                first = false;
28681            }
28682            if let Some(increment) = &e.increment {
28683                if !first {
28684                    self.write(" ");
28685                }
28686                self.write_keyword("INCREMENT BY ");
28687                self.generate_expression(increment)?;
28688                first = false;
28689            }
28690            if let Some(minvalue) = &e.minvalue {
28691                if !first {
28692                    self.write(" ");
28693                }
28694                self.write_keyword("MINVALUE ");
28695                self.generate_expression(minvalue)?;
28696                first = false;
28697            }
28698            if let Some(maxvalue) = &e.maxvalue {
28699                if !first {
28700                    self.write(" ");
28701                }
28702                self.write_keyword("MAXVALUE ");
28703                self.generate_expression(maxvalue)?;
28704            }
28705            self.write(")");
28706        }
28707        Ok(())
28708    }
28709
28710    fn generate_generated_as_row_column_constraint(
28711        &mut self,
28712        e: &GeneratedAsRowColumnConstraint,
28713    ) -> Result<()> {
28714        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
28715        self.write_keyword("GENERATED ALWAYS AS ROW ");
28716        if e.start.is_some() {
28717            self.write_keyword("START");
28718        } else {
28719            self.write_keyword("END");
28720        }
28721        if e.hidden.is_some() {
28722            self.write_keyword(" HIDDEN");
28723        }
28724        Ok(())
28725    }
28726
28727    fn generate_get(&mut self, e: &Get) -> Result<()> {
28728        // GET this target properties
28729        self.write_keyword("GET");
28730        self.write_space();
28731        self.generate_expression(&e.this)?;
28732        if let Some(target) = &e.target {
28733            self.write_space();
28734            self.generate_expression(target)?;
28735        }
28736        for prop in &e.properties {
28737            self.write_space();
28738            self.generate_expression(prop)?;
28739        }
28740        Ok(())
28741    }
28742
28743    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
28744        // GetExtract generates bracket access: this[expression]
28745        self.generate_expression(&e.this)?;
28746        self.write("[");
28747        self.generate_expression(&e.expression)?;
28748        self.write("]");
28749        Ok(())
28750    }
28751
28752    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
28753        // GETBIT(this, expression) or GET_BIT(this, expression)
28754        self.write_keyword("GETBIT");
28755        self.write("(");
28756        self.generate_expression(&e.this)?;
28757        self.write(", ");
28758        self.generate_expression(&e.expression)?;
28759        self.write(")");
28760        Ok(())
28761    }
28762
28763    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
28764        // [ROLE|GROUP|SHARE] name (e.g., "ROLE admin", "GROUP qa_users", "SHARE s1", or just "user1")
28765        if e.is_role {
28766            self.write_keyword("ROLE");
28767            self.write_space();
28768        } else if e.is_group {
28769            self.write_keyword("GROUP");
28770            self.write_space();
28771        } else if e.is_share {
28772            self.write_keyword("SHARE");
28773            self.write_space();
28774        }
28775        self.write(&e.name.name);
28776        Ok(())
28777    }
28778
28779    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
28780        // privilege(columns) or just privilege
28781        self.generate_expression(&e.this)?;
28782        if !e.expressions.is_empty() {
28783            self.write("(");
28784            for (i, expr) in e.expressions.iter().enumerate() {
28785                if i > 0 {
28786                    self.write(", ");
28787                }
28788                self.generate_expression(expr)?;
28789            }
28790            self.write(")");
28791        }
28792        Ok(())
28793    }
28794
28795    fn generate_group(&mut self, e: &Group) -> Result<()> {
28796        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
28797        self.write_keyword("GROUP BY");
28798        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
28799        match e.all {
28800            Some(true) => {
28801                self.write_space();
28802                self.write_keyword("ALL");
28803            }
28804            Some(false) => {
28805                self.write_space();
28806                self.write_keyword("DISTINCT");
28807            }
28808            None => {}
28809        }
28810        if !e.expressions.is_empty() {
28811            self.write_space();
28812            for (i, expr) in e.expressions.iter().enumerate() {
28813                if i > 0 {
28814                    self.write(", ");
28815                }
28816                self.generate_expression(expr)?;
28817            }
28818        }
28819        // Handle CUBE, ROLLUP, GROUPING SETS
28820        if let Some(cube) = &e.cube {
28821            if !e.expressions.is_empty() {
28822                self.write(", ");
28823            } else {
28824                self.write_space();
28825            }
28826            self.generate_expression(cube)?;
28827        }
28828        if let Some(rollup) = &e.rollup {
28829            if !e.expressions.is_empty() || e.cube.is_some() {
28830                self.write(", ");
28831            } else {
28832                self.write_space();
28833            }
28834            self.generate_expression(rollup)?;
28835        }
28836        if let Some(grouping_sets) = &e.grouping_sets {
28837            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
28838                self.write(", ");
28839            } else {
28840                self.write_space();
28841            }
28842            self.generate_expression(grouping_sets)?;
28843        }
28844        if let Some(totals) = &e.totals {
28845            self.write_space();
28846            self.write_keyword("WITH TOTALS");
28847            self.generate_expression(totals)?;
28848        }
28849        Ok(())
28850    }
28851
28852    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
28853        // GROUP BY expressions
28854        self.write_keyword("GROUP BY");
28855        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
28856        match e.all {
28857            Some(true) => {
28858                self.write_space();
28859                self.write_keyword("ALL");
28860            }
28861            Some(false) => {
28862                self.write_space();
28863                self.write_keyword("DISTINCT");
28864            }
28865            None => {}
28866        }
28867
28868        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
28869        // These are represented as Cube/Rollup expressions with empty expressions at the end
28870        let mut trailing_cube = false;
28871        let mut trailing_rollup = false;
28872        let mut regular_expressions: Vec<&Expression> = Vec::new();
28873
28874        for expr in &e.expressions {
28875            match expr {
28876                Expression::Cube(c) if c.expressions.is_empty() => {
28877                    trailing_cube = true;
28878                }
28879                Expression::Rollup(r) if r.expressions.is_empty() => {
28880                    trailing_rollup = true;
28881                }
28882                _ => {
28883                    regular_expressions.push(expr);
28884                }
28885            }
28886        }
28887
28888        // In pretty mode, put columns on separate lines
28889        if self.config.pretty {
28890            self.write_newline();
28891            self.indent_level += 1;
28892            for (i, expr) in regular_expressions.iter().enumerate() {
28893                if i > 0 {
28894                    self.write(",");
28895                    self.write_newline();
28896                }
28897                self.write_indent();
28898                self.generate_expression(expr)?;
28899            }
28900            self.indent_level -= 1;
28901        } else {
28902            self.write_space();
28903            for (i, expr) in regular_expressions.iter().enumerate() {
28904                if i > 0 {
28905                    self.write(", ");
28906                }
28907                self.generate_expression(expr)?;
28908            }
28909        }
28910
28911        // Output trailing WITH CUBE or WITH ROLLUP
28912        if trailing_cube {
28913            self.write_space();
28914            self.write_keyword("WITH CUBE");
28915        } else if trailing_rollup {
28916            self.write_space();
28917            self.write_keyword("WITH ROLLUP");
28918        }
28919
28920        // ClickHouse: WITH TOTALS
28921        if e.totals {
28922            self.write_space();
28923            self.write_keyword("WITH TOTALS");
28924        }
28925
28926        Ok(())
28927    }
28928
28929    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
28930        // GROUPING(col1, col2, ...)
28931        self.write_keyword("GROUPING");
28932        self.write("(");
28933        for (i, expr) in e.expressions.iter().enumerate() {
28934            if i > 0 {
28935                self.write(", ");
28936            }
28937            self.generate_expression(expr)?;
28938        }
28939        self.write(")");
28940        Ok(())
28941    }
28942
28943    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
28944        // GROUPING_ID(col1, col2, ...)
28945        self.write_keyword("GROUPING_ID");
28946        self.write("(");
28947        for (i, expr) in e.expressions.iter().enumerate() {
28948            if i > 0 {
28949                self.write(", ");
28950            }
28951            self.generate_expression(expr)?;
28952        }
28953        self.write(")");
28954        Ok(())
28955    }
28956
28957    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
28958        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
28959        self.write_keyword("GROUPING SETS");
28960        self.write(" (");
28961        for (i, expr) in e.expressions.iter().enumerate() {
28962            if i > 0 {
28963                self.write(", ");
28964            }
28965            self.generate_expression(expr)?;
28966        }
28967        self.write(")");
28968        Ok(())
28969    }
28970
28971    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
28972        // HASH_AGG(this, expressions...)
28973        self.write_keyword("HASH_AGG");
28974        self.write("(");
28975        self.generate_expression(&e.this)?;
28976        for expr in &e.expressions {
28977            self.write(", ");
28978            self.generate_expression(expr)?;
28979        }
28980        self.write(")");
28981        Ok(())
28982    }
28983
28984    fn generate_having(&mut self, e: &Having) -> Result<()> {
28985        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
28986        self.write_keyword("HAVING");
28987        self.write_space();
28988        self.generate_expression(&e.this)?;
28989        Ok(())
28990    }
28991
28992    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
28993        // Python: this HAVING MAX|MIN expression
28994        self.generate_expression(&e.this)?;
28995        self.write_space();
28996        self.write_keyword("HAVING");
28997        self.write_space();
28998        if e.max.is_some() {
28999            self.write_keyword("MAX");
29000        } else {
29001            self.write_keyword("MIN");
29002        }
29003        self.write_space();
29004        self.generate_expression(&e.expression)?;
29005        Ok(())
29006    }
29007
29008    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
29009        use crate::dialects::DialectType;
29010        // DuckDB: convert dollar-tagged strings to single-quoted
29011        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
29012            // Extract the string content and output as single-quoted
29013            if let Expression::Literal(ref lit) = *e.this {
29014                if let Literal::String(ref s) = lit.as_ref() {
29015                    return self.generate_string_literal(s);
29016                }
29017            }
29018        }
29019        // PostgreSQL: preserve dollar-quoting
29020        if matches!(
29021            self.config.dialect,
29022            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
29023        ) {
29024            self.write("$");
29025            if let Some(tag) = &e.tag {
29026                self.generate_expression(tag)?;
29027            }
29028            self.write("$");
29029            self.generate_expression(&e.this)?;
29030            self.write("$");
29031            if let Some(tag) = &e.tag {
29032                self.generate_expression(tag)?;
29033            }
29034            self.write("$");
29035            return Ok(());
29036        }
29037        // Default: output as dollar-tagged
29038        self.write("$");
29039        if let Some(tag) = &e.tag {
29040            self.generate_expression(tag)?;
29041        }
29042        self.write("$");
29043        self.generate_expression(&e.this)?;
29044        self.write("$");
29045        if let Some(tag) = &e.tag {
29046            self.generate_expression(tag)?;
29047        }
29048        self.write("$");
29049        Ok(())
29050    }
29051
29052    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
29053        // HEX_ENCODE(this)
29054        self.write_keyword("HEX_ENCODE");
29055        self.write("(");
29056        self.generate_expression(&e.this)?;
29057        self.write(")");
29058        Ok(())
29059    }
29060
29061    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
29062        // Python: this (kind => expression)
29063        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
29064        match e.this.as_ref() {
29065            Expression::Identifier(id) => self.write(&id.name),
29066            other => self.generate_expression(other)?,
29067        }
29068        self.write(" (");
29069        self.write(&e.kind);
29070        self.write(" => ");
29071        self.generate_expression(&e.expression)?;
29072        self.write(")");
29073        Ok(())
29074    }
29075
29076    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
29077        // HLL(this, expressions...)
29078        self.write_keyword("HLL");
29079        self.write("(");
29080        self.generate_expression(&e.this)?;
29081        for expr in &e.expressions {
29082            self.write(", ");
29083            self.generate_expression(expr)?;
29084        }
29085        self.write(")");
29086        Ok(())
29087    }
29088
29089    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
29090        // Python: IN|OUT|IN OUT
29091        if e.input_.is_some() && e.output.is_some() {
29092            self.write_keyword("IN OUT");
29093        } else if e.input_.is_some() {
29094            self.write_keyword("IN");
29095        } else if e.output.is_some() {
29096            self.write_keyword("OUT");
29097        }
29098        Ok(())
29099    }
29100
29101    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
29102        // Python: INCLUDE this [column_def] [AS alias]
29103        self.write_keyword("INCLUDE");
29104        self.write_space();
29105        self.generate_expression(&e.this)?;
29106        if let Some(column_def) = &e.column_def {
29107            self.write_space();
29108            self.generate_expression(column_def)?;
29109        }
29110        if let Some(alias) = &e.alias {
29111            self.write_space();
29112            self.write_keyword("AS");
29113            self.write_space();
29114            self.write(alias);
29115        }
29116        Ok(())
29117    }
29118
29119    fn generate_index(&mut self, e: &Index) -> Result<()> {
29120        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
29121        if e.unique {
29122            self.write_keyword("UNIQUE");
29123            self.write_space();
29124        }
29125        if e.primary.is_some() {
29126            self.write_keyword("PRIMARY");
29127            self.write_space();
29128        }
29129        if e.amp.is_some() {
29130            self.write_keyword("AMP");
29131            self.write_space();
29132        }
29133        if e.table.is_none() {
29134            self.write_keyword("INDEX");
29135            self.write_space();
29136        }
29137        if let Some(name) = &e.this {
29138            self.generate_expression(name)?;
29139            self.write_space();
29140        }
29141        if let Some(table) = &e.table {
29142            self.write_keyword("ON");
29143            self.write_space();
29144            self.generate_expression(table)?;
29145        }
29146        if !e.params.is_empty() {
29147            self.write("(");
29148            for (i, param) in e.params.iter().enumerate() {
29149                if i > 0 {
29150                    self.write(", ");
29151                }
29152                self.generate_expression(param)?;
29153            }
29154            self.write(")");
29155        }
29156        Ok(())
29157    }
29158
29159    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
29160        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
29161        if let Some(kind) = &e.kind {
29162            self.write(kind);
29163            self.write_space();
29164        }
29165        self.write_keyword("INDEX");
29166        if let Some(this) = &e.this {
29167            self.write_space();
29168            self.generate_expression(this)?;
29169        }
29170        if let Some(index_type) = &e.index_type {
29171            self.write_space();
29172            self.write_keyword("USING");
29173            self.write_space();
29174            self.generate_expression(index_type)?;
29175        }
29176        if !e.expressions.is_empty() {
29177            self.write(" (");
29178            for (i, expr) in e.expressions.iter().enumerate() {
29179                if i > 0 {
29180                    self.write(", ");
29181                }
29182                self.generate_expression(expr)?;
29183            }
29184            self.write(")");
29185        }
29186        for opt in &e.options {
29187            self.write_space();
29188            self.generate_expression(opt)?;
29189        }
29190        Ok(())
29191    }
29192
29193    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
29194        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
29195        if let Some(key_block_size) = &e.key_block_size {
29196            self.write_keyword("KEY_BLOCK_SIZE");
29197            self.write(" = ");
29198            self.generate_expression(key_block_size)?;
29199        } else if let Some(using) = &e.using {
29200            self.write_keyword("USING");
29201            self.write_space();
29202            self.generate_expression(using)?;
29203        } else if let Some(parser) = &e.parser {
29204            self.write_keyword("WITH PARSER");
29205            self.write_space();
29206            self.generate_expression(parser)?;
29207        } else if let Some(comment) = &e.comment {
29208            self.write_keyword("COMMENT");
29209            self.write_space();
29210            self.generate_expression(comment)?;
29211        } else if let Some(visible) = &e.visible {
29212            self.generate_expression(visible)?;
29213        } else if let Some(engine_attr) = &e.engine_attr {
29214            self.write_keyword("ENGINE_ATTRIBUTE");
29215            self.write(" = ");
29216            self.generate_expression(engine_attr)?;
29217        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
29218            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
29219            self.write(" = ");
29220            self.generate_expression(secondary_engine_attr)?;
29221        }
29222        Ok(())
29223    }
29224
29225    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
29226        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
29227        if let Some(using) = &e.using {
29228            self.write_keyword("USING");
29229            self.write_space();
29230            self.generate_expression(using)?;
29231        }
29232        if !e.columns.is_empty() {
29233            self.write("(");
29234            for (i, col) in e.columns.iter().enumerate() {
29235                if i > 0 {
29236                    self.write(", ");
29237                }
29238                self.generate_expression(col)?;
29239            }
29240            self.write(")");
29241        }
29242        if let Some(partition_by) = &e.partition_by {
29243            self.write_space();
29244            self.write_keyword("PARTITION BY");
29245            self.write_space();
29246            self.generate_expression(partition_by)?;
29247        }
29248        if let Some(where_) = &e.where_ {
29249            self.write_space();
29250            self.generate_expression(where_)?;
29251        }
29252        if let Some(include) = &e.include {
29253            self.write_space();
29254            self.write_keyword("INCLUDE");
29255            self.write(" (");
29256            self.generate_expression(include)?;
29257            self.write(")");
29258        }
29259        if let Some(with_storage) = &e.with_storage {
29260            self.write_space();
29261            self.write_keyword("WITH");
29262            self.write(" (");
29263            self.generate_expression(with_storage)?;
29264            self.write(")");
29265        }
29266        if let Some(tablespace) = &e.tablespace {
29267            self.write_space();
29268            self.write_keyword("USING INDEX TABLESPACE");
29269            self.write_space();
29270            self.generate_expression(tablespace)?;
29271        }
29272        Ok(())
29273    }
29274
29275    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
29276        // Python: this INDEX [FOR target] (expressions)
29277        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
29278        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
29279        if let Expression::Identifier(id) = &*e.this {
29280            self.write_keyword(&id.name);
29281        } else {
29282            self.generate_expression(&e.this)?;
29283        }
29284        self.write_space();
29285        self.write_keyword("INDEX");
29286        if let Some(target) = &e.target {
29287            self.write_space();
29288            self.write_keyword("FOR");
29289            self.write_space();
29290            if let Expression::Identifier(id) = &**target {
29291                self.write_keyword(&id.name);
29292            } else {
29293                self.generate_expression(target)?;
29294            }
29295        }
29296        // Always output parentheses (even if empty, e.g. USE INDEX ())
29297        self.write(" (");
29298        for (i, expr) in e.expressions.iter().enumerate() {
29299            if i > 0 {
29300                self.write(", ");
29301            }
29302            self.generate_expression(expr)?;
29303        }
29304        self.write(")");
29305        Ok(())
29306    }
29307
29308    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
29309        // INHERITS (table1, table2, ...)
29310        self.write_keyword("INHERITS");
29311        self.write(" (");
29312        for (i, expr) in e.expressions.iter().enumerate() {
29313            if i > 0 {
29314                self.write(", ");
29315            }
29316            self.generate_expression(expr)?;
29317        }
29318        self.write(")");
29319        Ok(())
29320    }
29321
29322    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
29323        // INPUT(model)
29324        self.write_keyword("INPUT");
29325        self.write("(");
29326        self.generate_expression(&e.this)?;
29327        self.write(")");
29328        Ok(())
29329    }
29330
29331    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
29332        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
29333        if let Some(input_format) = &e.input_format {
29334            self.write_keyword("INPUTFORMAT");
29335            self.write_space();
29336            self.generate_expression(input_format)?;
29337        }
29338        if let Some(output_format) = &e.output_format {
29339            if e.input_format.is_some() {
29340                self.write(" ");
29341            }
29342            self.write_keyword("OUTPUTFORMAT");
29343            self.write_space();
29344            self.generate_expression(output_format)?;
29345        }
29346        Ok(())
29347    }
29348
29349    fn generate_install(&mut self, e: &Install) -> Result<()> {
29350        // [FORCE] INSTALL extension [FROM source]
29351        if e.force.is_some() {
29352            self.write_keyword("FORCE");
29353            self.write_space();
29354        }
29355        self.write_keyword("INSTALL");
29356        self.write_space();
29357        self.generate_expression(&e.this)?;
29358        if let Some(from) = &e.from_ {
29359            self.write_space();
29360            self.write_keyword("FROM");
29361            self.write_space();
29362            self.generate_expression(from)?;
29363        }
29364        Ok(())
29365    }
29366
29367    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
29368        // INTERVAL 'expression' unit
29369        self.write_keyword("INTERVAL");
29370        self.write_space();
29371        // When a unit is specified and the expression is a number,
29372        self.generate_expression(&e.expression)?;
29373        if let Some(unit) = &e.unit {
29374            self.write_space();
29375            self.write(unit);
29376        }
29377        Ok(())
29378    }
29379
29380    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
29381        // unit TO unit (e.g., HOUR TO SECOND)
29382        self.write(&format!("{:?}", e.this).to_ascii_uppercase());
29383        self.write_space();
29384        self.write_keyword("TO");
29385        self.write_space();
29386        self.write(&format!("{:?}", e.expression).to_ascii_uppercase());
29387        Ok(())
29388    }
29389
29390    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
29391        // INTO [TEMPORARY|UNLOGGED] table
29392        self.write_keyword("INTO");
29393        if e.temporary {
29394            self.write_keyword(" TEMPORARY");
29395        }
29396        if e.unlogged.is_some() {
29397            self.write_keyword(" UNLOGGED");
29398        }
29399        if let Some(this) = &e.this {
29400            self.write_space();
29401            self.generate_expression(this)?;
29402        }
29403        if !e.expressions.is_empty() {
29404            self.write(" (");
29405            for (i, expr) in e.expressions.iter().enumerate() {
29406                if i > 0 {
29407                    self.write(", ");
29408                }
29409                self.generate_expression(expr)?;
29410            }
29411            self.write(")");
29412        }
29413        Ok(())
29414    }
29415
29416    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
29417        // Python: this expression (e.g., _utf8 'string')
29418        self.generate_expression(&e.this)?;
29419        self.write_space();
29420        self.generate_expression(&e.expression)?;
29421        Ok(())
29422    }
29423
29424    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
29425        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
29426        self.write_keyword("WITH");
29427        if e.no.is_some() {
29428            self.write_keyword(" NO");
29429        }
29430        if e.concurrent.is_some() {
29431            self.write_keyword(" CONCURRENT");
29432        }
29433        self.write_keyword(" ISOLATED LOADING");
29434        if let Some(target) = &e.target {
29435            self.write_space();
29436            self.generate_expression(target)?;
29437        }
29438        Ok(())
29439    }
29440
29441    fn generate_json(&mut self, e: &JSON) -> Result<()> {
29442        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
29443        self.write_keyword("JSON");
29444        if let Some(this) = &e.this {
29445            self.write_space();
29446            self.generate_expression(this)?;
29447        }
29448        if let Some(with_) = &e.with_ {
29449            // Check if it's a truthy boolean
29450            if let Expression::Boolean(b) = with_.as_ref() {
29451                if b.value {
29452                    self.write_keyword(" WITH");
29453                } else {
29454                    self.write_keyword(" WITHOUT");
29455                }
29456            }
29457        }
29458        if e.unique {
29459            self.write_keyword(" UNIQUE KEYS");
29460        }
29461        Ok(())
29462    }
29463
29464    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
29465        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
29466        self.write_keyword("JSON_ARRAY");
29467        self.write("(");
29468        for (i, expr) in e.expressions.iter().enumerate() {
29469            if i > 0 {
29470                self.write(", ");
29471            }
29472            self.generate_expression(expr)?;
29473        }
29474        if let Some(null_handling) = &e.null_handling {
29475            self.write_space();
29476            self.generate_expression(null_handling)?;
29477        }
29478        if let Some(return_type) = &e.return_type {
29479            self.write_space();
29480            self.write_keyword("RETURNING");
29481            self.write_space();
29482            self.generate_expression(return_type)?;
29483        }
29484        if e.strict.is_some() {
29485            self.write_space();
29486            self.write_keyword("STRICT");
29487        }
29488        self.write(")");
29489        Ok(())
29490    }
29491
29492    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
29493        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
29494        self.write_keyword("JSON_ARRAYAGG");
29495        self.write("(");
29496        self.generate_expression(&e.this)?;
29497        if let Some(order) = &e.order {
29498            self.write_space();
29499            // Order is stored as an OrderBy expression
29500            if let Expression::OrderBy(ob) = order.as_ref() {
29501                self.write_keyword("ORDER BY");
29502                self.write_space();
29503                for (i, ord) in ob.expressions.iter().enumerate() {
29504                    if i > 0 {
29505                        self.write(", ");
29506                    }
29507                    self.generate_ordered(ord)?;
29508                }
29509            } else {
29510                // Fallback: generate the expression directly
29511                self.generate_expression(order)?;
29512            }
29513        }
29514        if let Some(null_handling) = &e.null_handling {
29515            self.write_space();
29516            self.generate_expression(null_handling)?;
29517        }
29518        if let Some(return_type) = &e.return_type {
29519            self.write_space();
29520            self.write_keyword("RETURNING");
29521            self.write_space();
29522            self.generate_expression(return_type)?;
29523        }
29524        if e.strict.is_some() {
29525            self.write_space();
29526            self.write_keyword("STRICT");
29527        }
29528        self.write(")");
29529        Ok(())
29530    }
29531
29532    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
29533        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
29534        self.write_keyword("JSON_OBJECTAGG");
29535        self.write("(");
29536        for (i, expr) in e.expressions.iter().enumerate() {
29537            if i > 0 {
29538                self.write(", ");
29539            }
29540            self.generate_expression(expr)?;
29541        }
29542        if let Some(null_handling) = &e.null_handling {
29543            self.write_space();
29544            self.generate_expression(null_handling)?;
29545        }
29546        if let Some(unique_keys) = &e.unique_keys {
29547            self.write_space();
29548            if let Expression::Boolean(b) = unique_keys.as_ref() {
29549                if b.value {
29550                    self.write_keyword("WITH UNIQUE KEYS");
29551                } else {
29552                    self.write_keyword("WITHOUT UNIQUE KEYS");
29553                }
29554            }
29555        }
29556        if let Some(return_type) = &e.return_type {
29557            self.write_space();
29558            self.write_keyword("RETURNING");
29559            self.write_space();
29560            self.generate_expression(return_type)?;
29561        }
29562        self.write(")");
29563        Ok(())
29564    }
29565
29566    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
29567        // JSON_ARRAY_APPEND(this, path, value, ...)
29568        self.write_keyword("JSON_ARRAY_APPEND");
29569        self.write("(");
29570        self.generate_expression(&e.this)?;
29571        for expr in &e.expressions {
29572            self.write(", ");
29573            self.generate_expression(expr)?;
29574        }
29575        self.write(")");
29576        Ok(())
29577    }
29578
29579    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
29580        // JSON_ARRAY_CONTAINS(this, expression)
29581        self.write_keyword("JSON_ARRAY_CONTAINS");
29582        self.write("(");
29583        self.generate_expression(&e.this)?;
29584        self.write(", ");
29585        self.generate_expression(&e.expression)?;
29586        self.write(")");
29587        Ok(())
29588    }
29589
29590    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
29591        // JSON_ARRAY_INSERT(this, path, value, ...)
29592        self.write_keyword("JSON_ARRAY_INSERT");
29593        self.write("(");
29594        self.generate_expression(&e.this)?;
29595        for expr in &e.expressions {
29596            self.write(", ");
29597            self.generate_expression(expr)?;
29598        }
29599        self.write(")");
29600        Ok(())
29601    }
29602
29603    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
29604        // JSONB_EXISTS(this, path)
29605        self.write_keyword("JSONB_EXISTS");
29606        self.write("(");
29607        self.generate_expression(&e.this)?;
29608        if let Some(path) = &e.path {
29609            self.write(", ");
29610            self.generate_expression(path)?;
29611        }
29612        self.write(")");
29613        Ok(())
29614    }
29615
29616    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
29617        // JSONB_EXTRACT_SCALAR(this, expression)
29618        self.write_keyword("JSONB_EXTRACT_SCALAR");
29619        self.write("(");
29620        self.generate_expression(&e.this)?;
29621        self.write(", ");
29622        self.generate_expression(&e.expression)?;
29623        self.write(")");
29624        Ok(())
29625    }
29626
29627    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
29628        // JSONB_OBJECT_AGG(this, expression)
29629        self.write_keyword("JSONB_OBJECT_AGG");
29630        self.write("(");
29631        self.generate_expression(&e.this)?;
29632        self.write(", ");
29633        self.generate_expression(&e.expression)?;
29634        self.write(")");
29635        Ok(())
29636    }
29637
29638    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
29639        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
29640        if let Some(nested_schema) = &e.nested_schema {
29641            self.write_keyword("NESTED");
29642            if let Some(path) = &e.path {
29643                self.write_space();
29644                self.write_keyword("PATH");
29645                self.write_space();
29646                self.generate_expression(path)?;
29647            }
29648            self.write_space();
29649            self.generate_expression(nested_schema)?;
29650        } else {
29651            if let Some(this) = &e.this {
29652                self.generate_expression(this)?;
29653            }
29654            if let Some(kind) = &e.kind {
29655                self.write_space();
29656                self.write(kind);
29657            }
29658            if let Some(path) = &e.path {
29659                self.write_space();
29660                self.write_keyword("PATH");
29661                self.write_space();
29662                self.generate_expression(path)?;
29663            }
29664            if e.ordinality.is_some() {
29665                self.write_keyword(" FOR ORDINALITY");
29666            }
29667        }
29668        Ok(())
29669    }
29670
29671    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
29672        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
29673        self.write_keyword("JSON_EXISTS");
29674        self.write("(");
29675        self.generate_expression(&e.this)?;
29676        if let Some(path) = &e.path {
29677            self.write(", ");
29678            self.generate_expression(path)?;
29679        }
29680        if let Some(passing) = &e.passing {
29681            self.write_space();
29682            self.write_keyword("PASSING");
29683            self.write_space();
29684            self.generate_expression(passing)?;
29685        }
29686        if let Some(on_condition) = &e.on_condition {
29687            self.write_space();
29688            self.generate_expression(on_condition)?;
29689        }
29690        self.write(")");
29691        Ok(())
29692    }
29693
29694    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
29695        self.generate_expression(&e.this)?;
29696        self.write(".:");
29697        // If the data type has nested type parameters (like Array(JSON), Map(String, Int)),
29698        // wrap the entire type string in double quotes.
29699        // This matches Python sqlglot's ClickHouse _json_cast_sql behavior.
29700        if Self::data_type_has_nested_expressions(&e.to) {
29701            // Generate the data type to a temporary string buffer, then wrap in quotes
29702            let saved = std::mem::take(&mut self.output);
29703            self.generate_data_type(&e.to)?;
29704            let type_sql = std::mem::replace(&mut self.output, saved);
29705            self.write("\"");
29706            self.write(&type_sql);
29707            self.write("\"");
29708        } else {
29709            self.generate_data_type(&e.to)?;
29710        }
29711        Ok(())
29712    }
29713
29714    /// Check if a DataType has nested type expressions (sub-types).
29715    /// This corresponds to Python sqlglot's `to.expressions` being non-empty.
29716    fn data_type_has_nested_expressions(dt: &DataType) -> bool {
29717        matches!(
29718            dt,
29719            DataType::Array { .. } | DataType::Map { .. } | DataType::Struct { .. }
29720        )
29721    }
29722
29723    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
29724        // JSON_EXTRACT_ARRAY(this, expression)
29725        self.write_keyword("JSON_EXTRACT_ARRAY");
29726        self.write("(");
29727        self.generate_expression(&e.this)?;
29728        if let Some(expr) = &e.expression {
29729            self.write(", ");
29730            self.generate_expression(expr)?;
29731        }
29732        self.write(")");
29733        Ok(())
29734    }
29735
29736    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
29737        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
29738        if let Some(option) = &e.option {
29739            self.generate_expression(option)?;
29740            self.write_space();
29741        }
29742        self.write_keyword("QUOTES");
29743        if e.scalar.is_some() {
29744            self.write_keyword(" SCALAR_ONLY");
29745        }
29746        Ok(())
29747    }
29748
29749    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
29750        // JSON_EXTRACT_SCALAR(this, expression)
29751        self.write_keyword("JSON_EXTRACT_SCALAR");
29752        self.write("(");
29753        self.generate_expression(&e.this)?;
29754        self.write(", ");
29755        self.generate_expression(&e.expression)?;
29756        self.write(")");
29757        Ok(())
29758    }
29759
29760    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
29761        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
29762        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
29763        // Otherwise output JSON_EXTRACT(this, expression)
29764        if e.variant_extract.is_some() {
29765            use crate::dialects::DialectType;
29766            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
29767                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
29768                // Keys that are not safe identifiers (contain hyphens, spaces, etc.) must use
29769                // bracket notation: c:["x-y"] instead of c:x-y
29770                self.generate_expression(&e.this)?;
29771                self.write(":");
29772                match e.expression.as_ref() {
29773                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
29774                        let Literal::String(s) = lit.as_ref() else {
29775                            unreachable!()
29776                        };
29777                        self.write_databricks_json_path(s);
29778                    }
29779                    _ => {
29780                        // Fallback: generate as-is (shouldn't happen in typical cases)
29781                        self.generate_expression(&e.expression)?;
29782                    }
29783                }
29784            } else {
29785                // Snowflake and others: use GET_PATH(col, 'path')
29786                self.write_keyword("GET_PATH");
29787                self.write("(");
29788                self.generate_expression(&e.this)?;
29789                self.write(", ");
29790                self.generate_expression(&e.expression)?;
29791                self.write(")");
29792            }
29793        } else {
29794            self.write_keyword("JSON_EXTRACT");
29795            self.write("(");
29796            self.generate_expression(&e.this)?;
29797            self.write(", ");
29798            self.generate_expression(&e.expression)?;
29799            for expr in &e.expressions {
29800                self.write(", ");
29801                self.generate_expression(expr)?;
29802            }
29803            self.write(")");
29804        }
29805        Ok(())
29806    }
29807
29808    /// Write a Databricks JSON colon-path, using bracket notation for keys
29809    /// that are not safe identifiers (e.g., contain hyphens, spaces, etc.)
29810    /// Safe identifier regex: ^[_a-zA-Z]\w*$
29811    fn write_databricks_json_path(&mut self, path: &str) {
29812        // If the path already starts with bracket notation (e.g., '["fr\'uit"]'),
29813        // it was already formatted by the parser - output as-is
29814        if path.starts_with("[\"") || path.starts_with("['") {
29815            self.write(path);
29816            return;
29817        }
29818        // Split the path into segments at '.' boundaries, but preserve bracket subscripts
29819        // e.g., "price.items[0].name" -> ["price", "items[0]", "name"]
29820        // e.g., "x-y" -> ["x-y"]
29821        let mut first = true;
29822        for segment in path.split('.') {
29823            if !first {
29824                self.write(".");
29825            }
29826            first = false;
29827            // Check if there's a bracket subscript in this segment: "items[0]"
29828            if let Some(bracket_pos) = segment.find('[') {
29829                let key = &segment[..bracket_pos];
29830                let subscript = &segment[bracket_pos..];
29831                if key.is_empty() {
29832                    // Bracket notation at start of segment (e.g., already formatted)
29833                    self.write(segment);
29834                } else if Self::is_safe_json_path_key(key) {
29835                    self.write(key);
29836                    self.write(subscript);
29837                } else {
29838                    self.write("[\"");
29839                    self.write(key);
29840                    self.write("\"]");
29841                    self.write(subscript);
29842                }
29843            } else if Self::is_safe_json_path_key(segment) {
29844                self.write(segment);
29845            } else {
29846                self.write("[\"");
29847                self.write(segment);
29848                self.write("\"]");
29849            }
29850        }
29851    }
29852
29853    /// Check if a JSON path key is a safe identifier that doesn't need bracket quoting.
29854    /// Matches Python sqlglot's SAFE_IDENTIFIER_RE: ^[_a-zA-Z]\w*$
29855    fn is_safe_json_path_key(key: &str) -> bool {
29856        if key.is_empty() {
29857            return false;
29858        }
29859        let mut chars = key.chars();
29860        let first = chars.next().unwrap();
29861        if first != '_' && !first.is_ascii_alphabetic() {
29862            return false;
29863        }
29864        chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
29865    }
29866
29867    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
29868        // Output: {expr} FORMAT JSON
29869        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
29870        if let Some(this) = &e.this {
29871            self.generate_expression(this)?;
29872            self.write_space();
29873        }
29874        self.write_keyword("FORMAT JSON");
29875        Ok(())
29876    }
29877
29878    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
29879        // key: value (for JSON objects)
29880        self.generate_expression(&e.this)?;
29881        self.write(": ");
29882        self.generate_expression(&e.expression)?;
29883        Ok(())
29884    }
29885
29886    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
29887        // JSON_KEYS(this, expression, expressions...)
29888        self.write_keyword("JSON_KEYS");
29889        self.write("(");
29890        self.generate_expression(&e.this)?;
29891        if let Some(expr) = &e.expression {
29892            self.write(", ");
29893            self.generate_expression(expr)?;
29894        }
29895        for expr in &e.expressions {
29896            self.write(", ");
29897            self.generate_expression(expr)?;
29898        }
29899        self.write(")");
29900        Ok(())
29901    }
29902
29903    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
29904        // JSON_KEYS(this, expression)
29905        self.write_keyword("JSON_KEYS");
29906        self.write("(");
29907        self.generate_expression(&e.this)?;
29908        if let Some(expr) = &e.expression {
29909            self.write(", ");
29910            self.generate_expression(expr)?;
29911        }
29912        self.write(")");
29913        Ok(())
29914    }
29915
29916    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
29917        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
29918        // The path components are concatenated without spaces
29919        let mut path_str = String::new();
29920        for expr in &e.expressions {
29921            match expr {
29922                Expression::JSONPathRoot(_) => {
29923                    path_str.push('$');
29924                }
29925                Expression::JSONPathKey(k) => {
29926                    // .key or ."key" (quote if key has special characters)
29927                    if let Expression::Literal(lit) = k.this.as_ref() {
29928                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
29929                            path_str.push('.');
29930                            // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
29931                            let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
29932                            if needs_quoting {
29933                                path_str.push('"');
29934                                path_str.push_str(s);
29935                                path_str.push('"');
29936                            } else {
29937                                path_str.push_str(s);
29938                            }
29939                        }
29940                    }
29941                }
29942                Expression::JSONPathSubscript(s) => {
29943                    // [index]
29944                    if let Expression::Literal(lit) = s.this.as_ref() {
29945                        if let crate::expressions::Literal::Number(n) = lit.as_ref() {
29946                            path_str.push('[');
29947                            path_str.push_str(n);
29948                            path_str.push(']');
29949                        }
29950                    }
29951                }
29952                _ => {
29953                    // For other path parts, try to generate them
29954                    let mut temp_gen = Self::with_arc_config(self.config.clone());
29955                    temp_gen.generate_expression(expr)?;
29956                    path_str.push_str(&temp_gen.output);
29957                }
29958            }
29959        }
29960        // Output as quoted string
29961        self.write("'");
29962        self.write(&path_str);
29963        self.write("'");
29964        Ok(())
29965    }
29966
29967    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
29968        // JSON path filter: ?(predicate)
29969        self.write("?(");
29970        self.generate_expression(&e.this)?;
29971        self.write(")");
29972        Ok(())
29973    }
29974
29975    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
29976        // JSON path key: .key or ["key"]
29977        self.write(".");
29978        self.generate_expression(&e.this)?;
29979        Ok(())
29980    }
29981
29982    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
29983        // JSON path recursive descent: ..
29984        self.write("..");
29985        if let Some(this) = &e.this {
29986            self.generate_expression(this)?;
29987        }
29988        Ok(())
29989    }
29990
29991    fn generate_json_path_root(&mut self) -> Result<()> {
29992        // JSON path root: $
29993        self.write("$");
29994        Ok(())
29995    }
29996
29997    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
29998        // JSON path script: (expression)
29999        self.write("(");
30000        self.generate_expression(&e.this)?;
30001        self.write(")");
30002        Ok(())
30003    }
30004
30005    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
30006        // JSON path selector: *
30007        self.generate_expression(&e.this)?;
30008        Ok(())
30009    }
30010
30011    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
30012        // JSON path slice: [start:end:step]
30013        self.write("[");
30014        if let Some(start) = &e.start {
30015            self.generate_expression(start)?;
30016        }
30017        self.write(":");
30018        if let Some(end) = &e.end {
30019            self.generate_expression(end)?;
30020        }
30021        if let Some(step) = &e.step {
30022            self.write(":");
30023            self.generate_expression(step)?;
30024        }
30025        self.write("]");
30026        Ok(())
30027    }
30028
30029    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
30030        // JSON path subscript: [index] or [*]
30031        self.write("[");
30032        self.generate_expression(&e.this)?;
30033        self.write("]");
30034        Ok(())
30035    }
30036
30037    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
30038        // JSON path union: [key1, key2, ...]
30039        self.write("[");
30040        for (i, expr) in e.expressions.iter().enumerate() {
30041            if i > 0 {
30042                self.write(", ");
30043            }
30044            self.generate_expression(expr)?;
30045        }
30046        self.write("]");
30047        Ok(())
30048    }
30049
30050    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
30051        // JSON_REMOVE(this, path1, path2, ...)
30052        self.write_keyword("JSON_REMOVE");
30053        self.write("(");
30054        self.generate_expression(&e.this)?;
30055        for expr in &e.expressions {
30056            self.write(", ");
30057            self.generate_expression(expr)?;
30058        }
30059        self.write(")");
30060        Ok(())
30061    }
30062
30063    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
30064        // COLUMNS(col1 type, col2 type, ...)
30065        // When pretty printing and content is too wide, format with each column on a separate line
30066        self.write_keyword("COLUMNS");
30067        self.write("(");
30068
30069        if self.config.pretty && !e.expressions.is_empty() {
30070            // First, generate all expressions into strings to check width
30071            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
30072            for expr in &e.expressions {
30073                let mut temp_gen = Generator::with_arc_config(self.config.clone());
30074                temp_gen.generate_expression(expr)?;
30075                expr_strings.push(temp_gen.output);
30076            }
30077
30078            // Check if total width exceeds max_text_width
30079            if self.too_wide(&expr_strings) {
30080                // Pretty print: each column on its own line
30081                self.write_newline();
30082                self.indent_level += 1;
30083                for (i, expr_str) in expr_strings.iter().enumerate() {
30084                    if i > 0 {
30085                        self.write(",");
30086                        self.write_newline();
30087                    }
30088                    self.write_indent();
30089                    self.write(expr_str);
30090                }
30091                self.write_newline();
30092                self.indent_level -= 1;
30093                self.write_indent();
30094            } else {
30095                // Compact: all on one line
30096                for (i, expr_str) in expr_strings.iter().enumerate() {
30097                    if i > 0 {
30098                        self.write(", ");
30099                    }
30100                    self.write(expr_str);
30101                }
30102            }
30103        } else {
30104            // Non-pretty mode: compact format
30105            for (i, expr) in e.expressions.iter().enumerate() {
30106                if i > 0 {
30107                    self.write(", ");
30108                }
30109                self.generate_expression(expr)?;
30110            }
30111        }
30112        self.write(")");
30113        Ok(())
30114    }
30115
30116    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
30117        // JSON_SET(this, path, value, ...)
30118        self.write_keyword("JSON_SET");
30119        self.write("(");
30120        self.generate_expression(&e.this)?;
30121        for expr in &e.expressions {
30122            self.write(", ");
30123            self.generate_expression(expr)?;
30124        }
30125        self.write(")");
30126        Ok(())
30127    }
30128
30129    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
30130        // JSON_STRIP_NULLS(this, expression)
30131        self.write_keyword("JSON_STRIP_NULLS");
30132        self.write("(");
30133        self.generate_expression(&e.this)?;
30134        if let Some(expr) = &e.expression {
30135            self.write(", ");
30136            self.generate_expression(expr)?;
30137        }
30138        self.write(")");
30139        Ok(())
30140    }
30141
30142    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
30143        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
30144        self.write_keyword("JSON_TABLE");
30145        self.write("(");
30146        self.generate_expression(&e.this)?;
30147        if let Some(path) = &e.path {
30148            self.write(", ");
30149            self.generate_expression(path)?;
30150        }
30151        if let Some(error_handling) = &e.error_handling {
30152            self.write_space();
30153            self.generate_expression(error_handling)?;
30154        }
30155        if let Some(empty_handling) = &e.empty_handling {
30156            self.write_space();
30157            self.generate_expression(empty_handling)?;
30158        }
30159        if let Some(schema) = &e.schema {
30160            self.write_space();
30161            self.generate_expression(schema)?;
30162        }
30163        self.write(")");
30164        Ok(())
30165    }
30166
30167    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
30168        // JSON_TYPE(this)
30169        self.write_keyword("JSON_TYPE");
30170        self.write("(");
30171        self.generate_expression(&e.this)?;
30172        self.write(")");
30173        Ok(())
30174    }
30175
30176    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
30177        // JSON_VALUE(this, path RETURNING type ON condition)
30178        self.write_keyword("JSON_VALUE");
30179        self.write("(");
30180        self.generate_expression(&e.this)?;
30181        if let Some(path) = &e.path {
30182            self.write(", ");
30183            self.generate_expression(path)?;
30184        }
30185        if let Some(returning) = &e.returning {
30186            self.write_space();
30187            self.write_keyword("RETURNING");
30188            self.write_space();
30189            self.generate_expression(returning)?;
30190        }
30191        if let Some(on_condition) = &e.on_condition {
30192            self.write_space();
30193            self.generate_expression(on_condition)?;
30194        }
30195        self.write(")");
30196        Ok(())
30197    }
30198
30199    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
30200        // JSON_VALUE_ARRAY(this)
30201        self.write_keyword("JSON_VALUE_ARRAY");
30202        self.write("(");
30203        self.generate_expression(&e.this)?;
30204        self.write(")");
30205        Ok(())
30206    }
30207
30208    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
30209        // JAROWINKLER_SIMILARITY(str1, str2)
30210        self.write_keyword("JAROWINKLER_SIMILARITY");
30211        self.write("(");
30212        self.generate_expression(&e.this)?;
30213        self.write(", ");
30214        self.generate_expression(&e.expression)?;
30215        self.write(")");
30216        Ok(())
30217    }
30218
30219    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
30220        // Python: this(expressions)
30221        self.generate_expression(&e.this)?;
30222        self.write("(");
30223        for (i, expr) in e.expressions.iter().enumerate() {
30224            if i > 0 {
30225                self.write(", ");
30226            }
30227            self.generate_expression(expr)?;
30228        }
30229        self.write(")");
30230        Ok(())
30231    }
30232
30233    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
30234        // Python: {no}{local}{dual}{before}{after}JOURNAL
30235        if e.no.is_some() {
30236            self.write_keyword("NO ");
30237        }
30238        if let Some(local) = &e.local {
30239            self.generate_expression(local)?;
30240            self.write_space();
30241        }
30242        if e.dual.is_some() {
30243            self.write_keyword("DUAL ");
30244        }
30245        if e.before.is_some() {
30246            self.write_keyword("BEFORE ");
30247        }
30248        if e.after.is_some() {
30249            self.write_keyword("AFTER ");
30250        }
30251        self.write_keyword("JOURNAL");
30252        Ok(())
30253    }
30254
30255    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
30256        // LANGUAGE language_name
30257        self.write_keyword("LANGUAGE");
30258        self.write_space();
30259        self.generate_expression(&e.this)?;
30260        Ok(())
30261    }
30262
30263    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
30264        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
30265        if e.view.is_some() {
30266            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
30267            self.write_keyword("LATERAL VIEW");
30268            if e.outer.is_some() {
30269                self.write_space();
30270                self.write_keyword("OUTER");
30271            }
30272            self.write_space();
30273            self.generate_expression(&e.this)?;
30274            if let Some(alias) = &e.alias {
30275                self.write_space();
30276                self.write(alias);
30277            }
30278        } else {
30279            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
30280            self.write_keyword("LATERAL");
30281            self.write_space();
30282            self.generate_expression(&e.this)?;
30283            if e.ordinality.is_some() {
30284                self.write_space();
30285                self.write_keyword("WITH ORDINALITY");
30286            }
30287            if let Some(alias) = &e.alias {
30288                self.write_space();
30289                self.write_keyword("AS");
30290                self.write_space();
30291                self.write(alias);
30292                if !e.column_aliases.is_empty() {
30293                    self.write("(");
30294                    for (i, col) in e.column_aliases.iter().enumerate() {
30295                        if i > 0 {
30296                            self.write(", ");
30297                        }
30298                        self.write(col);
30299                    }
30300                    self.write(")");
30301                }
30302            }
30303        }
30304        Ok(())
30305    }
30306
30307    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
30308        // Python: LIKE this [options]
30309        self.write_keyword("LIKE");
30310        self.write_space();
30311        self.generate_expression(&e.this)?;
30312        for expr in &e.expressions {
30313            self.write_space();
30314            self.generate_expression(expr)?;
30315        }
30316        Ok(())
30317    }
30318
30319    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
30320        self.write_keyword("LIMIT");
30321        self.write_space();
30322        self.write_limit_expr(&e.this)?;
30323        if e.percent {
30324            self.write_space();
30325            self.write_keyword("PERCENT");
30326        }
30327        // Emit any comments that were captured from before the LIMIT keyword
30328        for comment in &e.comments {
30329            self.write(" ");
30330            self.write_formatted_comment(comment);
30331        }
30332        Ok(())
30333    }
30334
30335    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
30336        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
30337        if e.percent.is_some() {
30338            self.write_keyword(" PERCENT");
30339        }
30340        if e.rows.is_some() {
30341            self.write_keyword(" ROWS");
30342        }
30343        if e.with_ties.is_some() {
30344            self.write_keyword(" WITH TIES");
30345        } else if e.rows.is_some() {
30346            self.write_keyword(" ONLY");
30347        }
30348        Ok(())
30349    }
30350
30351    fn generate_list(&mut self, e: &List) -> Result<()> {
30352        use crate::dialects::DialectType;
30353        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
30354
30355        // Check if this is a subquery-based list (LIST(SELECT ...))
30356        if e.expressions.len() == 1 {
30357            if let Expression::Select(_) = &e.expressions[0] {
30358                self.write_keyword("LIST");
30359                self.write("(");
30360                self.generate_expression(&e.expressions[0])?;
30361                self.write(")");
30362                return Ok(());
30363            }
30364        }
30365
30366        // For Materialize, output as LIST[expr, expr, ...]
30367        if is_materialize {
30368            self.write_keyword("LIST");
30369            self.write("[");
30370            for (i, expr) in e.expressions.iter().enumerate() {
30371                if i > 0 {
30372                    self.write(", ");
30373                }
30374                self.generate_expression(expr)?;
30375            }
30376            self.write("]");
30377        } else {
30378            // For other dialects, output as LIST(expr, expr, ...)
30379            self.write_keyword("LIST");
30380            self.write("(");
30381            for (i, expr) in e.expressions.iter().enumerate() {
30382                if i > 0 {
30383                    self.write(", ");
30384                }
30385                self.generate_expression(expr)?;
30386            }
30387            self.write(")");
30388        }
30389        Ok(())
30390    }
30391
30392    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
30393        // Check if this is a subquery-based map (MAP(SELECT ...))
30394        if let Expression::Select(_) = &*e.this {
30395            self.write_keyword("MAP");
30396            self.write("(");
30397            self.generate_expression(&e.this)?;
30398            self.write(")");
30399            return Ok(());
30400        }
30401
30402        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
30403
30404        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
30405        self.write_keyword("MAP");
30406        if is_duckdb {
30407            self.write(" {");
30408        } else {
30409            self.write("[");
30410        }
30411        if let Expression::Struct(s) = &*e.this {
30412            for (i, (_, expr)) in s.fields.iter().enumerate() {
30413                if i > 0 {
30414                    self.write(", ");
30415                }
30416                if let Expression::PropertyEQ(op) = expr {
30417                    self.generate_expression(&op.left)?;
30418                    if is_duckdb {
30419                        self.write(": ");
30420                    } else {
30421                        self.write(" => ");
30422                    }
30423                    self.generate_expression(&op.right)?;
30424                } else {
30425                    self.generate_expression(expr)?;
30426                }
30427            }
30428        }
30429        if is_duckdb {
30430            self.write("}");
30431        } else {
30432            self.write("]");
30433        }
30434        Ok(())
30435    }
30436
30437    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
30438        // Python: LOCALTIME or LOCALTIME(precision)
30439        self.write_keyword("LOCALTIME");
30440        if let Some(precision) = &e.this {
30441            self.write("(");
30442            self.generate_expression(precision)?;
30443            self.write(")");
30444        }
30445        Ok(())
30446    }
30447
30448    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
30449        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
30450        self.write_keyword("LOCALTIMESTAMP");
30451        if let Some(precision) = &e.this {
30452            self.write("(");
30453            self.generate_expression(precision)?;
30454            self.write(")");
30455        }
30456        Ok(())
30457    }
30458
30459    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
30460        // LOCATION 'path'
30461        self.write_keyword("LOCATION");
30462        self.write_space();
30463        self.generate_expression(&e.this)?;
30464        Ok(())
30465    }
30466
30467    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
30468        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
30469        if e.update.is_some() {
30470            if e.key.is_some() {
30471                self.write_keyword("FOR NO KEY UPDATE");
30472            } else {
30473                self.write_keyword("FOR UPDATE");
30474            }
30475        } else {
30476            if e.key.is_some() {
30477                self.write_keyword("FOR KEY SHARE");
30478            } else {
30479                self.write_keyword("FOR SHARE");
30480            }
30481        }
30482        if !e.expressions.is_empty() {
30483            self.write_keyword(" OF ");
30484            for (i, expr) in e.expressions.iter().enumerate() {
30485                if i > 0 {
30486                    self.write(", ");
30487                }
30488                self.generate_expression(expr)?;
30489            }
30490        }
30491        // Handle wait option following Python sqlglot convention:
30492        // - Boolean(true) -> NOWAIT
30493        // - Boolean(false) -> SKIP LOCKED
30494        // - Literal (number) -> WAIT n
30495        if let Some(wait) = &e.wait {
30496            match wait.as_ref() {
30497                Expression::Boolean(b) => {
30498                    if b.value {
30499                        self.write_keyword(" NOWAIT");
30500                    } else {
30501                        self.write_keyword(" SKIP LOCKED");
30502                    }
30503                }
30504                _ => {
30505                    // It's a literal (number), output WAIT n
30506                    self.write_keyword(" WAIT ");
30507                    self.generate_expression(wait)?;
30508                }
30509            }
30510        }
30511        Ok(())
30512    }
30513
30514    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
30515        // LOCK property
30516        self.write_keyword("LOCK");
30517        self.write_space();
30518        self.generate_expression(&e.this)?;
30519        Ok(())
30520    }
30521
30522    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
30523        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
30524        self.write_keyword("LOCKING");
30525        self.write_space();
30526        self.write(&e.kind);
30527        if let Some(this) = &e.this {
30528            self.write_space();
30529            self.generate_expression(this)?;
30530        }
30531        if let Some(for_or_in) = &e.for_or_in {
30532            self.write_space();
30533            self.generate_expression(for_or_in)?;
30534        }
30535        if let Some(lock_type) = &e.lock_type {
30536            self.write_space();
30537            self.generate_expression(lock_type)?;
30538        }
30539        if e.override_.is_some() {
30540            self.write_keyword(" OVERRIDE");
30541        }
30542        Ok(())
30543    }
30544
30545    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
30546        // this expression
30547        self.generate_expression(&e.this)?;
30548        self.write_space();
30549        self.generate_expression(&e.expression)?;
30550        Ok(())
30551    }
30552
30553    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
30554        // [NO] LOG
30555        if e.no.is_some() {
30556            self.write_keyword("NO ");
30557        }
30558        self.write_keyword("LOG");
30559        Ok(())
30560    }
30561
30562    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
30563        // MD5(this, expressions...)
30564        self.write_keyword("MD5");
30565        self.write("(");
30566        self.generate_expression(&e.this)?;
30567        for expr in &e.expressions {
30568            self.write(", ");
30569            self.generate_expression(expr)?;
30570        }
30571        self.write(")");
30572        Ok(())
30573    }
30574
30575    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
30576        // ML.FORECAST(model, [params])
30577        self.write_keyword("ML.FORECAST");
30578        self.write("(");
30579        self.generate_expression(&e.this)?;
30580        if let Some(expression) = &e.expression {
30581            self.write(", ");
30582            self.generate_expression(expression)?;
30583        }
30584        if let Some(params) = &e.params_struct {
30585            self.write(", ");
30586            self.generate_expression(params)?;
30587        }
30588        self.write(")");
30589        Ok(())
30590    }
30591
30592    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
30593        // ML.TRANSLATE(model, input, [params])
30594        self.write_keyword("ML.TRANSLATE");
30595        self.write("(");
30596        self.generate_expression(&e.this)?;
30597        self.write(", ");
30598        self.generate_expression(&e.expression)?;
30599        if let Some(params) = &e.params_struct {
30600            self.write(", ");
30601            self.generate_expression(params)?;
30602        }
30603        self.write(")");
30604        Ok(())
30605    }
30606
30607    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
30608        // MAKE_INTERVAL(years => x, months => y, ...)
30609        self.write_keyword("MAKE_INTERVAL");
30610        self.write("(");
30611        let mut first = true;
30612        if let Some(year) = &e.year {
30613            self.write("years => ");
30614            self.generate_expression(year)?;
30615            first = false;
30616        }
30617        if let Some(month) = &e.month {
30618            if !first {
30619                self.write(", ");
30620            }
30621            self.write("months => ");
30622            self.generate_expression(month)?;
30623            first = false;
30624        }
30625        if let Some(week) = &e.week {
30626            if !first {
30627                self.write(", ");
30628            }
30629            self.write("weeks => ");
30630            self.generate_expression(week)?;
30631            first = false;
30632        }
30633        if let Some(day) = &e.day {
30634            if !first {
30635                self.write(", ");
30636            }
30637            self.write("days => ");
30638            self.generate_expression(day)?;
30639            first = false;
30640        }
30641        if let Some(hour) = &e.hour {
30642            if !first {
30643                self.write(", ");
30644            }
30645            self.write("hours => ");
30646            self.generate_expression(hour)?;
30647            first = false;
30648        }
30649        if let Some(minute) = &e.minute {
30650            if !first {
30651                self.write(", ");
30652            }
30653            self.write("mins => ");
30654            self.generate_expression(minute)?;
30655            first = false;
30656        }
30657        if let Some(second) = &e.second {
30658            if !first {
30659                self.write(", ");
30660            }
30661            self.write("secs => ");
30662            self.generate_expression(second)?;
30663        }
30664        self.write(")");
30665        Ok(())
30666    }
30667
30668    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
30669        // MANHATTAN_DISTANCE(vector1, vector2)
30670        self.write_keyword("MANHATTAN_DISTANCE");
30671        self.write("(");
30672        self.generate_expression(&e.this)?;
30673        self.write(", ");
30674        self.generate_expression(&e.expression)?;
30675        self.write(")");
30676        Ok(())
30677    }
30678
30679    fn generate_map(&mut self, e: &Map) -> Result<()> {
30680        // MAP(key1, value1, key2, value2, ...)
30681        self.write_keyword("MAP");
30682        self.write("(");
30683        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
30684            if i > 0 {
30685                self.write(", ");
30686            }
30687            self.generate_expression(key)?;
30688            self.write(", ");
30689            self.generate_expression(value)?;
30690        }
30691        self.write(")");
30692        Ok(())
30693    }
30694
30695    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
30696        // MAP_CAT(map1, map2)
30697        self.write_keyword("MAP_CAT");
30698        self.write("(");
30699        self.generate_expression(&e.this)?;
30700        self.write(", ");
30701        self.generate_expression(&e.expression)?;
30702        self.write(")");
30703        Ok(())
30704    }
30705
30706    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
30707        // MAP_DELETE(map, key1, key2, ...)
30708        self.write_keyword("MAP_DELETE");
30709        self.write("(");
30710        self.generate_expression(&e.this)?;
30711        for expr in &e.expressions {
30712            self.write(", ");
30713            self.generate_expression(expr)?;
30714        }
30715        self.write(")");
30716        Ok(())
30717    }
30718
30719    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
30720        // MAP_INSERT(map, key, value, [update_flag])
30721        self.write_keyword("MAP_INSERT");
30722        self.write("(");
30723        self.generate_expression(&e.this)?;
30724        if let Some(key) = &e.key {
30725            self.write(", ");
30726            self.generate_expression(key)?;
30727        }
30728        if let Some(value) = &e.value {
30729            self.write(", ");
30730            self.generate_expression(value)?;
30731        }
30732        if let Some(update_flag) = &e.update_flag {
30733            self.write(", ");
30734            self.generate_expression(update_flag)?;
30735        }
30736        self.write(")");
30737        Ok(())
30738    }
30739
30740    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
30741        // MAP_PICK(map, key1, key2, ...)
30742        self.write_keyword("MAP_PICK");
30743        self.write("(");
30744        self.generate_expression(&e.this)?;
30745        for expr in &e.expressions {
30746            self.write(", ");
30747            self.generate_expression(expr)?;
30748        }
30749        self.write(")");
30750        Ok(())
30751    }
30752
30753    fn generate_masking_policy_column_constraint(
30754        &mut self,
30755        e: &MaskingPolicyColumnConstraint,
30756    ) -> Result<()> {
30757        // Python: MASKING POLICY name [USING (cols)]
30758        self.write_keyword("MASKING POLICY");
30759        self.write_space();
30760        self.generate_expression(&e.this)?;
30761        if !e.expressions.is_empty() {
30762            self.write_keyword(" USING");
30763            self.write(" (");
30764            for (i, expr) in e.expressions.iter().enumerate() {
30765                if i > 0 {
30766                    self.write(", ");
30767                }
30768                self.generate_expression(expr)?;
30769            }
30770            self.write(")");
30771        }
30772        Ok(())
30773    }
30774
30775    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
30776        if matches!(
30777            self.config.dialect,
30778            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
30779        ) {
30780            if e.expressions.len() > 1 {
30781                self.write("(");
30782            }
30783            for (i, expr) in e.expressions.iter().enumerate() {
30784                if i > 0 {
30785                    self.write_keyword(" OR ");
30786                }
30787                self.generate_expression(expr)?;
30788                self.write_space();
30789                self.write("@@");
30790                self.write_space();
30791                self.generate_expression(&e.this)?;
30792            }
30793            if e.expressions.len() > 1 {
30794                self.write(")");
30795            }
30796            return Ok(());
30797        }
30798
30799        // MATCH(columns) AGAINST(expr [modifier])
30800        self.write_keyword("MATCH");
30801        self.write("(");
30802        for (i, expr) in e.expressions.iter().enumerate() {
30803            if i > 0 {
30804                self.write(", ");
30805            }
30806            self.generate_expression(expr)?;
30807        }
30808        self.write(")");
30809        self.write_keyword(" AGAINST");
30810        self.write("(");
30811        self.generate_expression(&e.this)?;
30812        if let Some(modifier) = &e.modifier {
30813            self.write_space();
30814            self.generate_expression(modifier)?;
30815        }
30816        self.write(")");
30817        Ok(())
30818    }
30819
30820    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
30821        // Python: [window_frame] this
30822        if let Some(window_frame) = &e.window_frame {
30823            self.write(&format!("{:?}", window_frame).to_ascii_uppercase());
30824            self.write_space();
30825        }
30826        self.generate_expression(&e.this)?;
30827        Ok(())
30828    }
30829
30830    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
30831        // MATERIALIZED [this]
30832        self.write_keyword("MATERIALIZED");
30833        if let Some(this) = &e.this {
30834            self.write_space();
30835            self.generate_expression(this)?;
30836        }
30837        Ok(())
30838    }
30839
30840    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
30841        // MERGE INTO target USING source ON condition WHEN ...
30842        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
30843        if let Some(with_) = &e.with_ {
30844            self.generate_expression(with_)?;
30845            self.write_space();
30846        }
30847        self.write_keyword("MERGE INTO");
30848        self.write_space();
30849        self.generate_expression(&e.this)?;
30850
30851        // USING clause - newline before in pretty mode
30852        if self.config.pretty {
30853            self.write_newline();
30854            self.write_indent();
30855        } else {
30856            self.write_space();
30857        }
30858        self.write_keyword("USING");
30859        self.write_space();
30860        self.generate_expression(&e.using)?;
30861
30862        // ON clause - newline before in pretty mode
30863        if let Some(on) = &e.on {
30864            if self.config.pretty {
30865                self.write_newline();
30866                self.write_indent();
30867            } else {
30868                self.write_space();
30869            }
30870            self.write_keyword("ON");
30871            self.write_space();
30872            self.generate_expression(on)?;
30873        }
30874        // DuckDB USING (key_columns) clause
30875        if let Some(using_cond) = &e.using_cond {
30876            self.write_space();
30877            self.write_keyword("USING");
30878            self.write_space();
30879            self.write("(");
30880            // using_cond is a Tuple containing the column identifiers
30881            if let Expression::Tuple(tuple) = using_cond.as_ref() {
30882                for (i, col) in tuple.expressions.iter().enumerate() {
30883                    if i > 0 {
30884                        self.write(", ");
30885                    }
30886                    self.generate_expression(col)?;
30887                }
30888            } else {
30889                self.generate_expression(using_cond)?;
30890            }
30891            self.write(")");
30892        }
30893        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
30894        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
30895        if matches!(
30896            self.config.dialect,
30897            Some(crate::DialectType::PostgreSQL)
30898                | Some(crate::DialectType::Redshift)
30899                | Some(crate::DialectType::Trino)
30900                | Some(crate::DialectType::Presto)
30901                | Some(crate::DialectType::Athena)
30902        ) {
30903            let mut names = Vec::new();
30904            match e.this.as_ref() {
30905                Expression::Alias(a) => {
30906                    // e.g., "x AS z" -> strip both "x" and "z"
30907                    if let Expression::Table(t) = &a.this {
30908                        names.push(t.name.name.clone());
30909                    } else if let Expression::Identifier(id) = &a.this {
30910                        names.push(id.name.clone());
30911                    }
30912                    names.push(a.alias.name.clone());
30913                }
30914                Expression::Table(t) => {
30915                    names.push(t.name.name.clone());
30916                }
30917                Expression::Identifier(id) => {
30918                    names.push(id.name.clone());
30919                }
30920                _ => {}
30921            }
30922            self.merge_strip_qualifiers = names;
30923        }
30924
30925        // WHEN clauses - newline before each in pretty mode
30926        if let Some(whens) = &e.whens {
30927            if self.config.pretty {
30928                self.write_newline();
30929                self.write_indent();
30930            } else {
30931                self.write_space();
30932            }
30933            self.generate_expression(whens)?;
30934        }
30935
30936        // Restore merge_strip_qualifiers
30937        self.merge_strip_qualifiers = saved_merge_strip;
30938
30939        // OUTPUT/RETURNING clause - newline before in pretty mode
30940        if let Some(returning) = &e.returning {
30941            if self.config.pretty {
30942                self.write_newline();
30943                self.write_indent();
30944            } else {
30945                self.write_space();
30946            }
30947            self.generate_expression(returning)?;
30948        }
30949        Ok(())
30950    }
30951
30952    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
30953        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
30954        if e.no.is_some() {
30955            self.write_keyword("NO MERGEBLOCKRATIO");
30956        } else if e.default.is_some() {
30957            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
30958        } else {
30959            self.write_keyword("MERGEBLOCKRATIO");
30960            self.write("=");
30961            if let Some(this) = &e.this {
30962                self.generate_expression(this)?;
30963            }
30964            if e.percent.is_some() {
30965                self.write_keyword(" PERCENT");
30966            }
30967        }
30968        Ok(())
30969    }
30970
30971    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
30972        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
30973        self.write_keyword("TTL");
30974        let pretty_clickhouse = self.config.pretty
30975            && matches!(
30976                self.config.dialect,
30977                Some(crate::dialects::DialectType::ClickHouse)
30978            );
30979
30980        if pretty_clickhouse {
30981            self.write_newline();
30982            self.indent_level += 1;
30983            for (i, expr) in e.expressions.iter().enumerate() {
30984                if i > 0 {
30985                    self.write(",");
30986                    self.write_newline();
30987                }
30988                self.write_indent();
30989                self.generate_expression(expr)?;
30990            }
30991            self.indent_level -= 1;
30992        } else {
30993            self.write_space();
30994            for (i, expr) in e.expressions.iter().enumerate() {
30995                if i > 0 {
30996                    self.write(", ");
30997                }
30998                self.generate_expression(expr)?;
30999            }
31000        }
31001
31002        if let Some(where_) = &e.where_ {
31003            if pretty_clickhouse {
31004                self.write_newline();
31005                if let Expression::Where(w) = where_.as_ref() {
31006                    self.write_indent();
31007                    self.write_keyword("WHERE");
31008                    self.write_newline();
31009                    self.indent_level += 1;
31010                    self.write_indent();
31011                    self.generate_expression(&w.this)?;
31012                    self.indent_level -= 1;
31013                } else {
31014                    self.write_indent();
31015                    self.generate_expression(where_)?;
31016                }
31017            } else {
31018                self.write_space();
31019                self.generate_expression(where_)?;
31020            }
31021        }
31022        if let Some(group) = &e.group {
31023            if pretty_clickhouse {
31024                self.write_newline();
31025                if let Expression::Group(g) = group.as_ref() {
31026                    self.write_indent();
31027                    self.write_keyword("GROUP BY");
31028                    self.write_newline();
31029                    self.indent_level += 1;
31030                    for (i, expr) in g.expressions.iter().enumerate() {
31031                        if i > 0 {
31032                            self.write(",");
31033                            self.write_newline();
31034                        }
31035                        self.write_indent();
31036                        self.generate_expression(expr)?;
31037                    }
31038                    self.indent_level -= 1;
31039                } else {
31040                    self.write_indent();
31041                    self.generate_expression(group)?;
31042                }
31043            } else {
31044                self.write_space();
31045                self.generate_expression(group)?;
31046            }
31047        }
31048        if let Some(aggregates) = &e.aggregates {
31049            if pretty_clickhouse {
31050                self.write_newline();
31051                self.write_indent();
31052                self.write_keyword("SET");
31053                self.write_newline();
31054                self.indent_level += 1;
31055                if let Expression::Tuple(t) = aggregates.as_ref() {
31056                    for (i, agg) in t.expressions.iter().enumerate() {
31057                        if i > 0 {
31058                            self.write(",");
31059                            self.write_newline();
31060                        }
31061                        self.write_indent();
31062                        self.generate_expression(agg)?;
31063                    }
31064                } else {
31065                    self.write_indent();
31066                    self.generate_expression(aggregates)?;
31067                }
31068                self.indent_level -= 1;
31069            } else {
31070                self.write_space();
31071                self.write_keyword("SET");
31072                self.write_space();
31073                self.generate_expression(aggregates)?;
31074            }
31075        }
31076        Ok(())
31077    }
31078
31079    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
31080        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
31081        self.generate_expression(&e.this)?;
31082        if e.delete.is_some() {
31083            self.write_keyword(" DELETE");
31084        }
31085        if let Some(recompress) = &e.recompress {
31086            self.write_keyword(" RECOMPRESS ");
31087            self.generate_expression(recompress)?;
31088        }
31089        if let Some(to_disk) = &e.to_disk {
31090            self.write_keyword(" TO DISK ");
31091            self.generate_expression(to_disk)?;
31092        }
31093        if let Some(to_volume) = &e.to_volume {
31094            self.write_keyword(" TO VOLUME ");
31095            self.generate_expression(to_volume)?;
31096        }
31097        Ok(())
31098    }
31099
31100    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
31101        // MINHASH(this, expressions...)
31102        self.write_keyword("MINHASH");
31103        self.write("(");
31104        self.generate_expression(&e.this)?;
31105        for expr in &e.expressions {
31106            self.write(", ");
31107            self.generate_expression(expr)?;
31108        }
31109        self.write(")");
31110        Ok(())
31111    }
31112
31113    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
31114        // model!attribute - Snowflake syntax
31115        self.generate_expression(&e.this)?;
31116        self.write("!");
31117        self.generate_expression(&e.expression)?;
31118        Ok(())
31119    }
31120
31121    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
31122        // MONTHNAME(this)
31123        self.write_keyword("MONTHNAME");
31124        self.write("(");
31125        self.generate_expression(&e.this)?;
31126        self.write(")");
31127        Ok(())
31128    }
31129
31130    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
31131        // Output leading comments
31132        for comment in &e.leading_comments {
31133            self.write_formatted_comment(comment);
31134            if self.config.pretty {
31135                self.write_newline();
31136                self.write_indent();
31137            } else {
31138                self.write_space();
31139            }
31140        }
31141        // Python: INSERT kind expressions source
31142        self.write_keyword("INSERT");
31143        self.write_space();
31144        self.write(&e.kind);
31145        if self.config.pretty {
31146            self.indent_level += 1;
31147            for expr in &e.expressions {
31148                self.write_newline();
31149                self.write_indent();
31150                self.generate_expression(expr)?;
31151            }
31152            self.indent_level -= 1;
31153        } else {
31154            for expr in &e.expressions {
31155                self.write_space();
31156                self.generate_expression(expr)?;
31157            }
31158        }
31159        if let Some(source) = &e.source {
31160            if self.config.pretty {
31161                self.write_newline();
31162                self.write_indent();
31163            } else {
31164                self.write_space();
31165            }
31166            self.generate_expression(source)?;
31167        }
31168        Ok(())
31169    }
31170
31171    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
31172        // Python: NEXT VALUE FOR this [OVER (order)]
31173        self.write_keyword("NEXT VALUE FOR");
31174        self.write_space();
31175        self.generate_expression(&e.this)?;
31176        if let Some(order) = &e.order {
31177            self.write_space();
31178            self.write_keyword("OVER");
31179            self.write(" (");
31180            self.generate_expression(order)?;
31181            self.write(")");
31182        }
31183        Ok(())
31184    }
31185
31186    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
31187        // NORMAL(mean, stddev, gen)
31188        self.write_keyword("NORMAL");
31189        self.write("(");
31190        self.generate_expression(&e.this)?;
31191        if let Some(stddev) = &e.stddev {
31192            self.write(", ");
31193            self.generate_expression(stddev)?;
31194        }
31195        if let Some(gen) = &e.gen {
31196            self.write(", ");
31197            self.generate_expression(gen)?;
31198        }
31199        self.write(")");
31200        Ok(())
31201    }
31202
31203    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
31204        // NORMALIZE(this, form) or CASEFOLD version
31205        if e.is_casefold.is_some() {
31206            self.write_keyword("NORMALIZE_AND_CASEFOLD");
31207        } else {
31208            self.write_keyword("NORMALIZE");
31209        }
31210        self.write("(");
31211        self.generate_expression(&e.this)?;
31212        if let Some(form) = &e.form {
31213            self.write(", ");
31214            self.generate_expression(form)?;
31215        }
31216        self.write(")");
31217        Ok(())
31218    }
31219
31220    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
31221        // Python: [NOT ]NULL
31222        if e.allow_null.is_none() {
31223            self.write_keyword("NOT ");
31224        }
31225        self.write_keyword("NULL");
31226        Ok(())
31227    }
31228
31229    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
31230        // NULLIF(this, expression)
31231        self.write_keyword("NULLIF");
31232        self.write("(");
31233        self.generate_expression(&e.this)?;
31234        self.write(", ");
31235        self.generate_expression(&e.expression)?;
31236        self.write(")");
31237        Ok(())
31238    }
31239
31240    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
31241        // FORMAT(this, format, culture)
31242        self.write_keyword("FORMAT");
31243        self.write("(");
31244        self.generate_expression(&e.this)?;
31245        self.write(", '");
31246        self.write(&e.format);
31247        self.write("'");
31248        if let Some(culture) = &e.culture {
31249            self.write(", ");
31250            self.generate_expression(culture)?;
31251        }
31252        self.write(")");
31253        Ok(())
31254    }
31255
31256    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
31257        // OBJECT_AGG(key, value)
31258        self.write_keyword("OBJECT_AGG");
31259        self.write("(");
31260        self.generate_expression(&e.this)?;
31261        self.write(", ");
31262        self.generate_expression(&e.expression)?;
31263        self.write(")");
31264        Ok(())
31265    }
31266
31267    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
31268        // Python: Just returns the name
31269        self.generate_expression(&e.this)?;
31270        Ok(())
31271    }
31272
31273    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
31274        // OBJECT_INSERT(obj, key, value, [update_flag])
31275        self.write_keyword("OBJECT_INSERT");
31276        self.write("(");
31277        self.generate_expression(&e.this)?;
31278        if let Some(key) = &e.key {
31279            self.write(", ");
31280            self.generate_expression(key)?;
31281        }
31282        if let Some(value) = &e.value {
31283            self.write(", ");
31284            self.generate_expression(value)?;
31285        }
31286        if let Some(update_flag) = &e.update_flag {
31287            self.write(", ");
31288            self.generate_expression(update_flag)?;
31289        }
31290        self.write(")");
31291        Ok(())
31292    }
31293
31294    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
31295        // OFFSET value [ROW|ROWS]
31296        self.write_keyword("OFFSET");
31297        self.write_space();
31298        self.generate_expression(&e.this)?;
31299        // Output ROWS keyword only for TSQL/Oracle targets
31300        if e.rows == Some(true)
31301            && matches!(
31302                self.config.dialect,
31303                Some(crate::dialects::DialectType::TSQL)
31304                    | Some(crate::dialects::DialectType::Oracle)
31305            )
31306        {
31307            self.write_space();
31308            self.write_keyword("ROWS");
31309        }
31310        Ok(())
31311    }
31312
31313    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
31314        // QUALIFY condition (Snowflake/BigQuery)
31315        self.write_keyword("QUALIFY");
31316        self.write_space();
31317        self.generate_expression(&e.this)?;
31318        Ok(())
31319    }
31320
31321    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
31322        // ON CLUSTER cluster_name
31323        self.write_keyword("ON CLUSTER");
31324        self.write_space();
31325        self.generate_expression(&e.this)?;
31326        Ok(())
31327    }
31328
31329    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
31330        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
31331        self.write_keyword("ON COMMIT");
31332        if e.delete.is_some() {
31333            self.write_keyword(" DELETE ROWS");
31334        } else {
31335            self.write_keyword(" PRESERVE ROWS");
31336        }
31337        Ok(())
31338    }
31339
31340    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
31341        // Python: error/empty/null handling
31342        if let Some(empty) = &e.empty {
31343            self.generate_expression(empty)?;
31344            self.write_keyword(" ON EMPTY");
31345        }
31346        if let Some(error) = &e.error {
31347            if e.empty.is_some() {
31348                self.write_space();
31349            }
31350            self.generate_expression(error)?;
31351            self.write_keyword(" ON ERROR");
31352        }
31353        if let Some(null) = &e.null {
31354            if e.empty.is_some() || e.error.is_some() {
31355                self.write_space();
31356            }
31357            self.generate_expression(null)?;
31358            self.write_keyword(" ON NULL");
31359        }
31360        Ok(())
31361    }
31362
31363    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
31364        // Materialize doesn't support ON CONFLICT - skip entirely
31365        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
31366            return Ok(());
31367        }
31368        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
31369        if e.duplicate.is_some() {
31370            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
31371            self.write_keyword("ON DUPLICATE KEY UPDATE");
31372            for (i, expr) in e.expressions.iter().enumerate() {
31373                if i > 0 {
31374                    self.write(",");
31375                }
31376                self.write_space();
31377                self.generate_expression(expr)?;
31378            }
31379            return Ok(());
31380        } else {
31381            self.write_keyword("ON CONFLICT");
31382        }
31383        if let Some(constraint) = &e.constraint {
31384            self.write_keyword(" ON CONSTRAINT ");
31385            self.generate_expression(constraint)?;
31386        }
31387        if let Some(conflict_keys) = &e.conflict_keys {
31388            // conflict_keys can be a Tuple containing expressions
31389            if let Expression::Tuple(t) = conflict_keys.as_ref() {
31390                self.write("(");
31391                for (i, expr) in t.expressions.iter().enumerate() {
31392                    if i > 0 {
31393                        self.write(", ");
31394                    }
31395                    self.generate_expression(expr)?;
31396                }
31397                self.write(")");
31398            } else {
31399                self.write("(");
31400                self.generate_expression(conflict_keys)?;
31401                self.write(")");
31402            }
31403        }
31404        if let Some(index_predicate) = &e.index_predicate {
31405            self.write_keyword(" WHERE ");
31406            self.generate_expression(index_predicate)?;
31407        }
31408        if let Some(action) = &e.action {
31409            // Check if action is "NOTHING" or an UPDATE set
31410            if let Expression::Identifier(id) = action.as_ref() {
31411                if id.name.eq_ignore_ascii_case("NOTHING") {
31412                    self.write_keyword(" DO NOTHING");
31413                } else {
31414                    self.write_keyword(" DO ");
31415                    self.generate_expression(action)?;
31416                }
31417            } else if let Expression::Tuple(t) = action.as_ref() {
31418                // DO UPDATE SET col1 = val1, col2 = val2
31419                self.write_keyword(" DO UPDATE SET ");
31420                for (i, expr) in t.expressions.iter().enumerate() {
31421                    if i > 0 {
31422                        self.write(", ");
31423                    }
31424                    self.generate_expression(expr)?;
31425                }
31426            } else {
31427                self.write_keyword(" DO ");
31428                self.generate_expression(action)?;
31429            }
31430        }
31431        // WHERE clause for the UPDATE action
31432        if let Some(where_) = &e.where_ {
31433            self.write_keyword(" WHERE ");
31434            self.generate_expression(where_)?;
31435        }
31436        Ok(())
31437    }
31438
31439    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
31440        // ON property_value
31441        self.write_keyword("ON");
31442        self.write_space();
31443        self.generate_expression(&e.this)?;
31444        Ok(())
31445    }
31446
31447    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
31448        // Python: this expression (e.g., column opclass)
31449        self.generate_expression(&e.this)?;
31450        self.write_space();
31451        self.generate_expression(&e.expression)?;
31452        Ok(())
31453    }
31454
31455    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
31456        // Python: OPENJSON(this[, path]) [WITH (columns)]
31457        self.write_keyword("OPENJSON");
31458        self.write("(");
31459        self.generate_expression(&e.this)?;
31460        if let Some(path) = &e.path {
31461            self.write(", ");
31462            self.generate_expression(path)?;
31463        }
31464        self.write(")");
31465        if !e.expressions.is_empty() {
31466            self.write_keyword(" WITH");
31467            if self.config.pretty {
31468                self.write(" (\n");
31469                self.indent_level += 2;
31470                for (i, expr) in e.expressions.iter().enumerate() {
31471                    if i > 0 {
31472                        self.write(",\n");
31473                    }
31474                    self.write_indent();
31475                    self.generate_expression(expr)?;
31476                }
31477                self.write("\n");
31478                self.indent_level -= 2;
31479                self.write(")");
31480            } else {
31481                self.write(" (");
31482                for (i, expr) in e.expressions.iter().enumerate() {
31483                    if i > 0 {
31484                        self.write(", ");
31485                    }
31486                    self.generate_expression(expr)?;
31487                }
31488                self.write(")");
31489            }
31490        }
31491        Ok(())
31492    }
31493
31494    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
31495        // Python: this kind [path] [AS JSON]
31496        self.generate_expression(&e.this)?;
31497        self.write_space();
31498        // Use parsed data_type if available, otherwise fall back to kind string
31499        if let Some(ref dt) = e.data_type {
31500            self.generate_data_type(dt)?;
31501        } else if !e.kind.is_empty() {
31502            self.write(&e.kind);
31503        }
31504        if let Some(path) = &e.path {
31505            self.write_space();
31506            self.generate_expression(path)?;
31507        }
31508        if e.as_json.is_some() {
31509            self.write_keyword(" AS JSON");
31510        }
31511        Ok(())
31512    }
31513
31514    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
31515        // this OPERATOR(op) expression
31516        self.generate_expression(&e.this)?;
31517        self.write_space();
31518        if let Some(op) = &e.operator {
31519            self.write_keyword("OPERATOR");
31520            self.write("(");
31521            self.generate_expression(op)?;
31522            self.write(")");
31523        }
31524        // Emit inline comments between OPERATOR() and the RHS
31525        for comment in &e.comments {
31526            self.write_space();
31527            self.write_formatted_comment(comment);
31528        }
31529        self.write_space();
31530        self.generate_expression(&e.expression)?;
31531        Ok(())
31532    }
31533
31534    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
31535        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
31536        self.write_keyword("ORDER BY");
31537        let pretty_clickhouse_single_paren = self.config.pretty
31538            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
31539            && e.expressions.len() == 1
31540            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
31541        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
31542            && e.expressions.len() == 1
31543            && matches!(e.expressions[0].this, Expression::Tuple(_))
31544            && !e.expressions[0].desc
31545            && e.expressions[0].nulls_first.is_none();
31546
31547        if pretty_clickhouse_single_paren {
31548            self.write_space();
31549            if let Expression::Paren(p) = &e.expressions[0].this {
31550                self.write("(");
31551                self.write_newline();
31552                self.indent_level += 1;
31553                self.write_indent();
31554                self.generate_expression(&p.this)?;
31555                self.indent_level -= 1;
31556                self.write_newline();
31557                self.write(")");
31558            }
31559            return Ok(());
31560        }
31561
31562        if clickhouse_single_tuple {
31563            self.write_space();
31564            if let Expression::Tuple(t) = &e.expressions[0].this {
31565                self.write("(");
31566                for (i, expr) in t.expressions.iter().enumerate() {
31567                    if i > 0 {
31568                        self.write(", ");
31569                    }
31570                    self.generate_expression(expr)?;
31571                }
31572                self.write(")");
31573            }
31574            return Ok(());
31575        }
31576
31577        self.write_space();
31578        for (i, ordered) in e.expressions.iter().enumerate() {
31579            if i > 0 {
31580                self.write(", ");
31581            }
31582            self.generate_expression(&ordered.this)?;
31583            if ordered.desc {
31584                self.write_space();
31585                self.write_keyword("DESC");
31586            } else if ordered.explicit_asc {
31587                self.write_space();
31588                self.write_keyword("ASC");
31589            }
31590            if let Some(nulls_first) = ordered.nulls_first {
31591                // In Dremio, NULLS LAST is the default, so skip generating it
31592                let skip_nulls_last =
31593                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
31594                if !skip_nulls_last {
31595                    self.write_space();
31596                    self.write_keyword("NULLS");
31597                    self.write_space();
31598                    if nulls_first {
31599                        self.write_keyword("FIRST");
31600                    } else {
31601                        self.write_keyword("LAST");
31602                    }
31603                }
31604            }
31605        }
31606        Ok(())
31607    }
31608
31609    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
31610        // OUTPUT(model)
31611        self.write_keyword("OUTPUT");
31612        self.write("(");
31613        if self.config.pretty {
31614            self.indent_level += 1;
31615            self.write_newline();
31616            self.write_indent();
31617            self.generate_expression(&e.this)?;
31618            self.indent_level -= 1;
31619            self.write_newline();
31620        } else {
31621            self.generate_expression(&e.this)?;
31622        }
31623        self.write(")");
31624        Ok(())
31625    }
31626
31627    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
31628        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
31629        self.write_keyword("TRUNCATE");
31630        if let Some(this) = &e.this {
31631            self.write_space();
31632            self.generate_expression(this)?;
31633        }
31634        if e.with_count.is_some() {
31635            self.write_keyword(" WITH COUNT");
31636        } else {
31637            self.write_keyword(" WITHOUT COUNT");
31638        }
31639        Ok(())
31640    }
31641
31642    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
31643        // Python: name(expressions)(params)
31644        self.generate_expression(&e.this)?;
31645        self.write("(");
31646        for (i, expr) in e.expressions.iter().enumerate() {
31647            if i > 0 {
31648                self.write(", ");
31649            }
31650            self.generate_expression(expr)?;
31651        }
31652        self.write(")(");
31653        for (i, param) in e.params.iter().enumerate() {
31654            if i > 0 {
31655                self.write(", ");
31656            }
31657            self.generate_expression(param)?;
31658        }
31659        self.write(")");
31660        Ok(())
31661    }
31662
31663    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
31664        // PARSE_DATETIME(format, this) or similar
31665        self.write_keyword("PARSE_DATETIME");
31666        self.write("(");
31667        if let Some(format) = &e.format {
31668            self.write("'");
31669            self.write(format);
31670            self.write("', ");
31671        }
31672        self.generate_expression(&e.this)?;
31673        if let Some(zone) = &e.zone {
31674            self.write(", ");
31675            self.generate_expression(zone)?;
31676        }
31677        self.write(")");
31678        Ok(())
31679    }
31680
31681    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
31682        // PARSE_IP(this, type, permissive)
31683        self.write_keyword("PARSE_IP");
31684        self.write("(");
31685        self.generate_expression(&e.this)?;
31686        if let Some(type_) = &e.type_ {
31687            self.write(", ");
31688            self.generate_expression(type_)?;
31689        }
31690        if let Some(permissive) = &e.permissive {
31691            self.write(", ");
31692            self.generate_expression(permissive)?;
31693        }
31694        self.write(")");
31695        Ok(())
31696    }
31697
31698    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
31699        // PARSE_JSON(this, [expression])
31700        self.write_keyword("PARSE_JSON");
31701        self.write("(");
31702        self.generate_expression(&e.this)?;
31703        if let Some(expression) = &e.expression {
31704            self.write(", ");
31705            self.generate_expression(expression)?;
31706        }
31707        self.write(")");
31708        Ok(())
31709    }
31710
31711    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
31712        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
31713        self.write_keyword("PARSE_TIME");
31714        self.write("(");
31715        self.write(&format!("'{}'", e.format));
31716        self.write(", ");
31717        self.generate_expression(&e.this)?;
31718        self.write(")");
31719        Ok(())
31720    }
31721
31722    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
31723        // PARSE_URL(this, [part_to_extract], [key], [permissive])
31724        self.write_keyword("PARSE_URL");
31725        self.write("(");
31726        self.generate_expression(&e.this)?;
31727        if let Some(part) = &e.part_to_extract {
31728            self.write(", ");
31729            self.generate_expression(part)?;
31730        }
31731        if let Some(key) = &e.key {
31732            self.write(", ");
31733            self.generate_expression(key)?;
31734        }
31735        if let Some(permissive) = &e.permissive {
31736            self.write(", ");
31737            self.generate_expression(permissive)?;
31738        }
31739        self.write(")");
31740        Ok(())
31741    }
31742
31743    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
31744        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
31745        if e.subpartition {
31746            self.write_keyword("SUBPARTITION");
31747        } else {
31748            self.write_keyword("PARTITION");
31749        }
31750        self.write("(");
31751        for (i, expr) in e.expressions.iter().enumerate() {
31752            if i > 0 {
31753                self.write(", ");
31754            }
31755            self.generate_expression(expr)?;
31756        }
31757        self.write(")");
31758        Ok(())
31759    }
31760
31761    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
31762        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
31763        if let Some(this) = &e.this {
31764            if let Some(expression) = &e.expression {
31765                // WITH (MODULUS this, REMAINDER expression)
31766                self.write_keyword("WITH");
31767                self.write(" (");
31768                self.write_keyword("MODULUS");
31769                self.write_space();
31770                self.generate_expression(this)?;
31771                self.write(", ");
31772                self.write_keyword("REMAINDER");
31773                self.write_space();
31774                self.generate_expression(expression)?;
31775                self.write(")");
31776            } else {
31777                // IN (this) - this could be a list
31778                self.write_keyword("IN");
31779                self.write(" (");
31780                self.generate_partition_bound_values(this)?;
31781                self.write(")");
31782            }
31783        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
31784            // FROM (from_expressions) TO (to_expressions)
31785            self.write_keyword("FROM");
31786            self.write(" (");
31787            self.generate_partition_bound_values(from)?;
31788            self.write(") ");
31789            self.write_keyword("TO");
31790            self.write(" (");
31791            self.generate_partition_bound_values(to)?;
31792            self.write(")");
31793        }
31794        Ok(())
31795    }
31796
31797    /// Generate partition bound values - handles Tuple expressions by outputting
31798    /// contents without wrapping parens (since caller provides the parens)
31799    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
31800        if let Expression::Tuple(t) = expr {
31801            for (i, e) in t.expressions.iter().enumerate() {
31802                if i > 0 {
31803                    self.write(", ");
31804                }
31805                self.generate_expression(e)?;
31806            }
31807            Ok(())
31808        } else {
31809            self.generate_expression(expr)
31810        }
31811    }
31812
31813    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
31814        // PARTITION BY LIST (partition_expressions) (create_expressions)
31815        self.write_keyword("PARTITION BY LIST");
31816        if let Some(partition_exprs) = &e.partition_expressions {
31817            self.write(" (");
31818            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
31819            self.generate_doris_partition_expressions(partition_exprs)?;
31820            self.write(")");
31821        }
31822        if let Some(create_exprs) = &e.create_expressions {
31823            self.write(" (");
31824            // Unwrap Tuple for partition definitions
31825            self.generate_doris_partition_definitions(create_exprs)?;
31826            self.write(")");
31827        }
31828        Ok(())
31829    }
31830
31831    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
31832        // PARTITION BY RANGE (partition_expressions) (create_expressions)
31833        self.write_keyword("PARTITION BY RANGE");
31834        if let Some(partition_exprs) = &e.partition_expressions {
31835            self.write(" (");
31836            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
31837            self.generate_doris_partition_expressions(partition_exprs)?;
31838            self.write(")");
31839        }
31840        if let Some(create_exprs) = &e.create_expressions {
31841            self.write(" (");
31842            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
31843            self.generate_doris_partition_definitions(create_exprs)?;
31844            self.write(")");
31845        }
31846        Ok(())
31847    }
31848
31849    /// Generate Doris partition column expressions (unwrap Tuple)
31850    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
31851        if let Expression::Tuple(t) = expr {
31852            for (i, e) in t.expressions.iter().enumerate() {
31853                if i > 0 {
31854                    self.write(", ");
31855                }
31856                self.generate_expression(e)?;
31857            }
31858        } else {
31859            self.generate_expression(expr)?;
31860        }
31861        Ok(())
31862    }
31863
31864    /// Generate Doris partition definitions (comma-separated Partition expressions)
31865    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
31866        match expr {
31867            Expression::Tuple(t) => {
31868                // Multiple partitions, comma-separated
31869                for (i, part) in t.expressions.iter().enumerate() {
31870                    if i > 0 {
31871                        self.write(", ");
31872                    }
31873                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
31874                    if let Expression::Partition(p) = part {
31875                        for (j, inner) in p.expressions.iter().enumerate() {
31876                            if j > 0 {
31877                                self.write(", ");
31878                            }
31879                            self.generate_expression(inner)?;
31880                        }
31881                    } else {
31882                        self.generate_expression(part)?;
31883                    }
31884                }
31885            }
31886            Expression::PartitionByRangePropertyDynamic(_) => {
31887                // Dynamic partition - FROM/TO/INTERVAL
31888                self.generate_expression(expr)?;
31889            }
31890            _ => {
31891                self.generate_expression(expr)?;
31892            }
31893        }
31894        Ok(())
31895    }
31896
31897    fn generate_partition_by_range_property_dynamic(
31898        &mut self,
31899        e: &PartitionByRangePropertyDynamic,
31900    ) -> Result<()> {
31901        if e.use_start_end {
31902            // StarRocks: START ('val') END ('val') EVERY (expr)
31903            if let Some(start) = &e.start {
31904                self.write_keyword("START");
31905                self.write(" (");
31906                self.generate_expression(start)?;
31907                self.write(")");
31908            }
31909            if let Some(end) = &e.end {
31910                self.write_space();
31911                self.write_keyword("END");
31912                self.write(" (");
31913                self.generate_expression(end)?;
31914                self.write(")");
31915            }
31916            if let Some(every) = &e.every {
31917                self.write_space();
31918                self.write_keyword("EVERY");
31919                self.write(" (");
31920                // Use unquoted interval format for StarRocks
31921                self.generate_doris_interval(every)?;
31922                self.write(")");
31923            }
31924        } else {
31925            // Doris: FROM (start) TO (end) INTERVAL n UNIT
31926            if let Some(start) = &e.start {
31927                self.write_keyword("FROM");
31928                self.write(" (");
31929                self.generate_expression(start)?;
31930                self.write(")");
31931            }
31932            if let Some(end) = &e.end {
31933                self.write_space();
31934                self.write_keyword("TO");
31935                self.write(" (");
31936                self.generate_expression(end)?;
31937                self.write(")");
31938            }
31939            if let Some(every) = &e.every {
31940                self.write_space();
31941                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
31942                self.generate_doris_interval(every)?;
31943            }
31944        }
31945        Ok(())
31946    }
31947
31948    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
31949    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
31950        if let Expression::Interval(interval) = expr {
31951            self.write_keyword("INTERVAL");
31952            if let Some(ref value) = interval.this {
31953                self.write_space();
31954                // If the value is a string literal that looks like a number,
31955                // output it without quotes (matching Python sqlglot's
31956                // partitionbyrangepropertydynamic_sql which converts back to number)
31957                match value {
31958                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if s.chars().all(|c| c.is_ascii_digit() || c == '.' || c == '-') && !s.is_empty()) => {
31959                        if let Literal::String(s) = lit.as_ref() {
31960                            self.write(s);
31961                        }
31962                    }
31963                    _ => {
31964                        self.generate_expression(value)?;
31965                    }
31966                }
31967            }
31968            if let Some(ref unit_spec) = interval.unit {
31969                self.write_space();
31970                self.write_interval_unit_spec(unit_spec)?;
31971            }
31972            Ok(())
31973        } else {
31974            self.generate_expression(expr)
31975        }
31976    }
31977
31978    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
31979        // TRUNCATE(expression, this)
31980        self.write_keyword("TRUNCATE");
31981        self.write("(");
31982        self.generate_expression(&e.expression)?;
31983        self.write(", ");
31984        self.generate_expression(&e.this)?;
31985        self.write(")");
31986        Ok(())
31987    }
31988
31989    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
31990        // Doris: PARTITION name VALUES IN (val1, val2)
31991        self.write_keyword("PARTITION");
31992        self.write_space();
31993        self.generate_expression(&e.this)?;
31994        self.write_space();
31995        self.write_keyword("VALUES IN");
31996        self.write(" (");
31997        for (i, expr) in e.expressions.iter().enumerate() {
31998            if i > 0 {
31999                self.write(", ");
32000            }
32001            self.generate_expression(expr)?;
32002        }
32003        self.write(")");
32004        Ok(())
32005    }
32006
32007    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
32008        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
32009        // TSQL ranges have no expressions and just use `this TO expression`
32010        if e.expressions.is_empty() && e.expression.is_some() {
32011            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
32012            self.generate_expression(&e.this)?;
32013            self.write_space();
32014            self.write_keyword("TO");
32015            self.write_space();
32016            self.generate_expression(e.expression.as_ref().unwrap())?;
32017            return Ok(());
32018        }
32019
32020        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
32021        self.write_keyword("PARTITION");
32022        self.write_space();
32023        self.generate_expression(&e.this)?;
32024        self.write_space();
32025
32026        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
32027        if e.expressions.len() == 1 {
32028            // Single value: VALUES LESS THAN (val)
32029            self.write_keyword("VALUES LESS THAN");
32030            self.write(" (");
32031            self.generate_expression(&e.expressions[0])?;
32032            self.write(")");
32033        } else if !e.expressions.is_empty() {
32034            // Multiple values with Tuple: VALUES [(val1), (val2))
32035            self.write_keyword("VALUES");
32036            self.write(" [");
32037            for (i, expr) in e.expressions.iter().enumerate() {
32038                if i > 0 {
32039                    self.write(", ");
32040                }
32041                // If the expr is a Tuple, generate its contents wrapped in parens
32042                if let Expression::Tuple(t) = expr {
32043                    self.write("(");
32044                    for (j, inner) in t.expressions.iter().enumerate() {
32045                        if j > 0 {
32046                            self.write(", ");
32047                        }
32048                        self.generate_expression(inner)?;
32049                    }
32050                    self.write(")");
32051                } else {
32052                    self.write("(");
32053                    self.generate_expression(expr)?;
32054                    self.write(")");
32055                }
32056            }
32057            self.write(")");
32058        }
32059        Ok(())
32060    }
32061
32062    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
32063        // BUCKET(this, expression)
32064        self.write_keyword("BUCKET");
32065        self.write("(");
32066        self.generate_expression(&e.this)?;
32067        self.write(", ");
32068        self.generate_expression(&e.expression)?;
32069        self.write(")");
32070        Ok(())
32071    }
32072
32073    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
32074        // BigQuery table property: PARTITION BY expression [, expression ...]
32075        self.write_keyword("PARTITION BY");
32076        self.write_space();
32077        for (i, expr) in e.expressions.iter().enumerate() {
32078            if i > 0 {
32079                self.write(", ");
32080            }
32081            self.generate_expression(expr)?;
32082        }
32083        Ok(())
32084    }
32085
32086    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
32087        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
32088        if matches!(
32089            self.config.dialect,
32090            Some(crate::dialects::DialectType::Teradata)
32091                | Some(crate::dialects::DialectType::ClickHouse)
32092        ) {
32093            self.write_keyword("PARTITION BY");
32094        } else {
32095            self.write_keyword("PARTITIONED BY");
32096        }
32097        self.write_space();
32098        // In pretty mode, always use multiline tuple format for PARTITIONED BY
32099        if self.config.pretty {
32100            if let Expression::Tuple(ref tuple) = *e.this {
32101                self.write("(");
32102                self.write_newline();
32103                self.indent_level += 1;
32104                for (i, expr) in tuple.expressions.iter().enumerate() {
32105                    if i > 0 {
32106                        self.write(",");
32107                        self.write_newline();
32108                    }
32109                    self.write_indent();
32110                    self.generate_expression(expr)?;
32111                }
32112                self.indent_level -= 1;
32113                self.write_newline();
32114                self.write(")");
32115            } else {
32116                self.generate_expression(&e.this)?;
32117            }
32118        } else {
32119            self.generate_expression(&e.this)?;
32120        }
32121        Ok(())
32122    }
32123
32124    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
32125        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
32126        self.write_keyword("PARTITION OF");
32127        self.write_space();
32128        self.generate_expression(&e.this)?;
32129        // Check if expression is a PartitionBoundSpec
32130        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
32131            self.write_space();
32132            self.write_keyword("FOR VALUES");
32133            self.write_space();
32134            self.generate_expression(&e.expression)?;
32135        } else {
32136            self.write_space();
32137            self.write_keyword("DEFAULT");
32138        }
32139        Ok(())
32140    }
32141
32142    fn generate_period_for_system_time_constraint(
32143        &mut self,
32144        e: &PeriodForSystemTimeConstraint,
32145    ) -> Result<()> {
32146        // PERIOD FOR SYSTEM_TIME (this, expression)
32147        self.write_keyword("PERIOD FOR SYSTEM_TIME");
32148        self.write(" (");
32149        self.generate_expression(&e.this)?;
32150        self.write(", ");
32151        self.generate_expression(&e.expression)?;
32152        self.write(")");
32153        Ok(())
32154    }
32155
32156    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
32157        // value AS alias
32158        // The alias can be an identifier or an expression (e.g., string concatenation)
32159        self.generate_expression(&e.this)?;
32160        self.write_space();
32161        self.write_keyword("AS");
32162        self.write_space();
32163        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
32164        if self.config.unpivot_aliases_are_identifiers {
32165            match &e.alias {
32166                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
32167                    let Literal::String(s) = lit.as_ref() else {
32168                        unreachable!()
32169                    };
32170                    // Convert string literal to identifier
32171                    self.generate_identifier(&Identifier::new(s.clone()))?;
32172                }
32173                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
32174                    let Literal::Number(n) = lit.as_ref() else {
32175                        unreachable!()
32176                    };
32177                    // Convert number literal to quoted identifier
32178                    let mut id = Identifier::new(n.clone());
32179                    id.quoted = true;
32180                    self.generate_identifier(&id)?;
32181                }
32182                other => {
32183                    self.generate_expression(other)?;
32184                }
32185            }
32186        } else {
32187            self.generate_expression(&e.alias)?;
32188        }
32189        Ok(())
32190    }
32191
32192    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
32193        // ANY or ANY [expression]
32194        self.write_keyword("ANY");
32195        if let Some(this) = &e.this {
32196            self.write_space();
32197            self.generate_expression(this)?;
32198        }
32199        Ok(())
32200    }
32201
32202    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
32203        // ML.PREDICT(MODEL this, expression, [params_struct])
32204        self.write_keyword("ML.PREDICT");
32205        self.write("(");
32206        self.write_keyword("MODEL");
32207        self.write_space();
32208        self.generate_expression(&e.this)?;
32209        self.write(", ");
32210        self.generate_expression(&e.expression)?;
32211        if let Some(params) = &e.params_struct {
32212            self.write(", ");
32213            self.generate_expression(params)?;
32214        }
32215        self.write(")");
32216        Ok(())
32217    }
32218
32219    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
32220        // PREVIOUS_DAY(this, expression)
32221        self.write_keyword("PREVIOUS_DAY");
32222        self.write("(");
32223        self.generate_expression(&e.this)?;
32224        self.write(", ");
32225        self.generate_expression(&e.expression)?;
32226        self.write(")");
32227        Ok(())
32228    }
32229
32230    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
32231        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
32232        self.write_keyword("PRIMARY KEY");
32233        if let Some(name) = &e.this {
32234            self.write_space();
32235            self.generate_expression(name)?;
32236        }
32237        if !e.expressions.is_empty() {
32238            self.write(" (");
32239            for (i, expr) in e.expressions.iter().enumerate() {
32240                if i > 0 {
32241                    self.write(", ");
32242                }
32243                self.generate_expression(expr)?;
32244            }
32245            self.write(")");
32246        }
32247        if let Some(include) = &e.include {
32248            self.write_space();
32249            self.generate_expression(include)?;
32250        }
32251        if !e.options.is_empty() {
32252            self.write_space();
32253            for (i, opt) in e.options.iter().enumerate() {
32254                if i > 0 {
32255                    self.write_space();
32256                }
32257                self.generate_expression(opt)?;
32258            }
32259        }
32260        Ok(())
32261    }
32262
32263    fn generate_primary_key_column_constraint(
32264        &mut self,
32265        _e: &PrimaryKeyColumnConstraint,
32266    ) -> Result<()> {
32267        // PRIMARY KEY constraint at column level
32268        self.write_keyword("PRIMARY KEY");
32269        Ok(())
32270    }
32271
32272    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
32273        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
32274        self.write_keyword("PATH");
32275        self.write_space();
32276        self.generate_expression(&e.this)?;
32277        Ok(())
32278    }
32279
32280    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
32281        // PROJECTION this (expression)
32282        self.write_keyword("PROJECTION");
32283        self.write_space();
32284        self.generate_expression(&e.this)?;
32285        self.write(" (");
32286        self.generate_expression(&e.expression)?;
32287        self.write(")");
32288        Ok(())
32289    }
32290
32291    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
32292        // Properties list
32293        for (i, prop) in e.expressions.iter().enumerate() {
32294            if i > 0 {
32295                self.write(", ");
32296            }
32297            self.generate_expression(prop)?;
32298        }
32299        Ok(())
32300    }
32301
32302    fn generate_property(&mut self, e: &Property) -> Result<()> {
32303        // name=value
32304        self.generate_expression(&e.this)?;
32305        if let Some(value) = &e.value {
32306            self.write("=");
32307            self.generate_expression(value)?;
32308        }
32309        Ok(())
32310    }
32311
32312    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
32313        self.write_keyword("OPTIONS");
32314        if e.entries.is_empty() {
32315            self.write(" ()");
32316            return Ok(());
32317        }
32318
32319        if self.config.pretty {
32320            self.write(" (");
32321            self.write_newline();
32322            self.indent_level += 1;
32323            for (i, entry) in e.entries.iter().enumerate() {
32324                if i > 0 {
32325                    self.write(",");
32326                    self.write_newline();
32327                }
32328                self.write_indent();
32329                self.generate_identifier(&entry.key)?;
32330                self.write("=");
32331                self.generate_expression(&entry.value)?;
32332            }
32333            self.indent_level -= 1;
32334            self.write_newline();
32335            self.write(")");
32336        } else {
32337            self.write(" (");
32338            for (i, entry) in e.entries.iter().enumerate() {
32339                if i > 0 {
32340                    self.write(", ");
32341                }
32342                self.generate_identifier(&entry.key)?;
32343                self.write("=");
32344                self.generate_expression(&entry.value)?;
32345            }
32346            self.write(")");
32347        }
32348        Ok(())
32349    }
32350
32351    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
32352    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
32353        self.write_keyword("OPTIONS");
32354        self.write(" (");
32355        for (i, opt) in options.iter().enumerate() {
32356            if i > 0 {
32357                self.write(", ");
32358            }
32359            self.generate_option_expression(opt)?;
32360        }
32361        self.write(")");
32362        Ok(())
32363    }
32364
32365    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
32366    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
32367        self.write_keyword("PROPERTIES");
32368        self.write(" (");
32369        for (i, prop) in properties.iter().enumerate() {
32370            if i > 0 {
32371                self.write(", ");
32372            }
32373            self.generate_option_expression(prop)?;
32374        }
32375        self.write(")");
32376        Ok(())
32377    }
32378
32379    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
32380    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
32381        self.write_keyword("ENVIRONMENT");
32382        self.write(" (");
32383        for (i, env_item) in environment.iter().enumerate() {
32384            if i > 0 {
32385                self.write(", ");
32386            }
32387            self.generate_environment_expression(env_item)?;
32388        }
32389        self.write(")");
32390        Ok(())
32391    }
32392
32393    /// Generate an environment expression with spaces around =
32394    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
32395        match expr {
32396            Expression::Eq(eq) => {
32397                // Generate key = value with spaces (Databricks ENVIRONMENT style)
32398                self.generate_expression(&eq.left)?;
32399                self.write(" = ");
32400                self.generate_expression(&eq.right)?;
32401                Ok(())
32402            }
32403            _ => self.generate_expression(expr),
32404        }
32405    }
32406
32407    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
32408    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
32409        self.write_keyword("TBLPROPERTIES");
32410        if self.config.pretty {
32411            self.write(" (");
32412            self.write_newline();
32413            self.indent_level += 1;
32414            for (i, opt) in options.iter().enumerate() {
32415                if i > 0 {
32416                    self.write(",");
32417                    self.write_newline();
32418                }
32419                self.write_indent();
32420                self.generate_option_expression(opt)?;
32421            }
32422            self.indent_level -= 1;
32423            self.write_newline();
32424            self.write(")");
32425        } else {
32426            self.write(" (");
32427            for (i, opt) in options.iter().enumerate() {
32428                if i > 0 {
32429                    self.write(", ");
32430                }
32431                self.generate_option_expression(opt)?;
32432            }
32433            self.write(")");
32434        }
32435        Ok(())
32436    }
32437
32438    /// Generate an option expression without spaces around =
32439    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
32440        match expr {
32441            Expression::Eq(eq) => {
32442                // Generate key=value without spaces
32443                self.generate_expression(&eq.left)?;
32444                self.write("=");
32445                self.generate_expression(&eq.right)?;
32446                Ok(())
32447            }
32448            _ => self.generate_expression(expr),
32449        }
32450    }
32451
32452    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
32453        // Just output the name
32454        self.generate_expression(&e.this)?;
32455        Ok(())
32456    }
32457
32458    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
32459        // PUT source_file @stage [options]
32460        self.write_keyword("PUT");
32461        self.write_space();
32462
32463        // Source file path - preserve original quoting
32464        if e.source_quoted {
32465            self.write("'");
32466            self.write(&e.source);
32467            self.write("'");
32468        } else {
32469            self.write(&e.source);
32470        }
32471
32472        self.write_space();
32473
32474        // Target stage reference - output the string directly (includes @)
32475        if let Expression::Literal(lit) = &e.target {
32476            if let Literal::String(s) = lit.as_ref() {
32477                self.write(s);
32478            }
32479        } else {
32480            self.generate_expression(&e.target)?;
32481        }
32482
32483        // Optional parameters: KEY=VALUE
32484        for param in &e.params {
32485            self.write_space();
32486            self.write(&param.name);
32487            if let Some(ref value) = param.value {
32488                self.write("=");
32489                self.generate_expression(value)?;
32490            }
32491        }
32492
32493        Ok(())
32494    }
32495
32496    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
32497        // QUANTILE(this, quantile)
32498        self.write_keyword("QUANTILE");
32499        self.write("(");
32500        self.generate_expression(&e.this)?;
32501        if let Some(quantile) = &e.quantile {
32502            self.write(", ");
32503            self.generate_expression(quantile)?;
32504        }
32505        self.write(")");
32506        Ok(())
32507    }
32508
32509    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
32510        // QUERY_BAND = this [UPDATE] [FOR scope]
32511        if matches!(
32512            self.config.dialect,
32513            Some(crate::dialects::DialectType::Teradata)
32514        ) {
32515            self.write_keyword("SET");
32516            self.write_space();
32517        }
32518        self.write_keyword("QUERY_BAND");
32519        self.write(" = ");
32520        self.generate_expression(&e.this)?;
32521        if e.update.is_some() {
32522            self.write_space();
32523            self.write_keyword("UPDATE");
32524        }
32525        if let Some(scope) = &e.scope {
32526            self.write_space();
32527            self.write_keyword("FOR");
32528            self.write_space();
32529            self.generate_expression(scope)?;
32530        }
32531        Ok(())
32532    }
32533
32534    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
32535        // this = expression
32536        self.generate_expression(&e.this)?;
32537        if let Some(expression) = &e.expression {
32538            self.write(" = ");
32539            self.generate_expression(expression)?;
32540        }
32541        Ok(())
32542    }
32543
32544    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
32545        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
32546        self.write_keyword("TRANSFORM");
32547        self.write("(");
32548        for (i, expr) in e.expressions.iter().enumerate() {
32549            if i > 0 {
32550                self.write(", ");
32551            }
32552            self.generate_expression(expr)?;
32553        }
32554        self.write(")");
32555        if let Some(row_format_before) = &e.row_format_before {
32556            self.write_space();
32557            self.generate_expression(row_format_before)?;
32558        }
32559        if let Some(record_writer) = &e.record_writer {
32560            self.write_space();
32561            self.write_keyword("RECORDWRITER");
32562            self.write_space();
32563            self.generate_expression(record_writer)?;
32564        }
32565        if let Some(command_script) = &e.command_script {
32566            self.write_space();
32567            self.write_keyword("USING");
32568            self.write_space();
32569            self.generate_expression(command_script)?;
32570        }
32571        if let Some(schema) = &e.schema {
32572            self.write_space();
32573            self.write_keyword("AS");
32574            self.write_space();
32575            self.generate_expression(schema)?;
32576        }
32577        if let Some(row_format_after) = &e.row_format_after {
32578            self.write_space();
32579            self.generate_expression(row_format_after)?;
32580        }
32581        if let Some(record_reader) = &e.record_reader {
32582            self.write_space();
32583            self.write_keyword("RECORDREADER");
32584            self.write_space();
32585            self.generate_expression(record_reader)?;
32586        }
32587        Ok(())
32588    }
32589
32590    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
32591        // RANDN([seed])
32592        self.write_keyword("RANDN");
32593        self.write("(");
32594        if let Some(this) = &e.this {
32595            self.generate_expression(this)?;
32596        }
32597        self.write(")");
32598        Ok(())
32599    }
32600
32601    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
32602        // RANDSTR(this, [generator])
32603        self.write_keyword("RANDSTR");
32604        self.write("(");
32605        self.generate_expression(&e.this)?;
32606        if let Some(generator) = &e.generator {
32607            self.write(", ");
32608            self.generate_expression(generator)?;
32609        }
32610        self.write(")");
32611        Ok(())
32612    }
32613
32614    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
32615        // RANGE_BUCKET(this, expression)
32616        self.write_keyword("RANGE_BUCKET");
32617        self.write("(");
32618        self.generate_expression(&e.this)?;
32619        self.write(", ");
32620        self.generate_expression(&e.expression)?;
32621        self.write(")");
32622        Ok(())
32623    }
32624
32625    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
32626        // RANGE_N(this BETWEEN expressions [EACH each])
32627        self.write_keyword("RANGE_N");
32628        self.write("(");
32629        self.generate_expression(&e.this)?;
32630        self.write_space();
32631        self.write_keyword("BETWEEN");
32632        self.write_space();
32633        for (i, expr) in e.expressions.iter().enumerate() {
32634            if i > 0 {
32635                self.write(", ");
32636            }
32637            self.generate_expression(expr)?;
32638        }
32639        if let Some(each) = &e.each {
32640            self.write_space();
32641            self.write_keyword("EACH");
32642            self.write_space();
32643            self.generate_expression(each)?;
32644        }
32645        self.write(")");
32646        Ok(())
32647    }
32648
32649    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
32650        // READ_CSV(this, expressions...)
32651        self.write_keyword("READ_CSV");
32652        self.write("(");
32653        self.generate_expression(&e.this)?;
32654        for expr in &e.expressions {
32655            self.write(", ");
32656            self.generate_expression(expr)?;
32657        }
32658        self.write(")");
32659        Ok(())
32660    }
32661
32662    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
32663        // READ_PARQUET(expressions...)
32664        self.write_keyword("READ_PARQUET");
32665        self.write("(");
32666        for (i, expr) in e.expressions.iter().enumerate() {
32667            if i > 0 {
32668                self.write(", ");
32669            }
32670            self.generate_expression(expr)?;
32671        }
32672        self.write(")");
32673        Ok(())
32674    }
32675
32676    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
32677        // SEARCH kind FIRST BY this SET expression [USING using]
32678        // or CYCLE this SET expression [USING using]
32679        if e.kind == "CYCLE" {
32680            self.write_keyword("CYCLE");
32681        } else {
32682            self.write_keyword("SEARCH");
32683            self.write_space();
32684            self.write(&e.kind);
32685            self.write_space();
32686            self.write_keyword("FIRST BY");
32687        }
32688        self.write_space();
32689        self.generate_expression(&e.this)?;
32690        self.write_space();
32691        self.write_keyword("SET");
32692        self.write_space();
32693        self.generate_expression(&e.expression)?;
32694        if let Some(using) = &e.using {
32695            self.write_space();
32696            self.write_keyword("USING");
32697            self.write_space();
32698            self.generate_expression(using)?;
32699        }
32700        Ok(())
32701    }
32702
32703    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
32704        // REDUCE(this, initial, merge, [finish])
32705        self.write_keyword("REDUCE");
32706        self.write("(");
32707        self.generate_expression(&e.this)?;
32708        if let Some(initial) = &e.initial {
32709            self.write(", ");
32710            self.generate_expression(initial)?;
32711        }
32712        if let Some(merge) = &e.merge {
32713            self.write(", ");
32714            self.generate_expression(merge)?;
32715        }
32716        if let Some(finish) = &e.finish {
32717            self.write(", ");
32718            self.generate_expression(finish)?;
32719        }
32720        self.write(")");
32721        Ok(())
32722    }
32723
32724    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
32725        // REFERENCES this (expressions) [options]
32726        self.write_keyword("REFERENCES");
32727        self.write_space();
32728        self.generate_expression(&e.this)?;
32729        if !e.expressions.is_empty() {
32730            self.write(" (");
32731            for (i, expr) in e.expressions.iter().enumerate() {
32732                if i > 0 {
32733                    self.write(", ");
32734                }
32735                self.generate_expression(expr)?;
32736            }
32737            self.write(")");
32738        }
32739        for opt in &e.options {
32740            self.write_space();
32741            self.generate_expression(opt)?;
32742        }
32743        Ok(())
32744    }
32745
32746    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
32747        // REFRESH [kind] this
32748        self.write_keyword("REFRESH");
32749        if !e.kind.is_empty() {
32750            self.write_space();
32751            self.write_keyword(&e.kind);
32752        }
32753        self.write_space();
32754        self.generate_expression(&e.this)?;
32755        Ok(())
32756    }
32757
32758    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
32759        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
32760        self.write_keyword("REFRESH");
32761        self.write_space();
32762        self.write_keyword(&e.method);
32763
32764        if let Some(ref kind) = e.kind {
32765            self.write_space();
32766            self.write_keyword("ON");
32767            self.write_space();
32768            self.write_keyword(kind);
32769
32770            // EVERY n UNIT
32771            if let Some(ref every) = e.every {
32772                self.write_space();
32773                self.write_keyword("EVERY");
32774                self.write_space();
32775                self.generate_expression(every)?;
32776                if let Some(ref unit) = e.unit {
32777                    self.write_space();
32778                    self.write_keyword(unit);
32779                }
32780            }
32781
32782            // STARTS 'datetime'
32783            if let Some(ref starts) = e.starts {
32784                self.write_space();
32785                self.write_keyword("STARTS");
32786                self.write_space();
32787                self.generate_expression(starts)?;
32788            }
32789        }
32790        Ok(())
32791    }
32792
32793    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
32794        // REGEXP_COUNT(this, expression, position, parameters)
32795        self.write_keyword("REGEXP_COUNT");
32796        self.write("(");
32797        self.generate_expression(&e.this)?;
32798        self.write(", ");
32799        self.generate_expression(&e.expression)?;
32800        if let Some(position) = &e.position {
32801            self.write(", ");
32802            self.generate_expression(position)?;
32803        }
32804        if let Some(parameters) = &e.parameters {
32805            self.write(", ");
32806            self.generate_expression(parameters)?;
32807        }
32808        self.write(")");
32809        Ok(())
32810    }
32811
32812    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
32813        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
32814        self.write_keyword("REGEXP_EXTRACT_ALL");
32815        self.write("(");
32816        self.generate_expression(&e.this)?;
32817        self.write(", ");
32818        self.generate_expression(&e.expression)?;
32819        if let Some(group) = &e.group {
32820            self.write(", ");
32821            self.generate_expression(group)?;
32822        }
32823        self.write(")");
32824        Ok(())
32825    }
32826
32827    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
32828        // REGEXP_FULL_MATCH(this, expression)
32829        self.write_keyword("REGEXP_FULL_MATCH");
32830        self.write("(");
32831        self.generate_expression(&e.this)?;
32832        self.write(", ");
32833        self.generate_expression(&e.expression)?;
32834        self.write(")");
32835        Ok(())
32836    }
32837
32838    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
32839        use crate::dialects::DialectType;
32840        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
32841        if matches!(
32842            self.config.dialect,
32843            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
32844        ) && e.flag.is_none()
32845        {
32846            self.generate_expression(&e.this)?;
32847            self.write(" ~* ");
32848            self.generate_expression(&e.expression)?;
32849        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
32850            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
32851            self.write_keyword("REGEXP_LIKE");
32852            self.write("(");
32853            self.generate_expression(&e.this)?;
32854            self.write(", ");
32855            self.generate_expression(&e.expression)?;
32856            self.write(", ");
32857            if let Some(flag) = &e.flag {
32858                self.generate_expression(flag)?;
32859            } else {
32860                self.write("'i'");
32861            }
32862            self.write(")");
32863        } else {
32864            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
32865            self.generate_expression(&e.this)?;
32866            self.write_space();
32867            self.write_keyword("REGEXP_ILIKE");
32868            self.write_space();
32869            self.generate_expression(&e.expression)?;
32870            if let Some(flag) = &e.flag {
32871                self.write(", ");
32872                self.generate_expression(flag)?;
32873            }
32874        }
32875        Ok(())
32876    }
32877
32878    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
32879        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
32880        self.write_keyword("REGEXP_INSTR");
32881        self.write("(");
32882        self.generate_expression(&e.this)?;
32883        self.write(", ");
32884        self.generate_expression(&e.expression)?;
32885        if let Some(position) = &e.position {
32886            self.write(", ");
32887            self.generate_expression(position)?;
32888        }
32889        if let Some(occurrence) = &e.occurrence {
32890            self.write(", ");
32891            self.generate_expression(occurrence)?;
32892        }
32893        if let Some(option) = &e.option {
32894            self.write(", ");
32895            self.generate_expression(option)?;
32896        }
32897        if let Some(parameters) = &e.parameters {
32898            self.write(", ");
32899            self.generate_expression(parameters)?;
32900        }
32901        if let Some(group) = &e.group {
32902            self.write(", ");
32903            self.generate_expression(group)?;
32904        }
32905        self.write(")");
32906        Ok(())
32907    }
32908
32909    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
32910        // REGEXP_SPLIT(this, expression, limit)
32911        self.write_keyword("REGEXP_SPLIT");
32912        self.write("(");
32913        self.generate_expression(&e.this)?;
32914        self.write(", ");
32915        self.generate_expression(&e.expression)?;
32916        if let Some(limit) = &e.limit {
32917            self.write(", ");
32918            self.generate_expression(limit)?;
32919        }
32920        self.write(")");
32921        Ok(())
32922    }
32923
32924    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
32925        // REGR_AVGX(this, expression)
32926        self.write_keyword("REGR_AVGX");
32927        self.write("(");
32928        self.generate_expression(&e.this)?;
32929        self.write(", ");
32930        self.generate_expression(&e.expression)?;
32931        self.write(")");
32932        Ok(())
32933    }
32934
32935    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
32936        // REGR_AVGY(this, expression)
32937        self.write_keyword("REGR_AVGY");
32938        self.write("(");
32939        self.generate_expression(&e.this)?;
32940        self.write(", ");
32941        self.generate_expression(&e.expression)?;
32942        self.write(")");
32943        Ok(())
32944    }
32945
32946    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
32947        // REGR_COUNT(this, expression)
32948        self.write_keyword("REGR_COUNT");
32949        self.write("(");
32950        self.generate_expression(&e.this)?;
32951        self.write(", ");
32952        self.generate_expression(&e.expression)?;
32953        self.write(")");
32954        Ok(())
32955    }
32956
32957    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
32958        // REGR_INTERCEPT(this, expression)
32959        self.write_keyword("REGR_INTERCEPT");
32960        self.write("(");
32961        self.generate_expression(&e.this)?;
32962        self.write(", ");
32963        self.generate_expression(&e.expression)?;
32964        self.write(")");
32965        Ok(())
32966    }
32967
32968    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
32969        // REGR_R2(this, expression)
32970        self.write_keyword("REGR_R2");
32971        self.write("(");
32972        self.generate_expression(&e.this)?;
32973        self.write(", ");
32974        self.generate_expression(&e.expression)?;
32975        self.write(")");
32976        Ok(())
32977    }
32978
32979    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
32980        // REGR_SLOPE(this, expression)
32981        self.write_keyword("REGR_SLOPE");
32982        self.write("(");
32983        self.generate_expression(&e.this)?;
32984        self.write(", ");
32985        self.generate_expression(&e.expression)?;
32986        self.write(")");
32987        Ok(())
32988    }
32989
32990    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
32991        // REGR_SXX(this, expression)
32992        self.write_keyword("REGR_SXX");
32993        self.write("(");
32994        self.generate_expression(&e.this)?;
32995        self.write(", ");
32996        self.generate_expression(&e.expression)?;
32997        self.write(")");
32998        Ok(())
32999    }
33000
33001    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
33002        // REGR_SXY(this, expression)
33003        self.write_keyword("REGR_SXY");
33004        self.write("(");
33005        self.generate_expression(&e.this)?;
33006        self.write(", ");
33007        self.generate_expression(&e.expression)?;
33008        self.write(")");
33009        Ok(())
33010    }
33011
33012    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
33013        // REGR_SYY(this, expression)
33014        self.write_keyword("REGR_SYY");
33015        self.write("(");
33016        self.generate_expression(&e.this)?;
33017        self.write(", ");
33018        self.generate_expression(&e.expression)?;
33019        self.write(")");
33020        Ok(())
33021    }
33022
33023    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
33024        // REGR_VALX(this, expression)
33025        self.write_keyword("REGR_VALX");
33026        self.write("(");
33027        self.generate_expression(&e.this)?;
33028        self.write(", ");
33029        self.generate_expression(&e.expression)?;
33030        self.write(")");
33031        Ok(())
33032    }
33033
33034    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
33035        // REGR_VALY(this, expression)
33036        self.write_keyword("REGR_VALY");
33037        self.write("(");
33038        self.generate_expression(&e.this)?;
33039        self.write(", ");
33040        self.generate_expression(&e.expression)?;
33041        self.write(")");
33042        Ok(())
33043    }
33044
33045    fn generate_remote_with_connection_model_property(
33046        &mut self,
33047        e: &RemoteWithConnectionModelProperty,
33048    ) -> Result<()> {
33049        // REMOTE WITH CONNECTION this
33050        self.write_keyword("REMOTE WITH CONNECTION");
33051        self.write_space();
33052        self.generate_expression(&e.this)?;
33053        Ok(())
33054    }
33055
33056    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
33057        // RENAME COLUMN [IF EXISTS] this TO new_name
33058        self.write_keyword("RENAME COLUMN");
33059        if e.exists {
33060            self.write_space();
33061            self.write_keyword("IF EXISTS");
33062        }
33063        self.write_space();
33064        self.generate_expression(&e.this)?;
33065        if let Some(to) = &e.to {
33066            self.write_space();
33067            self.write_keyword("TO");
33068            self.write_space();
33069            self.generate_expression(to)?;
33070        }
33071        Ok(())
33072    }
33073
33074    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
33075        // REPLACE PARTITION expression [FROM source]
33076        self.write_keyword("REPLACE PARTITION");
33077        self.write_space();
33078        self.generate_expression(&e.expression)?;
33079        if let Some(source) = &e.source {
33080            self.write_space();
33081            self.write_keyword("FROM");
33082            self.write_space();
33083            self.generate_expression(source)?;
33084        }
33085        Ok(())
33086    }
33087
33088    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
33089        // RETURNING expressions [INTO into]
33090        // TSQL and Fabric use OUTPUT instead of RETURNING
33091        let keyword = match self.config.dialect {
33092            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
33093            _ => "RETURNING",
33094        };
33095        self.write_keyword(keyword);
33096        self.write_space();
33097        for (i, expr) in e.expressions.iter().enumerate() {
33098            if i > 0 {
33099                self.write(", ");
33100            }
33101            self.generate_expression(expr)?;
33102        }
33103        if let Some(into) = &e.into {
33104            self.write_space();
33105            self.write_keyword("INTO");
33106            self.write_space();
33107            self.generate_expression(into)?;
33108        }
33109        Ok(())
33110    }
33111
33112    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
33113        // OUTPUT expressions [INTO into_table]
33114        self.write_space();
33115        self.write_keyword("OUTPUT");
33116        self.write_space();
33117        for (i, expr) in output.columns.iter().enumerate() {
33118            if i > 0 {
33119                self.write(", ");
33120            }
33121            self.generate_expression(expr)?;
33122        }
33123        if let Some(into_table) = &output.into_table {
33124            self.write_space();
33125            self.write_keyword("INTO");
33126            self.write_space();
33127            self.generate_expression(into_table)?;
33128        }
33129        Ok(())
33130    }
33131
33132    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
33133        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
33134        self.write_keyword("RETURNS");
33135        if e.is_table.is_some() {
33136            self.write_space();
33137            self.write_keyword("TABLE");
33138        }
33139        if let Some(table) = &e.table {
33140            self.write_space();
33141            self.generate_expression(table)?;
33142        } else if let Some(this) = &e.this {
33143            self.write_space();
33144            self.generate_expression(this)?;
33145        }
33146        if e.null.is_some() {
33147            self.write_space();
33148            self.write_keyword("NULL ON NULL INPUT");
33149        }
33150        Ok(())
33151    }
33152
33153    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
33154        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
33155        self.write_keyword("ROLLBACK");
33156
33157        // TSQL always uses ROLLBACK TRANSACTION
33158        if e.this.is_none()
33159            && matches!(
33160                self.config.dialect,
33161                Some(DialectType::TSQL) | Some(DialectType::Fabric)
33162            )
33163        {
33164            self.write_space();
33165            self.write_keyword("TRANSACTION");
33166        }
33167
33168        // Check if this has TRANSACTION keyword or transaction name
33169        if let Some(this) = &e.this {
33170            // Check if it's just the "TRANSACTION" marker or an actual transaction name
33171            let is_transaction_marker = matches!(
33172                this.as_ref(),
33173                Expression::Identifier(id) if id.name == "TRANSACTION"
33174            );
33175
33176            self.write_space();
33177            self.write_keyword("TRANSACTION");
33178
33179            // If it's a real transaction name, output it
33180            if !is_transaction_marker {
33181                self.write_space();
33182                self.generate_expression(this)?;
33183            }
33184        }
33185
33186        // Output TO savepoint
33187        if let Some(savepoint) = &e.savepoint {
33188            self.write_space();
33189            self.write_keyword("TO");
33190            self.write_space();
33191            self.generate_expression(savepoint)?;
33192        }
33193        Ok(())
33194    }
33195
33196    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
33197        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
33198        if e.expressions.is_empty() {
33199            self.write_keyword("WITH ROLLUP");
33200        } else {
33201            self.write_keyword("ROLLUP");
33202            self.write("(");
33203            for (i, expr) in e.expressions.iter().enumerate() {
33204                if i > 0 {
33205                    self.write(", ");
33206                }
33207                self.generate_expression(expr)?;
33208            }
33209            self.write(")");
33210        }
33211        Ok(())
33212    }
33213
33214    fn generate_row_format_delimited_property(
33215        &mut self,
33216        e: &RowFormatDelimitedProperty,
33217    ) -> Result<()> {
33218        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
33219        self.write_keyword("ROW FORMAT DELIMITED");
33220        if let Some(fields) = &e.fields {
33221            self.write_space();
33222            self.write_keyword("FIELDS TERMINATED BY");
33223            self.write_space();
33224            self.generate_expression(fields)?;
33225        }
33226        if let Some(escaped) = &e.escaped {
33227            self.write_space();
33228            self.write_keyword("ESCAPED BY");
33229            self.write_space();
33230            self.generate_expression(escaped)?;
33231        }
33232        if let Some(items) = &e.collection_items {
33233            self.write_space();
33234            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
33235            self.write_space();
33236            self.generate_expression(items)?;
33237        }
33238        if let Some(keys) = &e.map_keys {
33239            self.write_space();
33240            self.write_keyword("MAP KEYS TERMINATED BY");
33241            self.write_space();
33242            self.generate_expression(keys)?;
33243        }
33244        if let Some(lines) = &e.lines {
33245            self.write_space();
33246            self.write_keyword("LINES TERMINATED BY");
33247            self.write_space();
33248            self.generate_expression(lines)?;
33249        }
33250        if let Some(null) = &e.null {
33251            self.write_space();
33252            self.write_keyword("NULL DEFINED AS");
33253            self.write_space();
33254            self.generate_expression(null)?;
33255        }
33256        if let Some(serde) = &e.serde {
33257            self.write_space();
33258            self.generate_expression(serde)?;
33259        }
33260        Ok(())
33261    }
33262
33263    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
33264        // ROW FORMAT this
33265        self.write_keyword("ROW FORMAT");
33266        self.write_space();
33267        self.generate_expression(&e.this)?;
33268        Ok(())
33269    }
33270
33271    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
33272        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
33273        self.write_keyword("ROW FORMAT SERDE");
33274        self.write_space();
33275        self.generate_expression(&e.this)?;
33276        if let Some(props) = &e.serde_properties {
33277            self.write_space();
33278            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
33279            self.generate_expression(props)?;
33280        }
33281        Ok(())
33282    }
33283
33284    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
33285        // SHA2(this, length)
33286        self.write_keyword("SHA2");
33287        self.write("(");
33288        self.generate_expression(&e.this)?;
33289        if let Some(length) = e.length {
33290            self.write(", ");
33291            self.write(&length.to_string());
33292        }
33293        self.write(")");
33294        Ok(())
33295    }
33296
33297    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
33298        // SHA2_DIGEST(this, length)
33299        self.write_keyword("SHA2_DIGEST");
33300        self.write("(");
33301        self.generate_expression(&e.this)?;
33302        if let Some(length) = e.length {
33303            self.write(", ");
33304            self.write(&length.to_string());
33305        }
33306        self.write(")");
33307        Ok(())
33308    }
33309
33310    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
33311        let name = if matches!(
33312            self.config.dialect,
33313            Some(crate::dialects::DialectType::Spark)
33314                | Some(crate::dialects::DialectType::Databricks)
33315        ) {
33316            "TRY_ADD"
33317        } else {
33318            "SAFE_ADD"
33319        };
33320        self.write_keyword(name);
33321        self.write("(");
33322        self.generate_expression(&e.this)?;
33323        self.write(", ");
33324        self.generate_expression(&e.expression)?;
33325        self.write(")");
33326        Ok(())
33327    }
33328
33329    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
33330        // SAFE_DIVIDE(this, expression)
33331        self.write_keyword("SAFE_DIVIDE");
33332        self.write("(");
33333        self.generate_expression(&e.this)?;
33334        self.write(", ");
33335        self.generate_expression(&e.expression)?;
33336        self.write(")");
33337        Ok(())
33338    }
33339
33340    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
33341        let name = if matches!(
33342            self.config.dialect,
33343            Some(crate::dialects::DialectType::Spark)
33344                | Some(crate::dialects::DialectType::Databricks)
33345        ) {
33346            "TRY_MULTIPLY"
33347        } else {
33348            "SAFE_MULTIPLY"
33349        };
33350        self.write_keyword(name);
33351        self.write("(");
33352        self.generate_expression(&e.this)?;
33353        self.write(", ");
33354        self.generate_expression(&e.expression)?;
33355        self.write(")");
33356        Ok(())
33357    }
33358
33359    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
33360        let name = if matches!(
33361            self.config.dialect,
33362            Some(crate::dialects::DialectType::Spark)
33363                | Some(crate::dialects::DialectType::Databricks)
33364        ) {
33365            "TRY_SUBTRACT"
33366        } else {
33367            "SAFE_SUBTRACT"
33368        };
33369        self.write_keyword(name);
33370        self.write("(");
33371        self.generate_expression(&e.this)?;
33372        self.write(", ");
33373        self.generate_expression(&e.expression)?;
33374        self.write(")");
33375        Ok(())
33376    }
33377
33378    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
33379    /// METHOD (size UNIT) [REPEATABLE (seed)]
33380    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
33381        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
33382        if matches!(sample.method, SampleMethod::Bucket) {
33383            self.write(" (");
33384            self.write_keyword("BUCKET");
33385            self.write_space();
33386            if let Some(ref num) = sample.bucket_numerator {
33387                self.generate_expression(num)?;
33388            }
33389            self.write_space();
33390            self.write_keyword("OUT OF");
33391            self.write_space();
33392            if let Some(ref denom) = sample.bucket_denominator {
33393                self.generate_expression(denom)?;
33394            }
33395            if let Some(ref field) = sample.bucket_field {
33396                self.write_space();
33397                self.write_keyword("ON");
33398                self.write_space();
33399                self.generate_expression(field)?;
33400            }
33401            self.write(")");
33402            return Ok(());
33403        }
33404
33405        // Output method name if explicitly specified, or for dialects that always require it
33406        let is_snowflake = matches!(
33407            self.config.dialect,
33408            Some(crate::dialects::DialectType::Snowflake)
33409        );
33410        let is_postgres = matches!(
33411            self.config.dialect,
33412            Some(crate::dialects::DialectType::PostgreSQL)
33413                | Some(crate::dialects::DialectType::Redshift)
33414        );
33415        // Databricks and Spark don't output method names
33416        let is_databricks = matches!(
33417            self.config.dialect,
33418            Some(crate::dialects::DialectType::Databricks)
33419        );
33420        let is_spark = matches!(
33421            self.config.dialect,
33422            Some(crate::dialects::DialectType::Spark)
33423        );
33424        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
33425        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
33426        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
33427        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
33428            self.write_space();
33429            if !sample.explicit_method && (is_snowflake || force_method) {
33430                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
33431                self.write_keyword("BERNOULLI");
33432            } else {
33433                match sample.method {
33434                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
33435                    SampleMethod::System => self.write_keyword("SYSTEM"),
33436                    SampleMethod::Block => self.write_keyword("BLOCK"),
33437                    SampleMethod::Row => self.write_keyword("ROW"),
33438                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
33439                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
33440                    SampleMethod::Bucket => {} // handled above
33441                }
33442            }
33443        }
33444
33445        // Output size, with or without parentheses depending on dialect
33446        let emit_size_no_parens = !self.config.tablesample_requires_parens;
33447        if emit_size_no_parens {
33448            self.write_space();
33449            match &sample.size {
33450                Expression::Tuple(tuple) => {
33451                    for (i, expr) in tuple.expressions.iter().enumerate() {
33452                        if i > 0 {
33453                            self.write(", ");
33454                        }
33455                        self.generate_expression(expr)?;
33456                    }
33457                }
33458                expr => self.generate_expression(expr)?,
33459            }
33460        } else {
33461            self.write(" (");
33462            self.generate_expression(&sample.size)?;
33463        }
33464
33465        // Determine unit
33466        let is_rows_method = matches!(
33467            sample.method,
33468            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
33469        );
33470        let is_percent = matches!(
33471            sample.method,
33472            SampleMethod::Percent
33473                | SampleMethod::System
33474                | SampleMethod::Bernoulli
33475                | SampleMethod::Block
33476        );
33477
33478        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
33479        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
33480        // For Databricks and Spark, always output PERCENT for percentage samples.
33481        let is_presto = matches!(
33482            self.config.dialect,
33483            Some(crate::dialects::DialectType::Presto)
33484                | Some(crate::dialects::DialectType::Trino)
33485                | Some(crate::dialects::DialectType::Athena)
33486        );
33487        let should_output_unit = if is_databricks || is_spark {
33488            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
33489            is_percent || is_rows_method || sample.unit_after_size
33490        } else if is_snowflake || is_postgres || is_presto {
33491            sample.unit_after_size
33492        } else {
33493            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
33494        };
33495
33496        if should_output_unit {
33497            self.write_space();
33498            if sample.is_percent {
33499                self.write_keyword("PERCENT");
33500            } else if is_rows_method && !sample.unit_after_size {
33501                self.write_keyword("ROWS");
33502            } else if sample.unit_after_size {
33503                match sample.method {
33504                    SampleMethod::Percent
33505                    | SampleMethod::System
33506                    | SampleMethod::Bernoulli
33507                    | SampleMethod::Block => {
33508                        self.write_keyword("PERCENT");
33509                    }
33510                    SampleMethod::Row | SampleMethod::Reservoir => {
33511                        self.write_keyword("ROWS");
33512                    }
33513                    _ => self.write_keyword("ROWS"),
33514                }
33515            } else {
33516                self.write_keyword("PERCENT");
33517            }
33518        }
33519
33520        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
33521            if let Some(ref offset) = sample.offset {
33522                self.write_space();
33523                self.write_keyword("OFFSET");
33524                self.write_space();
33525                self.generate_expression(offset)?;
33526            }
33527        }
33528        if !emit_size_no_parens {
33529            self.write(")");
33530        }
33531
33532        Ok(())
33533    }
33534
33535    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
33536        // SAMPLE this (ClickHouse uses SAMPLE BY)
33537        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
33538            self.write_keyword("SAMPLE BY");
33539        } else {
33540            self.write_keyword("SAMPLE");
33541        }
33542        self.write_space();
33543        self.generate_expression(&e.this)?;
33544        Ok(())
33545    }
33546
33547    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
33548        // this (expressions...)
33549        if let Some(this) = &e.this {
33550            self.generate_expression(this)?;
33551        }
33552        if !e.expressions.is_empty() {
33553            // Add space before column list if there's a preceding expression
33554            if e.this.is_some() {
33555                self.write_space();
33556            }
33557            self.write("(");
33558            for (i, expr) in e.expressions.iter().enumerate() {
33559                if i > 0 {
33560                    self.write(", ");
33561                }
33562                self.generate_expression(expr)?;
33563            }
33564            self.write(")");
33565        }
33566        Ok(())
33567    }
33568
33569    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
33570        // COMMENT this
33571        self.write_keyword("COMMENT");
33572        self.write_space();
33573        self.generate_expression(&e.this)?;
33574        Ok(())
33575    }
33576
33577    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
33578        // [this::]expression
33579        if let Some(this) = &e.this {
33580            self.generate_expression(this)?;
33581            self.write("::");
33582        }
33583        self.generate_expression(&e.expression)?;
33584        Ok(())
33585    }
33586
33587    fn generate_search(&mut self, e: &Search) -> Result<()> {
33588        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
33589        self.write_keyword("SEARCH");
33590        self.write("(");
33591        self.generate_expression(&e.this)?;
33592        self.write(", ");
33593        self.generate_expression(&e.expression)?;
33594        if let Some(json_scope) = &e.json_scope {
33595            self.write(", ");
33596            self.generate_expression(json_scope)?;
33597        }
33598        if let Some(analyzer) = &e.analyzer {
33599            self.write(", ");
33600            self.generate_expression(analyzer)?;
33601        }
33602        if let Some(analyzer_options) = &e.analyzer_options {
33603            self.write(", ");
33604            self.generate_expression(analyzer_options)?;
33605        }
33606        if let Some(search_mode) = &e.search_mode {
33607            self.write(", ");
33608            self.generate_expression(search_mode)?;
33609        }
33610        self.write(")");
33611        Ok(())
33612    }
33613
33614    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
33615        // SEARCH_IP(this, expression)
33616        self.write_keyword("SEARCH_IP");
33617        self.write("(");
33618        self.generate_expression(&e.this)?;
33619        self.write(", ");
33620        self.generate_expression(&e.expression)?;
33621        self.write(")");
33622        Ok(())
33623    }
33624
33625    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
33626        // SECURITY this
33627        self.write_keyword("SECURITY");
33628        self.write_space();
33629        self.generate_expression(&e.this)?;
33630        Ok(())
33631    }
33632
33633    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
33634        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
33635        self.write("SEMANTIC_VIEW(");
33636
33637        if self.config.pretty {
33638            // Pretty print: each clause on its own line
33639            self.write_newline();
33640            self.indent_level += 1;
33641            self.write_indent();
33642            self.generate_expression(&e.this)?;
33643
33644            if let Some(metrics) = &e.metrics {
33645                self.write_newline();
33646                self.write_indent();
33647                self.write_keyword("METRICS");
33648                self.write_space();
33649                self.generate_semantic_view_tuple(metrics)?;
33650            }
33651            if let Some(dimensions) = &e.dimensions {
33652                self.write_newline();
33653                self.write_indent();
33654                self.write_keyword("DIMENSIONS");
33655                self.write_space();
33656                self.generate_semantic_view_tuple(dimensions)?;
33657            }
33658            if let Some(facts) = &e.facts {
33659                self.write_newline();
33660                self.write_indent();
33661                self.write_keyword("FACTS");
33662                self.write_space();
33663                self.generate_semantic_view_tuple(facts)?;
33664            }
33665            if let Some(where_) = &e.where_ {
33666                self.write_newline();
33667                self.write_indent();
33668                self.write_keyword("WHERE");
33669                self.write_space();
33670                self.generate_expression(where_)?;
33671            }
33672            self.write_newline();
33673            self.indent_level -= 1;
33674            self.write_indent();
33675        } else {
33676            // Compact: all on one line
33677            self.generate_expression(&e.this)?;
33678            if let Some(metrics) = &e.metrics {
33679                self.write_space();
33680                self.write_keyword("METRICS");
33681                self.write_space();
33682                self.generate_semantic_view_tuple(metrics)?;
33683            }
33684            if let Some(dimensions) = &e.dimensions {
33685                self.write_space();
33686                self.write_keyword("DIMENSIONS");
33687                self.write_space();
33688                self.generate_semantic_view_tuple(dimensions)?;
33689            }
33690            if let Some(facts) = &e.facts {
33691                self.write_space();
33692                self.write_keyword("FACTS");
33693                self.write_space();
33694                self.generate_semantic_view_tuple(facts)?;
33695            }
33696            if let Some(where_) = &e.where_ {
33697                self.write_space();
33698                self.write_keyword("WHERE");
33699                self.write_space();
33700                self.generate_expression(where_)?;
33701            }
33702        }
33703        self.write(")");
33704        Ok(())
33705    }
33706
33707    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
33708    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
33709        if let Expression::Tuple(t) = expr {
33710            for (i, e) in t.expressions.iter().enumerate() {
33711                if i > 0 {
33712                    self.write(", ");
33713                }
33714                self.generate_expression(e)?;
33715            }
33716        } else {
33717            self.generate_expression(expr)?;
33718        }
33719        Ok(())
33720    }
33721
33722    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
33723        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
33724        if let Some(start) = &e.start {
33725            self.write_keyword("START WITH");
33726            self.write_space();
33727            self.generate_expression(start)?;
33728        }
33729        if let Some(increment) = &e.increment {
33730            self.write_space();
33731            self.write_keyword("INCREMENT BY");
33732            self.write_space();
33733            self.generate_expression(increment)?;
33734        }
33735        if let Some(minvalue) = &e.minvalue {
33736            self.write_space();
33737            self.write_keyword("MINVALUE");
33738            self.write_space();
33739            self.generate_expression(minvalue)?;
33740        }
33741        if let Some(maxvalue) = &e.maxvalue {
33742            self.write_space();
33743            self.write_keyword("MAXVALUE");
33744            self.write_space();
33745            self.generate_expression(maxvalue)?;
33746        }
33747        if let Some(cache) = &e.cache {
33748            self.write_space();
33749            self.write_keyword("CACHE");
33750            self.write_space();
33751            self.generate_expression(cache)?;
33752        }
33753        if let Some(owned) = &e.owned {
33754            self.write_space();
33755            self.write_keyword("OWNED BY");
33756            self.write_space();
33757            self.generate_expression(owned)?;
33758        }
33759        for opt in &e.options {
33760            self.write_space();
33761            self.generate_expression(opt)?;
33762        }
33763        Ok(())
33764    }
33765
33766    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
33767        // [WITH] SERDEPROPERTIES (expressions)
33768        if e.with_.is_some() {
33769            self.write_keyword("WITH");
33770            self.write_space();
33771        }
33772        self.write_keyword("SERDEPROPERTIES");
33773        self.write(" (");
33774        for (i, expr) in e.expressions.iter().enumerate() {
33775            if i > 0 {
33776                self.write(", ");
33777            }
33778            // Generate key=value without spaces around =
33779            match expr {
33780                Expression::Eq(eq) => {
33781                    self.generate_expression(&eq.left)?;
33782                    self.write("=");
33783                    self.generate_expression(&eq.right)?;
33784                }
33785                _ => self.generate_expression(expr)?,
33786            }
33787        }
33788        self.write(")");
33789        Ok(())
33790    }
33791
33792    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
33793        // @@[kind.]this
33794        self.write("@@");
33795        if let Some(kind) = &e.kind {
33796            self.write(kind);
33797            self.write(".");
33798        }
33799        self.generate_expression(&e.this)?;
33800        Ok(())
33801    }
33802
33803    fn generate_set(&mut self, e: &Set) -> Result<()> {
33804        // SET/UNSET [TAG] expressions
33805        if e.unset.is_some() {
33806            self.write_keyword("UNSET");
33807        } else {
33808            self.write_keyword("SET");
33809        }
33810        if e.tag.is_some() {
33811            self.write_space();
33812            self.write_keyword("TAG");
33813        }
33814        if !e.expressions.is_empty() {
33815            self.write_space();
33816            for (i, expr) in e.expressions.iter().enumerate() {
33817                if i > 0 {
33818                    self.write(", ");
33819                }
33820                self.generate_expression(expr)?;
33821            }
33822        }
33823        Ok(())
33824    }
33825
33826    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
33827        // SET this or SETCONFIG this
33828        self.write_keyword("SET");
33829        self.write_space();
33830        self.generate_expression(&e.this)?;
33831        Ok(())
33832    }
33833
33834    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
33835        // [kind] name = value
33836        if let Some(kind) = &e.kind {
33837            self.write_keyword(kind);
33838            self.write_space();
33839        }
33840        self.generate_expression(&e.name)?;
33841        self.write(" = ");
33842        self.generate_expression(&e.value)?;
33843        Ok(())
33844    }
33845
33846    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
33847        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
33848        if let Some(with_) = &e.with_ {
33849            self.generate_expression(with_)?;
33850            self.write_space();
33851        }
33852        self.generate_expression(&e.this)?;
33853        self.write_space();
33854        // kind should be UNION, INTERSECT, EXCEPT, etc.
33855        if let Some(kind) = &e.kind {
33856            self.write_keyword(kind);
33857        }
33858        if e.distinct {
33859            self.write_space();
33860            self.write_keyword("DISTINCT");
33861        } else {
33862            self.write_space();
33863            self.write_keyword("ALL");
33864        }
33865        if e.by_name.is_some() {
33866            self.write_space();
33867            self.write_keyword("BY NAME");
33868        }
33869        self.write_space();
33870        self.generate_expression(&e.expression)?;
33871        Ok(())
33872    }
33873
33874    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
33875        // SET or MULTISET
33876        if e.multi.is_some() {
33877            self.write_keyword("MULTISET");
33878        } else {
33879            self.write_keyword("SET");
33880        }
33881        Ok(())
33882    }
33883
33884    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
33885        // SETTINGS expressions
33886        self.write_keyword("SETTINGS");
33887        if self.config.pretty && e.expressions.len() > 1 {
33888            // Pretty print: each setting on its own line, indented
33889            self.indent_level += 1;
33890            for (i, expr) in e.expressions.iter().enumerate() {
33891                if i > 0 {
33892                    self.write(",");
33893                }
33894                self.write_newline();
33895                self.write_indent();
33896                self.generate_expression(expr)?;
33897            }
33898            self.indent_level -= 1;
33899        } else {
33900            self.write_space();
33901            for (i, expr) in e.expressions.iter().enumerate() {
33902                if i > 0 {
33903                    self.write(", ");
33904                }
33905                self.generate_expression(expr)?;
33906            }
33907        }
33908        Ok(())
33909    }
33910
33911    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
33912        // SHARING = this
33913        self.write_keyword("SHARING");
33914        if let Some(this) = &e.this {
33915            self.write(" = ");
33916            self.generate_expression(this)?;
33917        }
33918        Ok(())
33919    }
33920
33921    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
33922        // Python array slicing: begin:end:step
33923        if let Some(begin) = &e.this {
33924            self.generate_expression(begin)?;
33925        }
33926        self.write(":");
33927        if let Some(end) = &e.expression {
33928            self.generate_expression(end)?;
33929        }
33930        if let Some(step) = &e.step {
33931            self.write(":");
33932            self.generate_expression(step)?;
33933        }
33934        Ok(())
33935    }
33936
33937    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
33938        // SORT_ARRAY(this, asc)
33939        self.write_keyword("SORT_ARRAY");
33940        self.write("(");
33941        self.generate_expression(&e.this)?;
33942        if let Some(asc) = &e.asc {
33943            self.write(", ");
33944            self.generate_expression(asc)?;
33945        }
33946        self.write(")");
33947        Ok(())
33948    }
33949
33950    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
33951        // SORT BY expressions
33952        self.write_keyword("SORT BY");
33953        self.write_space();
33954        for (i, expr) in e.expressions.iter().enumerate() {
33955            if i > 0 {
33956                self.write(", ");
33957            }
33958            self.generate_ordered(expr)?;
33959        }
33960        Ok(())
33961    }
33962
33963    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
33964        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
33965        if e.compound.is_some() {
33966            self.write_keyword("COMPOUND");
33967            self.write_space();
33968        }
33969        self.write_keyword("SORTKEY");
33970        self.write("(");
33971        // If this is a Tuple, unwrap its contents to avoid double parentheses
33972        if let Expression::Tuple(t) = e.this.as_ref() {
33973            for (i, expr) in t.expressions.iter().enumerate() {
33974                if i > 0 {
33975                    self.write(", ");
33976                }
33977                self.generate_expression(expr)?;
33978            }
33979        } else {
33980            self.generate_expression(&e.this)?;
33981        }
33982        self.write(")");
33983        Ok(())
33984    }
33985
33986    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
33987        // SPLIT_PART(this, delimiter, part_index)
33988        self.write_keyword("SPLIT_PART");
33989        self.write("(");
33990        self.generate_expression(&e.this)?;
33991        if let Some(delimiter) = &e.delimiter {
33992            self.write(", ");
33993            self.generate_expression(delimiter)?;
33994        }
33995        if let Some(part_index) = &e.part_index {
33996            self.write(", ");
33997            self.generate_expression(part_index)?;
33998        }
33999        self.write(")");
34000        Ok(())
34001    }
34002
34003    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
34004        // READS SQL DATA or MODIFIES SQL DATA, etc.
34005        self.generate_expression(&e.this)?;
34006        Ok(())
34007    }
34008
34009    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
34010        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
34011        self.write_keyword("SQL SECURITY");
34012        self.write_space();
34013        self.generate_expression(&e.this)?;
34014        Ok(())
34015    }
34016
34017    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
34018        // ST_DISTANCE(this, expression, [use_spheroid])
34019        self.write_keyword("ST_DISTANCE");
34020        self.write("(");
34021        self.generate_expression(&e.this)?;
34022        self.write(", ");
34023        self.generate_expression(&e.expression)?;
34024        if let Some(use_spheroid) = &e.use_spheroid {
34025            self.write(", ");
34026            self.generate_expression(use_spheroid)?;
34027        }
34028        self.write(")");
34029        Ok(())
34030    }
34031
34032    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
34033        // ST_POINT(this, expression)
34034        self.write_keyword("ST_POINT");
34035        self.write("(");
34036        self.generate_expression(&e.this)?;
34037        self.write(", ");
34038        self.generate_expression(&e.expression)?;
34039        self.write(")");
34040        Ok(())
34041    }
34042
34043    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
34044        // IMMUTABLE, STABLE, VOLATILE
34045        self.generate_expression(&e.this)?;
34046        Ok(())
34047    }
34048
34049    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
34050        // STANDARD_HASH(this, [expression])
34051        self.write_keyword("STANDARD_HASH");
34052        self.write("(");
34053        self.generate_expression(&e.this)?;
34054        if let Some(expression) = &e.expression {
34055            self.write(", ");
34056            self.generate_expression(expression)?;
34057        }
34058        self.write(")");
34059        Ok(())
34060    }
34061
34062    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
34063        // STORED BY this
34064        self.write_keyword("STORED BY");
34065        self.write_space();
34066        self.generate_expression(&e.this)?;
34067        Ok(())
34068    }
34069
34070    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
34071        // STRPOS(this, substr) or STRPOS(this, substr, position)
34072        // Different dialects have different function names
34073        use crate::dialects::DialectType;
34074        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
34075            // Snowflake: CHARINDEX(substr, str[, position])
34076            self.write_keyword("CHARINDEX");
34077            self.write("(");
34078            if let Some(substr) = &e.substr {
34079                self.generate_expression(substr)?;
34080                self.write(", ");
34081            }
34082            self.generate_expression(&e.this)?;
34083            if let Some(position) = &e.position {
34084                self.write(", ");
34085                self.generate_expression(position)?;
34086            }
34087            self.write(")");
34088        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34089            self.write_keyword("POSITION");
34090            self.write("(");
34091            self.generate_expression(&e.this)?;
34092            if let Some(substr) = &e.substr {
34093                self.write(", ");
34094                self.generate_expression(substr)?;
34095            }
34096            if let Some(position) = &e.position {
34097                self.write(", ");
34098                self.generate_expression(position)?;
34099            }
34100            if let Some(occurrence) = &e.occurrence {
34101                self.write(", ");
34102                self.generate_expression(occurrence)?;
34103            }
34104            self.write(")");
34105        } else if matches!(
34106            self.config.dialect,
34107            Some(DialectType::SQLite)
34108                | Some(DialectType::Oracle)
34109                | Some(DialectType::BigQuery)
34110                | Some(DialectType::Teradata)
34111        ) {
34112            self.write_keyword("INSTR");
34113            self.write("(");
34114            self.generate_expression(&e.this)?;
34115            if let Some(substr) = &e.substr {
34116                self.write(", ");
34117                self.generate_expression(substr)?;
34118            }
34119            if let Some(position) = &e.position {
34120                self.write(", ");
34121                self.generate_expression(position)?;
34122            } else if e.occurrence.is_some() {
34123                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
34124                // Default start position is 1
34125                self.write(", 1");
34126            }
34127            if let Some(occurrence) = &e.occurrence {
34128                self.write(", ");
34129                self.generate_expression(occurrence)?;
34130            }
34131            self.write(")");
34132        } else if matches!(
34133            self.config.dialect,
34134            Some(DialectType::MySQL)
34135                | Some(DialectType::SingleStore)
34136                | Some(DialectType::Doris)
34137                | Some(DialectType::StarRocks)
34138                | Some(DialectType::Hive)
34139                | Some(DialectType::Spark)
34140                | Some(DialectType::Databricks)
34141        ) {
34142            // LOCATE(substr, str[, position]) - substr first
34143            self.write_keyword("LOCATE");
34144            self.write("(");
34145            if let Some(substr) = &e.substr {
34146                self.generate_expression(substr)?;
34147                self.write(", ");
34148            }
34149            self.generate_expression(&e.this)?;
34150            if let Some(position) = &e.position {
34151                self.write(", ");
34152                self.generate_expression(position)?;
34153            }
34154            self.write(")");
34155        } else if matches!(self.config.dialect, Some(DialectType::TSQL)) {
34156            // CHARINDEX(substr, str[, position])
34157            self.write_keyword("CHARINDEX");
34158            self.write("(");
34159            if let Some(substr) = &e.substr {
34160                self.generate_expression(substr)?;
34161                self.write(", ");
34162            }
34163            self.generate_expression(&e.this)?;
34164            if let Some(position) = &e.position {
34165                self.write(", ");
34166                self.generate_expression(position)?;
34167            }
34168            self.write(")");
34169        } else if matches!(
34170            self.config.dialect,
34171            Some(DialectType::PostgreSQL)
34172                | Some(DialectType::Materialize)
34173                | Some(DialectType::RisingWave)
34174                | Some(DialectType::Redshift)
34175        ) {
34176            // POSITION(substr IN str) syntax
34177            self.write_keyword("POSITION");
34178            self.write("(");
34179            if let Some(substr) = &e.substr {
34180                self.generate_expression(substr)?;
34181                self.write(" IN ");
34182            }
34183            self.generate_expression(&e.this)?;
34184            self.write(")");
34185        } else {
34186            self.write_keyword("STRPOS");
34187            self.write("(");
34188            self.generate_expression(&e.this)?;
34189            if let Some(substr) = &e.substr {
34190                self.write(", ");
34191                self.generate_expression(substr)?;
34192            }
34193            if let Some(position) = &e.position {
34194                self.write(", ");
34195                self.generate_expression(position)?;
34196            }
34197            if let Some(occurrence) = &e.occurrence {
34198                self.write(", ");
34199                self.generate_expression(occurrence)?;
34200            }
34201            self.write(")");
34202        }
34203        Ok(())
34204    }
34205
34206    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
34207        match self.config.dialect {
34208            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
34209                // TO_DATE(this, java_format)
34210                self.write_keyword("TO_DATE");
34211                self.write("(");
34212                self.generate_expression(&e.this)?;
34213                if let Some(format) = &e.format {
34214                    self.write(", '");
34215                    self.write(&Self::strftime_to_java_format(format));
34216                    self.write("'");
34217                }
34218                self.write(")");
34219            }
34220            Some(DialectType::DuckDB) => {
34221                // CAST(STRPTIME(this, format) AS DATE)
34222                self.write_keyword("CAST");
34223                self.write("(");
34224                self.write_keyword("STRPTIME");
34225                self.write("(");
34226                self.generate_expression(&e.this)?;
34227                if let Some(format) = &e.format {
34228                    self.write(", '");
34229                    self.write(format);
34230                    self.write("'");
34231                }
34232                self.write(")");
34233                self.write_keyword(" AS ");
34234                self.write_keyword("DATE");
34235                self.write(")");
34236            }
34237            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
34238                // TO_DATE(this, pg_format)
34239                self.write_keyword("TO_DATE");
34240                self.write("(");
34241                self.generate_expression(&e.this)?;
34242                if let Some(format) = &e.format {
34243                    self.write(", '");
34244                    self.write(&Self::strftime_to_postgres_format(format));
34245                    self.write("'");
34246                }
34247                self.write(")");
34248            }
34249            Some(DialectType::BigQuery) => {
34250                // PARSE_DATE(format, this) - note: format comes first for BigQuery
34251                self.write_keyword("PARSE_DATE");
34252                self.write("(");
34253                if let Some(format) = &e.format {
34254                    self.write("'");
34255                    self.write(format);
34256                    self.write("'");
34257                    self.write(", ");
34258                }
34259                self.generate_expression(&e.this)?;
34260                self.write(")");
34261            }
34262            Some(DialectType::Teradata) => {
34263                // CAST(this AS DATE FORMAT 'teradata_fmt')
34264                self.write_keyword("CAST");
34265                self.write("(");
34266                self.generate_expression(&e.this)?;
34267                self.write_keyword(" AS ");
34268                self.write_keyword("DATE");
34269                if let Some(format) = &e.format {
34270                    self.write_keyword(" FORMAT ");
34271                    self.write("'");
34272                    self.write(&Self::strftime_to_teradata_format(format));
34273                    self.write("'");
34274                }
34275                self.write(")");
34276            }
34277            _ => {
34278                // STR_TO_DATE(this, format) - MySQL default
34279                self.write_keyword("STR_TO_DATE");
34280                self.write("(");
34281                self.generate_expression(&e.this)?;
34282                if let Some(format) = &e.format {
34283                    self.write(", '");
34284                    self.write(format);
34285                    self.write("'");
34286                }
34287                self.write(")");
34288            }
34289        }
34290        Ok(())
34291    }
34292
34293    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
34294    fn strftime_to_teradata_format(fmt: &str) -> String {
34295        let mut result = String::with_capacity(fmt.len() * 2);
34296        let bytes = fmt.as_bytes();
34297        let len = bytes.len();
34298        let mut i = 0;
34299        while i < len {
34300            if bytes[i] == b'%' && i + 1 < len {
34301                let replacement = match bytes[i + 1] {
34302                    b'Y' => "YYYY",
34303                    b'y' => "YY",
34304                    b'm' => "MM",
34305                    b'B' => "MMMM",
34306                    b'b' => "MMM",
34307                    b'd' => "DD",
34308                    b'j' => "DDD",
34309                    b'H' => "HH",
34310                    b'M' => "MI",
34311                    b'S' => "SS",
34312                    b'f' => "SSSSSS",
34313                    b'A' => "EEEE",
34314                    b'a' => "EEE",
34315                    _ => {
34316                        result.push('%');
34317                        i += 1;
34318                        continue;
34319                    }
34320                };
34321                result.push_str(replacement);
34322                i += 2;
34323            } else {
34324                result.push(bytes[i] as char);
34325                i += 1;
34326            }
34327        }
34328        result
34329    }
34330
34331    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
34332    /// Public static version for use by other modules
34333    pub fn strftime_to_java_format_static(fmt: &str) -> String {
34334        Self::strftime_to_java_format(fmt)
34335    }
34336
34337    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
34338    fn strftime_to_java_format(fmt: &str) -> String {
34339        let mut result = String::with_capacity(fmt.len() * 2);
34340        let bytes = fmt.as_bytes();
34341        let len = bytes.len();
34342        let mut i = 0;
34343        while i < len {
34344            if bytes[i] == b'%' && i + 1 < len {
34345                // Check for non-padded variants (%-X)
34346                if bytes[i + 1] == b'-' && i + 2 < len {
34347                    let replacement = match bytes[i + 2] {
34348                        b'd' => "d",
34349                        b'm' => "M",
34350                        b'H' => "H",
34351                        b'M' => "m",
34352                        b'S' => "s",
34353                        _ => {
34354                            result.push('%');
34355                            i += 1;
34356                            continue;
34357                        }
34358                    };
34359                    result.push_str(replacement);
34360                    i += 3;
34361                } else {
34362                    let replacement = match bytes[i + 1] {
34363                        b'Y' => "yyyy",
34364                        b'y' => "yy",
34365                        b'm' => "MM",
34366                        b'B' => "MMMM",
34367                        b'b' => "MMM",
34368                        b'd' => "dd",
34369                        b'j' => "DDD",
34370                        b'H' => "HH",
34371                        b'M' => "mm",
34372                        b'S' => "ss",
34373                        b'f' => "SSSSSS",
34374                        b'A' => "EEEE",
34375                        b'a' => "EEE",
34376                        _ => {
34377                            result.push('%');
34378                            i += 1;
34379                            continue;
34380                        }
34381                    };
34382                    result.push_str(replacement);
34383                    i += 2;
34384                }
34385            } else {
34386                result.push(bytes[i] as char);
34387                i += 1;
34388            }
34389        }
34390        result
34391    }
34392
34393    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
34394    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
34395    fn strftime_to_tsql_format(fmt: &str) -> String {
34396        let mut result = String::with_capacity(fmt.len() * 2);
34397        let bytes = fmt.as_bytes();
34398        let len = bytes.len();
34399        let mut i = 0;
34400        while i < len {
34401            if bytes[i] == b'%' && i + 1 < len {
34402                // Check for non-padded variants (%-X)
34403                if bytes[i + 1] == b'-' && i + 2 < len {
34404                    let replacement = match bytes[i + 2] {
34405                        b'd' => "d",
34406                        b'm' => "M",
34407                        b'H' => "H",
34408                        b'M' => "m",
34409                        b'S' => "s",
34410                        _ => {
34411                            result.push('%');
34412                            i += 1;
34413                            continue;
34414                        }
34415                    };
34416                    result.push_str(replacement);
34417                    i += 3;
34418                } else {
34419                    let replacement = match bytes[i + 1] {
34420                        b'Y' => "yyyy",
34421                        b'y' => "yy",
34422                        b'm' => "MM",
34423                        b'B' => "MMMM",
34424                        b'b' => "MMM",
34425                        b'd' => "dd",
34426                        b'j' => "DDD",
34427                        b'H' => "HH",
34428                        b'M' => "mm",
34429                        b'S' => "ss",
34430                        b'f' => "ffffff",
34431                        b'A' => "dddd",
34432                        b'a' => "ddd",
34433                        _ => {
34434                            result.push('%');
34435                            i += 1;
34436                            continue;
34437                        }
34438                    };
34439                    result.push_str(replacement);
34440                    i += 2;
34441                }
34442            } else {
34443                result.push(bytes[i] as char);
34444                i += 1;
34445            }
34446        }
34447        result
34448    }
34449
34450    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
34451    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
34452    fn decompose_json_path(path: &str) -> Vec<String> {
34453        let mut parts = Vec::new();
34454        // Strip leading $ and optional .
34455        let path = if path.starts_with("$.") {
34456            &path[2..]
34457        } else if path.starts_with('$') {
34458            &path[1..]
34459        } else {
34460            path
34461        };
34462        if path.is_empty() {
34463            return parts;
34464        }
34465        let mut current = String::new();
34466        let chars: Vec<char> = path.chars().collect();
34467        let mut i = 0;
34468        while i < chars.len() {
34469            match chars[i] {
34470                '.' => {
34471                    if !current.is_empty() {
34472                        parts.push(current.clone());
34473                        current.clear();
34474                    }
34475                    i += 1;
34476                }
34477                '[' => {
34478                    if !current.is_empty() {
34479                        parts.push(current.clone());
34480                        current.clear();
34481                    }
34482                    i += 1;
34483                    // Read the content inside brackets
34484                    let mut bracket_content = String::new();
34485                    while i < chars.len() && chars[i] != ']' {
34486                        // Skip quotes inside brackets
34487                        if chars[i] == '"' || chars[i] == '\'' {
34488                            let quote = chars[i];
34489                            i += 1;
34490                            while i < chars.len() && chars[i] != quote {
34491                                bracket_content.push(chars[i]);
34492                                i += 1;
34493                            }
34494                            if i < chars.len() {
34495                                i += 1;
34496                            } // skip closing quote
34497                        } else {
34498                            bracket_content.push(chars[i]);
34499                            i += 1;
34500                        }
34501                    }
34502                    if i < chars.len() {
34503                        i += 1;
34504                    } // skip ]
34505                      // Skip wildcard [*] - don't add as a part
34506                    if bracket_content != "*" {
34507                        parts.push(bracket_content);
34508                    }
34509                }
34510                _ => {
34511                    current.push(chars[i]);
34512                    i += 1;
34513                }
34514            }
34515        }
34516        if !current.is_empty() {
34517            parts.push(current);
34518        }
34519        parts
34520    }
34521
34522    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
34523    fn strftime_to_postgres_format(fmt: &str) -> String {
34524        let mut result = String::with_capacity(fmt.len() * 2);
34525        let bytes = fmt.as_bytes();
34526        let len = bytes.len();
34527        let mut i = 0;
34528        while i < len {
34529            if bytes[i] == b'%' && i + 1 < len {
34530                // Check for non-padded variants (%-X)
34531                if bytes[i + 1] == b'-' && i + 2 < len {
34532                    let replacement = match bytes[i + 2] {
34533                        b'd' => "FMDD",
34534                        b'm' => "FMMM",
34535                        b'H' => "FMHH24",
34536                        b'M' => "FMMI",
34537                        b'S' => "FMSS",
34538                        _ => {
34539                            result.push('%');
34540                            i += 1;
34541                            continue;
34542                        }
34543                    };
34544                    result.push_str(replacement);
34545                    i += 3;
34546                } else {
34547                    let replacement = match bytes[i + 1] {
34548                        b'Y' => "YYYY",
34549                        b'y' => "YY",
34550                        b'm' => "MM",
34551                        b'B' => "Month",
34552                        b'b' => "Mon",
34553                        b'd' => "DD",
34554                        b'j' => "DDD",
34555                        b'H' => "HH24",
34556                        b'M' => "MI",
34557                        b'S' => "SS",
34558                        b'f' => "US",
34559                        b'A' => "Day",
34560                        b'a' => "Dy",
34561                        _ => {
34562                            result.push('%');
34563                            i += 1;
34564                            continue;
34565                        }
34566                    };
34567                    result.push_str(replacement);
34568                    i += 2;
34569                }
34570            } else {
34571                result.push(bytes[i] as char);
34572                i += 1;
34573            }
34574        }
34575        result
34576    }
34577
34578    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
34579    fn strftime_to_snowflake_format(fmt: &str) -> String {
34580        let mut result = String::with_capacity(fmt.len() * 2);
34581        let bytes = fmt.as_bytes();
34582        let len = bytes.len();
34583        let mut i = 0;
34584        while i < len {
34585            if bytes[i] == b'%' && i + 1 < len {
34586                // Check for non-padded variants (%-X)
34587                if bytes[i + 1] == b'-' && i + 2 < len {
34588                    let replacement = match bytes[i + 2] {
34589                        b'd' => "dd",
34590                        b'm' => "mm",
34591                        _ => {
34592                            result.push('%');
34593                            i += 1;
34594                            continue;
34595                        }
34596                    };
34597                    result.push_str(replacement);
34598                    i += 3;
34599                } else {
34600                    let replacement = match bytes[i + 1] {
34601                        b'Y' => "yyyy",
34602                        b'y' => "yy",
34603                        b'm' => "mm",
34604                        b'd' => "DD",
34605                        b'H' => "hh24",
34606                        b'M' => "mi",
34607                        b'S' => "ss",
34608                        b'f' => "ff",
34609                        _ => {
34610                            result.push('%');
34611                            i += 1;
34612                            continue;
34613                        }
34614                    };
34615                    result.push_str(replacement);
34616                    i += 2;
34617                }
34618            } else {
34619                result.push(bytes[i] as char);
34620                i += 1;
34621            }
34622        }
34623        result
34624    }
34625
34626    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
34627        // STR_TO_MAP(this, pair_delim, key_value_delim)
34628        self.write_keyword("STR_TO_MAP");
34629        self.write("(");
34630        self.generate_expression(&e.this)?;
34631        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
34632        let needs_defaults = matches!(
34633            self.config.dialect,
34634            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
34635        );
34636        if let Some(pair_delim) = &e.pair_delim {
34637            self.write(", ");
34638            self.generate_expression(pair_delim)?;
34639        } else if needs_defaults {
34640            self.write(", ','");
34641        }
34642        if let Some(key_value_delim) = &e.key_value_delim {
34643            self.write(", ");
34644            self.generate_expression(key_value_delim)?;
34645        } else if needs_defaults {
34646            self.write(", ':'");
34647        }
34648        self.write(")");
34649        Ok(())
34650    }
34651
34652    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
34653        // Detect format style: strftime (starts with %) vs Snowflake/Java
34654        let is_strftime = e.format.contains('%');
34655        // Helper: get strftime format from whatever style is stored
34656        let to_strftime = |f: &str| -> String {
34657            if is_strftime {
34658                f.to_string()
34659            } else {
34660                Self::snowflake_format_to_strftime(f)
34661            }
34662        };
34663        // Helper: get Java format
34664        let to_java = |f: &str| -> String {
34665            if is_strftime {
34666                Self::strftime_to_java_format(f)
34667            } else {
34668                Self::snowflake_format_to_spark(f)
34669            }
34670        };
34671        // Helper: get PG format
34672        let to_pg = |f: &str| -> String {
34673            if is_strftime {
34674                Self::strftime_to_postgres_format(f)
34675            } else {
34676                Self::convert_strptime_to_postgres_format(f)
34677            }
34678        };
34679
34680        match self.config.dialect {
34681            Some(DialectType::Exasol) => {
34682                self.write_keyword("TO_DATE");
34683                self.write("(");
34684                self.generate_expression(&e.this)?;
34685                self.write(", '");
34686                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
34687                self.write("'");
34688                self.write(")");
34689            }
34690            Some(DialectType::BigQuery) => {
34691                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
34692                let fmt = to_strftime(&e.format);
34693                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
34694                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
34695                self.write_keyword("PARSE_TIMESTAMP");
34696                self.write("('");
34697                self.write(&fmt);
34698                self.write("', ");
34699                self.generate_expression(&e.this)?;
34700                self.write(")");
34701            }
34702            Some(DialectType::Hive) => {
34703                // Hive: CAST(x AS TIMESTAMP) for simple date formats
34704                // Check both the raw format and the converted format (in case it's already Java)
34705                let java_fmt = to_java(&e.format);
34706                if java_fmt == "yyyy-MM-dd HH:mm:ss"
34707                    || java_fmt == "yyyy-MM-dd"
34708                    || e.format == "yyyy-MM-dd HH:mm:ss"
34709                    || e.format == "yyyy-MM-dd"
34710                {
34711                    self.write_keyword("CAST");
34712                    self.write("(");
34713                    self.generate_expression(&e.this)?;
34714                    self.write(" ");
34715                    self.write_keyword("AS TIMESTAMP");
34716                    self.write(")");
34717                } else {
34718                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
34719                    self.write_keyword("CAST");
34720                    self.write("(");
34721                    self.write_keyword("FROM_UNIXTIME");
34722                    self.write("(");
34723                    self.write_keyword("UNIX_TIMESTAMP");
34724                    self.write("(");
34725                    self.generate_expression(&e.this)?;
34726                    self.write(", '");
34727                    self.write(&java_fmt);
34728                    self.write("')");
34729                    self.write(") ");
34730                    self.write_keyword("AS TIMESTAMP");
34731                    self.write(")");
34732                }
34733            }
34734            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
34735                // Spark: TO_TIMESTAMP(value, java_format)
34736                let java_fmt = to_java(&e.format);
34737                self.write_keyword("TO_TIMESTAMP");
34738                self.write("(");
34739                self.generate_expression(&e.this)?;
34740                self.write(", '");
34741                self.write(&java_fmt);
34742                self.write("')");
34743            }
34744            Some(DialectType::MySQL) => {
34745                // MySQL: STR_TO_DATE(value, format)
34746                let mut fmt = to_strftime(&e.format);
34747                // MySQL uses %e for non-padded day, %T for %H:%M:%S
34748                fmt = fmt.replace("%-d", "%e");
34749                fmt = fmt.replace("%-m", "%c");
34750                fmt = fmt.replace("%H:%M:%S", "%T");
34751                self.write_keyword("STR_TO_DATE");
34752                self.write("(");
34753                self.generate_expression(&e.this)?;
34754                self.write(", '");
34755                self.write(&fmt);
34756                self.write("')");
34757            }
34758            Some(DialectType::Drill) => {
34759                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
34760                let java_fmt = to_java(&e.format);
34761                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
34762                let java_fmt = java_fmt.replace('T', "''T''");
34763                self.write_keyword("TO_TIMESTAMP");
34764                self.write("(");
34765                self.generate_expression(&e.this)?;
34766                self.write(", '");
34767                self.write(&java_fmt);
34768                self.write("')");
34769            }
34770            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
34771                // Presto: DATE_PARSE(value, strftime_format)
34772                let mut fmt = to_strftime(&e.format);
34773                // Presto uses %e for non-padded day, %T for %H:%M:%S
34774                fmt = fmt.replace("%-d", "%e");
34775                fmt = fmt.replace("%-m", "%c");
34776                fmt = fmt.replace("%H:%M:%S", "%T");
34777                self.write_keyword("DATE_PARSE");
34778                self.write("(");
34779                self.generate_expression(&e.this)?;
34780                self.write(", '");
34781                self.write(&fmt);
34782                self.write("')");
34783            }
34784            Some(DialectType::DuckDB) => {
34785                // DuckDB: STRPTIME(value, strftime_format)
34786                let fmt = to_strftime(&e.format);
34787                self.write_keyword("STRPTIME");
34788                self.write("(");
34789                self.generate_expression(&e.this)?;
34790                self.write(", '");
34791                self.write(&fmt);
34792                self.write("')");
34793            }
34794            Some(DialectType::PostgreSQL)
34795            | Some(DialectType::Redshift)
34796            | Some(DialectType::Materialize) => {
34797                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
34798                let pg_fmt = to_pg(&e.format);
34799                self.write_keyword("TO_TIMESTAMP");
34800                self.write("(");
34801                self.generate_expression(&e.this)?;
34802                self.write(", '");
34803                self.write(&pg_fmt);
34804                self.write("')");
34805            }
34806            Some(DialectType::Oracle) => {
34807                // Oracle: TO_TIMESTAMP(value, pg_format)
34808                let pg_fmt = to_pg(&e.format);
34809                self.write_keyword("TO_TIMESTAMP");
34810                self.write("(");
34811                self.generate_expression(&e.this)?;
34812                self.write(", '");
34813                self.write(&pg_fmt);
34814                self.write("')");
34815            }
34816            Some(DialectType::Snowflake) => {
34817                // Snowflake: TO_TIMESTAMP(value, format) - native format
34818                self.write_keyword("TO_TIMESTAMP");
34819                self.write("(");
34820                self.generate_expression(&e.this)?;
34821                self.write(", '");
34822                self.write(&e.format);
34823                self.write("')");
34824            }
34825            _ => {
34826                // Default: STR_TO_TIME(this, format)
34827                self.write_keyword("STR_TO_TIME");
34828                self.write("(");
34829                self.generate_expression(&e.this)?;
34830                self.write(", '");
34831                self.write(&e.format);
34832                self.write("'");
34833                self.write(")");
34834            }
34835        }
34836        Ok(())
34837    }
34838
34839    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
34840    fn snowflake_format_to_strftime(format: &str) -> String {
34841        let mut result = String::new();
34842        let chars: Vec<char> = format.chars().collect();
34843        let mut i = 0;
34844        while i < chars.len() {
34845            let remaining = &format[i..];
34846            if remaining.starts_with("yyyy") {
34847                result.push_str("%Y");
34848                i += 4;
34849            } else if remaining.starts_with("yy") {
34850                result.push_str("%y");
34851                i += 2;
34852            } else if remaining.starts_with("mmmm") {
34853                result.push_str("%B"); // full month name
34854                i += 4;
34855            } else if remaining.starts_with("mon") {
34856                result.push_str("%b"); // abbreviated month
34857                i += 3;
34858            } else if remaining.starts_with("mm") {
34859                result.push_str("%m");
34860                i += 2;
34861            } else if remaining.starts_with("DD") {
34862                result.push_str("%d");
34863                i += 2;
34864            } else if remaining.starts_with("dy") {
34865                result.push_str("%a"); // abbreviated day name
34866                i += 2;
34867            } else if remaining.starts_with("hh24") {
34868                result.push_str("%H");
34869                i += 4;
34870            } else if remaining.starts_with("hh12") {
34871                result.push_str("%I");
34872                i += 4;
34873            } else if remaining.starts_with("hh") {
34874                result.push_str("%H");
34875                i += 2;
34876            } else if remaining.starts_with("mi") {
34877                result.push_str("%M");
34878                i += 2;
34879            } else if remaining.starts_with("ss") {
34880                result.push_str("%S");
34881                i += 2;
34882            } else if remaining.starts_with("ff") {
34883                // Fractional seconds
34884                result.push_str("%f");
34885                i += 2;
34886                // Skip digits after ff (ff3, ff6, ff9)
34887                while i < chars.len() && chars[i].is_ascii_digit() {
34888                    i += 1;
34889                }
34890            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
34891                result.push_str("%p");
34892                i += 2;
34893            } else if remaining.starts_with("tz") {
34894                result.push_str("%Z");
34895                i += 2;
34896            } else {
34897                result.push(chars[i]);
34898                i += 1;
34899            }
34900        }
34901        result
34902    }
34903
34904    /// Convert Snowflake normalized format to Spark format (Java-style)
34905    fn snowflake_format_to_spark(format: &str) -> String {
34906        let mut result = String::new();
34907        let chars: Vec<char> = format.chars().collect();
34908        let mut i = 0;
34909        while i < chars.len() {
34910            let remaining = &format[i..];
34911            if remaining.starts_with("yyyy") {
34912                result.push_str("yyyy");
34913                i += 4;
34914            } else if remaining.starts_with("yy") {
34915                result.push_str("yy");
34916                i += 2;
34917            } else if remaining.starts_with("mmmm") {
34918                result.push_str("MMMM"); // full month name
34919                i += 4;
34920            } else if remaining.starts_with("mon") {
34921                result.push_str("MMM"); // abbreviated month
34922                i += 3;
34923            } else if remaining.starts_with("mm") {
34924                result.push_str("MM");
34925                i += 2;
34926            } else if remaining.starts_with("DD") {
34927                result.push_str("dd");
34928                i += 2;
34929            } else if remaining.starts_with("dy") {
34930                result.push_str("EEE"); // abbreviated day name
34931                i += 2;
34932            } else if remaining.starts_with("hh24") {
34933                result.push_str("HH");
34934                i += 4;
34935            } else if remaining.starts_with("hh12") {
34936                result.push_str("hh");
34937                i += 4;
34938            } else if remaining.starts_with("hh") {
34939                result.push_str("HH");
34940                i += 2;
34941            } else if remaining.starts_with("mi") {
34942                result.push_str("mm");
34943                i += 2;
34944            } else if remaining.starts_with("ss") {
34945                result.push_str("ss");
34946                i += 2;
34947            } else if remaining.starts_with("ff") {
34948                result.push_str("SSS"); // milliseconds
34949                i += 2;
34950                // Skip digits after ff
34951                while i < chars.len() && chars[i].is_ascii_digit() {
34952                    i += 1;
34953                }
34954            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
34955                result.push_str("a");
34956                i += 2;
34957            } else if remaining.starts_with("tz") {
34958                result.push_str("z");
34959                i += 2;
34960            } else {
34961                result.push(chars[i]);
34962                i += 1;
34963            }
34964        }
34965        result
34966    }
34967
34968    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
34969        match self.config.dialect {
34970            Some(DialectType::DuckDB) => {
34971                // DuckDB: EPOCH(STRPTIME(value, format))
34972                self.write_keyword("EPOCH");
34973                self.write("(");
34974                self.write_keyword("STRPTIME");
34975                self.write("(");
34976                if let Some(this) = &e.this {
34977                    self.generate_expression(this)?;
34978                }
34979                if let Some(format) = &e.format {
34980                    self.write(", '");
34981                    self.write(format);
34982                    self.write("'");
34983                }
34984                self.write("))");
34985            }
34986            Some(DialectType::Hive) => {
34987                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
34988                self.write_keyword("UNIX_TIMESTAMP");
34989                self.write("(");
34990                if let Some(this) = &e.this {
34991                    self.generate_expression(this)?;
34992                }
34993                if let Some(format) = &e.format {
34994                    let java_fmt = Self::strftime_to_java_format(format);
34995                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
34996                        self.write(", '");
34997                        self.write(&java_fmt);
34998                        self.write("'");
34999                    }
35000                }
35001                self.write(")");
35002            }
35003            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
35004                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
35005                self.write_keyword("UNIX_TIMESTAMP");
35006                self.write("(");
35007                if let Some(this) = &e.this {
35008                    self.generate_expression(this)?;
35009                }
35010                if let Some(format) = &e.format {
35011                    self.write(", '");
35012                    self.write(format);
35013                    self.write("'");
35014                }
35015                self.write(")");
35016            }
35017            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35018                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
35019                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
35020                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
35021                let java_fmt = Self::strftime_to_java_format(c_fmt);
35022                self.write_keyword("TO_UNIXTIME");
35023                self.write("(");
35024                self.write_keyword("COALESCE");
35025                self.write("(");
35026                self.write_keyword("TRY");
35027                self.write("(");
35028                self.write_keyword("DATE_PARSE");
35029                self.write("(");
35030                self.write_keyword("CAST");
35031                self.write("(");
35032                if let Some(this) = &e.this {
35033                    self.generate_expression(this)?;
35034                }
35035                self.write(" ");
35036                self.write_keyword("AS VARCHAR");
35037                self.write("), '");
35038                self.write(c_fmt);
35039                self.write("')), ");
35040                self.write_keyword("PARSE_DATETIME");
35041                self.write("(");
35042                self.write_keyword("DATE_FORMAT");
35043                self.write("(");
35044                self.write_keyword("CAST");
35045                self.write("(");
35046                if let Some(this) = &e.this {
35047                    self.generate_expression(this)?;
35048                }
35049                self.write(" ");
35050                self.write_keyword("AS TIMESTAMP");
35051                self.write("), '");
35052                self.write(c_fmt);
35053                self.write("'), '");
35054                self.write(&java_fmt);
35055                self.write("')))");
35056            }
35057            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35058                // Spark: UNIX_TIMESTAMP(value, java_format)
35059                self.write_keyword("UNIX_TIMESTAMP");
35060                self.write("(");
35061                if let Some(this) = &e.this {
35062                    self.generate_expression(this)?;
35063                }
35064                if let Some(format) = &e.format {
35065                    let java_fmt = Self::strftime_to_java_format(format);
35066                    self.write(", '");
35067                    self.write(&java_fmt);
35068                    self.write("'");
35069                }
35070                self.write(")");
35071            }
35072            _ => {
35073                // Default: STR_TO_UNIX(this, format)
35074                self.write_keyword("STR_TO_UNIX");
35075                self.write("(");
35076                if let Some(this) = &e.this {
35077                    self.generate_expression(this)?;
35078                }
35079                if let Some(format) = &e.format {
35080                    self.write(", '");
35081                    self.write(format);
35082                    self.write("'");
35083                }
35084                self.write(")");
35085            }
35086        }
35087        Ok(())
35088    }
35089
35090    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
35091        // STRING_TO_ARRAY(this, delimiter, null_string)
35092        self.write_keyword("STRING_TO_ARRAY");
35093        self.write("(");
35094        self.generate_expression(&e.this)?;
35095        if let Some(expression) = &e.expression {
35096            self.write(", ");
35097            self.generate_expression(expression)?;
35098        }
35099        if let Some(null_val) = &e.null {
35100            self.write(", ");
35101            self.generate_expression(null_val)?;
35102        }
35103        self.write(")");
35104        Ok(())
35105    }
35106
35107    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
35108        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
35109            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
35110            self.write_keyword("OBJECT_CONSTRUCT");
35111            self.write("(");
35112            for (i, (name, expr)) in e.fields.iter().enumerate() {
35113                if i > 0 {
35114                    self.write(", ");
35115                }
35116                if let Some(name) = name {
35117                    self.write("'");
35118                    self.write(name);
35119                    self.write("'");
35120                    self.write(", ");
35121                } else {
35122                    self.write("'_");
35123                    self.write(&i.to_string());
35124                    self.write("'");
35125                    self.write(", ");
35126                }
35127                self.generate_expression(expr)?;
35128            }
35129            self.write(")");
35130        } else if self.config.struct_curly_brace_notation {
35131            // DuckDB-style: {'key': value, ...}
35132            self.write("{");
35133            for (i, (name, expr)) in e.fields.iter().enumerate() {
35134                if i > 0 {
35135                    self.write(", ");
35136                }
35137                if let Some(name) = name {
35138                    // Quote the key as a string literal
35139                    self.write("'");
35140                    self.write(name);
35141                    self.write("'");
35142                    self.write(": ");
35143                } else {
35144                    // Unnamed field: use positional key
35145                    self.write("'_");
35146                    self.write(&i.to_string());
35147                    self.write("'");
35148                    self.write(": ");
35149                }
35150                self.generate_expression(expr)?;
35151            }
35152            self.write("}");
35153        } else {
35154            // Standard SQL struct notation
35155            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
35156            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
35157            let value_as_name = matches!(
35158                self.config.dialect,
35159                Some(DialectType::BigQuery)
35160                    | Some(DialectType::Spark)
35161                    | Some(DialectType::Databricks)
35162                    | Some(DialectType::Hive)
35163            );
35164            self.write_keyword("STRUCT");
35165            self.write("(");
35166            for (i, (name, expr)) in e.fields.iter().enumerate() {
35167                if i > 0 {
35168                    self.write(", ");
35169                }
35170                if let Some(name) = name {
35171                    if value_as_name {
35172                        // STRUCT(value AS name)
35173                        self.generate_expression(expr)?;
35174                        self.write_space();
35175                        self.write_keyword("AS");
35176                        self.write_space();
35177                        // Quote name if it contains spaces or special chars
35178                        let needs_quoting = name.contains(' ') || name.contains('-');
35179                        if needs_quoting {
35180                            if matches!(
35181                                self.config.dialect,
35182                                Some(DialectType::Spark)
35183                                    | Some(DialectType::Databricks)
35184                                    | Some(DialectType::Hive)
35185                            ) {
35186                                self.write("`");
35187                                self.write(name);
35188                                self.write("`");
35189                            } else {
35190                                self.write(name);
35191                            }
35192                        } else {
35193                            self.write(name);
35194                        }
35195                    } else {
35196                        // STRUCT(name AS value)
35197                        self.write(name);
35198                        self.write_space();
35199                        self.write_keyword("AS");
35200                        self.write_space();
35201                        self.generate_expression(expr)?;
35202                    }
35203                } else {
35204                    self.generate_expression(expr)?;
35205                }
35206            }
35207            self.write(")");
35208        }
35209        Ok(())
35210    }
35211
35212    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
35213        // STUFF(this, start, length, expression)
35214        self.write_keyword("STUFF");
35215        self.write("(");
35216        self.generate_expression(&e.this)?;
35217        if let Some(start) = &e.start {
35218            self.write(", ");
35219            self.generate_expression(start)?;
35220        }
35221        if let Some(length) = e.length {
35222            self.write(", ");
35223            self.write(&length.to_string());
35224        }
35225        self.write(", ");
35226        self.generate_expression(&e.expression)?;
35227        self.write(")");
35228        Ok(())
35229    }
35230
35231    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
35232        // SUBSTRING_INDEX(this, delimiter, count)
35233        self.write_keyword("SUBSTRING_INDEX");
35234        self.write("(");
35235        self.generate_expression(&e.this)?;
35236        if let Some(delimiter) = &e.delimiter {
35237            self.write(", ");
35238            self.generate_expression(delimiter)?;
35239        }
35240        if let Some(count) = &e.count {
35241            self.write(", ");
35242            self.generate_expression(count)?;
35243        }
35244        self.write(")");
35245        Ok(())
35246    }
35247
35248    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
35249        // SUMMARIZE [TABLE] this
35250        self.write_keyword("SUMMARIZE");
35251        if e.table.is_some() {
35252            self.write_space();
35253            self.write_keyword("TABLE");
35254        }
35255        self.write_space();
35256        self.generate_expression(&e.this)?;
35257        Ok(())
35258    }
35259
35260    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
35261        // SYSTIMESTAMP
35262        self.write_keyword("SYSTIMESTAMP");
35263        Ok(())
35264    }
35265
35266    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
35267        // alias (columns...)
35268        if let Some(this) = &e.this {
35269            self.generate_expression(this)?;
35270        }
35271        if !e.columns.is_empty() {
35272            self.write("(");
35273            for (i, col) in e.columns.iter().enumerate() {
35274                if i > 0 {
35275                    self.write(", ");
35276                }
35277                self.generate_expression(col)?;
35278            }
35279            self.write(")");
35280        }
35281        Ok(())
35282    }
35283
35284    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
35285        // TABLE(this) [AS alias]
35286        self.write_keyword("TABLE");
35287        self.write("(");
35288        self.generate_expression(&e.this)?;
35289        self.write(")");
35290        if let Some(alias) = &e.alias {
35291            self.write_space();
35292            self.write_keyword("AS");
35293            self.write_space();
35294            self.write(alias);
35295        }
35296        Ok(())
35297    }
35298
35299    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
35300        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
35301        self.write_keyword("ROWS FROM");
35302        self.write(" (");
35303        for (i, expr) in e.expressions.iter().enumerate() {
35304            if i > 0 {
35305                self.write(", ");
35306            }
35307            // Each expression is either:
35308            // - A plain function (no alias)
35309            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
35310            match expr {
35311                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
35312                    // First element is the function, second is the TableAlias
35313                    self.generate_expression(&tuple.expressions[0])?;
35314                    self.write_space();
35315                    self.write_keyword("AS");
35316                    self.write_space();
35317                    self.generate_expression(&tuple.expressions[1])?;
35318                }
35319                _ => {
35320                    self.generate_expression(expr)?;
35321                }
35322            }
35323        }
35324        self.write(")");
35325        if e.ordinality {
35326            self.write_space();
35327            self.write_keyword("WITH ORDINALITY");
35328        }
35329        if let Some(alias) = &e.alias {
35330            self.write_space();
35331            self.write_keyword("AS");
35332            self.write_space();
35333            self.generate_expression(alias)?;
35334        }
35335        Ok(())
35336    }
35337
35338    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
35339        use crate::dialects::DialectType;
35340
35341        // New wrapper pattern: expression + Sample struct
35342        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
35343            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
35344            if self.config.alias_post_tablesample {
35345                // Handle Subquery with alias and Alias wrapper
35346                if let Expression::Subquery(ref s) = **this {
35347                    if let Some(ref alias) = s.alias {
35348                        // Create a clone without alias for output
35349                        let mut subquery_no_alias = (**s).clone();
35350                        subquery_no_alias.alias = None;
35351                        subquery_no_alias.column_aliases = Vec::new();
35352                        self.generate_expression(&Expression::Subquery(Box::new(
35353                            subquery_no_alias,
35354                        )))?;
35355                        self.write_space();
35356                        self.write_keyword("TABLESAMPLE");
35357                        self.generate_sample_body(sample)?;
35358                        if let Some(ref seed) = sample.seed {
35359                            self.write_space();
35360                            let use_seed = sample.use_seed_keyword
35361                                && !matches!(
35362                                    self.config.dialect,
35363                                    Some(crate::dialects::DialectType::Databricks)
35364                                        | Some(crate::dialects::DialectType::Spark)
35365                                );
35366                            if use_seed {
35367                                self.write_keyword("SEED");
35368                            } else {
35369                                self.write_keyword("REPEATABLE");
35370                            }
35371                            self.write(" (");
35372                            self.generate_expression(seed)?;
35373                            self.write(")");
35374                        }
35375                        self.write_space();
35376                        self.write_keyword("AS");
35377                        self.write_space();
35378                        self.generate_identifier(alias)?;
35379                        return Ok(());
35380                    }
35381                } else if let Expression::Alias(ref a) = **this {
35382                    // Output the base expression without alias
35383                    self.generate_expression(&a.this)?;
35384                    self.write_space();
35385                    self.write_keyword("TABLESAMPLE");
35386                    self.generate_sample_body(sample)?;
35387                    if let Some(ref seed) = sample.seed {
35388                        self.write_space();
35389                        let use_seed = sample.use_seed_keyword
35390                            && !matches!(
35391                                self.config.dialect,
35392                                Some(crate::dialects::DialectType::Databricks)
35393                                    | Some(crate::dialects::DialectType::Spark)
35394                            );
35395                        if use_seed {
35396                            self.write_keyword("SEED");
35397                        } else {
35398                            self.write_keyword("REPEATABLE");
35399                        }
35400                        self.write(" (");
35401                        self.generate_expression(seed)?;
35402                        self.write(")");
35403                    }
35404                    // Output alias after TABLESAMPLE
35405                    self.write_space();
35406                    self.write_keyword("AS");
35407                    self.write_space();
35408                    self.generate_identifier(&a.alias)?;
35409                    return Ok(());
35410                }
35411            }
35412            // Default: generate wrapped expression first, then TABLESAMPLE
35413            self.generate_expression(this)?;
35414            self.write_space();
35415            self.write_keyword("TABLESAMPLE");
35416            self.generate_sample_body(sample)?;
35417            // Seed for table-level sample
35418            if let Some(ref seed) = sample.seed {
35419                self.write_space();
35420                // Databricks uses REPEATABLE, not SEED
35421                let use_seed = sample.use_seed_keyword
35422                    && !matches!(
35423                        self.config.dialect,
35424                        Some(crate::dialects::DialectType::Databricks)
35425                            | Some(crate::dialects::DialectType::Spark)
35426                    );
35427                if use_seed {
35428                    self.write_keyword("SEED");
35429                } else {
35430                    self.write_keyword("REPEATABLE");
35431                }
35432                self.write(" (");
35433                self.generate_expression(seed)?;
35434                self.write(")");
35435            }
35436            return Ok(());
35437        }
35438
35439        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
35440        self.write_keyword("TABLESAMPLE");
35441        if let Some(method) = &e.method {
35442            self.write_space();
35443            self.write_keyword(method);
35444        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
35445            // Snowflake defaults to BERNOULLI when no method is specified
35446            self.write_space();
35447            self.write_keyword("BERNOULLI");
35448        }
35449        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
35450            self.write_space();
35451            self.write_keyword("BUCKET");
35452            self.write_space();
35453            self.generate_expression(numerator)?;
35454            self.write_space();
35455            self.write_keyword("OUT OF");
35456            self.write_space();
35457            self.generate_expression(denominator)?;
35458            if let Some(field) = &e.bucket_field {
35459                self.write_space();
35460                self.write_keyword("ON");
35461                self.write_space();
35462                self.generate_expression(field)?;
35463            }
35464        } else if !e.expressions.is_empty() {
35465            self.write(" (");
35466            for (i, expr) in e.expressions.iter().enumerate() {
35467                if i > 0 {
35468                    self.write(", ");
35469                }
35470                self.generate_expression(expr)?;
35471            }
35472            self.write(")");
35473        } else if let Some(percent) = &e.percent {
35474            self.write(" (");
35475            self.generate_expression(percent)?;
35476            self.write_space();
35477            self.write_keyword("PERCENT");
35478            self.write(")");
35479        }
35480        Ok(())
35481    }
35482
35483    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
35484        // [prefix]this[postfix]
35485        if let Some(prefix) = &e.prefix {
35486            self.generate_expression(prefix)?;
35487        }
35488        if let Some(this) = &e.this {
35489            self.generate_expression(this)?;
35490        }
35491        if let Some(postfix) = &e.postfix {
35492            self.generate_expression(postfix)?;
35493        }
35494        Ok(())
35495    }
35496
35497    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
35498        // TAG (expressions)
35499        self.write_keyword("TAG");
35500        self.write(" (");
35501        for (i, expr) in e.expressions.iter().enumerate() {
35502            if i > 0 {
35503                self.write(", ");
35504            }
35505            self.generate_expression(expr)?;
35506        }
35507        self.write(")");
35508        Ok(())
35509    }
35510
35511    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
35512        // TEMPORARY or TEMP or [this] TEMPORARY
35513        if let Some(this) = &e.this {
35514            self.generate_expression(this)?;
35515            self.write_space();
35516        }
35517        self.write_keyword("TEMPORARY");
35518        Ok(())
35519    }
35520
35521    /// Generate a Time function expression
35522    /// For most dialects: TIME('value')
35523    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
35524        // Standard: TIME(value)
35525        self.write_keyword("TIME");
35526        self.write("(");
35527        self.generate_expression(&e.this)?;
35528        self.write(")");
35529        Ok(())
35530    }
35531
35532    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
35533        // TIME_ADD(this, expression, unit)
35534        self.write_keyword("TIME_ADD");
35535        self.write("(");
35536        self.generate_expression(&e.this)?;
35537        self.write(", ");
35538        self.generate_expression(&e.expression)?;
35539        if let Some(unit) = &e.unit {
35540            self.write(", ");
35541            self.write_keyword(unit);
35542        }
35543        self.write(")");
35544        Ok(())
35545    }
35546
35547    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
35548        // TIME_DIFF(this, expression, unit)
35549        self.write_keyword("TIME_DIFF");
35550        self.write("(");
35551        self.generate_expression(&e.this)?;
35552        self.write(", ");
35553        self.generate_expression(&e.expression)?;
35554        if let Some(unit) = &e.unit {
35555            self.write(", ");
35556            self.write_keyword(unit);
35557        }
35558        self.write(")");
35559        Ok(())
35560    }
35561
35562    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
35563        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
35564        self.write_keyword("TIME_FROM_PARTS");
35565        self.write("(");
35566        let mut first = true;
35567        if let Some(hour) = &e.hour {
35568            self.generate_expression(hour)?;
35569            first = false;
35570        }
35571        if let Some(minute) = &e.min {
35572            if !first {
35573                self.write(", ");
35574            }
35575            self.generate_expression(minute)?;
35576            first = false;
35577        }
35578        if let Some(second) = &e.sec {
35579            if !first {
35580                self.write(", ");
35581            }
35582            self.generate_expression(second)?;
35583            first = false;
35584        }
35585        if let Some(ns) = &e.nano {
35586            if !first {
35587                self.write(", ");
35588            }
35589            self.generate_expression(ns)?;
35590        }
35591        self.write(")");
35592        Ok(())
35593    }
35594
35595    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
35596        // TIME_SLICE(this, expression, unit)
35597        self.write_keyword("TIME_SLICE");
35598        self.write("(");
35599        self.generate_expression(&e.this)?;
35600        self.write(", ");
35601        self.generate_expression(&e.expression)?;
35602        self.write(", ");
35603        self.write_keyword(&e.unit);
35604        self.write(")");
35605        Ok(())
35606    }
35607
35608    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
35609        // TIME_STR_TO_TIME(this)
35610        self.write_keyword("TIME_STR_TO_TIME");
35611        self.write("(");
35612        self.generate_expression(&e.this)?;
35613        self.write(")");
35614        Ok(())
35615    }
35616
35617    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
35618        // TIME_SUB(this, expression, unit)
35619        self.write_keyword("TIME_SUB");
35620        self.write("(");
35621        self.generate_expression(&e.this)?;
35622        self.write(", ");
35623        self.generate_expression(&e.expression)?;
35624        if let Some(unit) = &e.unit {
35625            self.write(", ");
35626            self.write_keyword(unit);
35627        }
35628        self.write(")");
35629        Ok(())
35630    }
35631
35632    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
35633        match self.config.dialect {
35634            Some(DialectType::Exasol) => {
35635                // Exasol uses TO_CHAR with Exasol-specific format
35636                self.write_keyword("TO_CHAR");
35637                self.write("(");
35638                self.generate_expression(&e.this)?;
35639                self.write(", '");
35640                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
35641                self.write("'");
35642                self.write(")");
35643            }
35644            Some(DialectType::PostgreSQL)
35645            | Some(DialectType::Redshift)
35646            | Some(DialectType::Materialize) => {
35647                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
35648                self.write_keyword("TO_CHAR");
35649                self.write("(");
35650                self.generate_expression(&e.this)?;
35651                self.write(", '");
35652                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
35653                self.write("'");
35654                self.write(")");
35655            }
35656            Some(DialectType::Oracle) => {
35657                // Oracle uses TO_CHAR with PG-like format
35658                self.write_keyword("TO_CHAR");
35659                self.write("(");
35660                self.generate_expression(&e.this)?;
35661                self.write(", '");
35662                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
35663                self.write("'");
35664                self.write(")");
35665            }
35666            Some(DialectType::Drill) => {
35667                // Drill: TO_CHAR with Java format
35668                self.write_keyword("TO_CHAR");
35669                self.write("(");
35670                self.generate_expression(&e.this)?;
35671                self.write(", '");
35672                self.write(&Self::strftime_to_java_format(&e.format));
35673                self.write("'");
35674                self.write(")");
35675            }
35676            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
35677                // TSQL: FORMAT(value, format) with .NET-style format
35678                self.write_keyword("FORMAT");
35679                self.write("(");
35680                self.generate_expression(&e.this)?;
35681                self.write(", '");
35682                self.write(&Self::strftime_to_tsql_format(&e.format));
35683                self.write("'");
35684                self.write(")");
35685            }
35686            Some(DialectType::DuckDB) => {
35687                // DuckDB: STRFTIME(value, format) - keeps C format
35688                self.write_keyword("STRFTIME");
35689                self.write("(");
35690                self.generate_expression(&e.this)?;
35691                self.write(", '");
35692                self.write(&e.format);
35693                self.write("'");
35694                self.write(")");
35695            }
35696            Some(DialectType::BigQuery) => {
35697                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
35698                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
35699                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
35700                self.write_keyword("FORMAT_DATE");
35701                self.write("('");
35702                self.write(&fmt);
35703                self.write("', ");
35704                self.generate_expression(&e.this)?;
35705                self.write(")");
35706            }
35707            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35708                // Hive/Spark: DATE_FORMAT(value, java_format)
35709                self.write_keyword("DATE_FORMAT");
35710                self.write("(");
35711                self.generate_expression(&e.this)?;
35712                self.write(", '");
35713                self.write(&Self::strftime_to_java_format(&e.format));
35714                self.write("'");
35715                self.write(")");
35716            }
35717            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
35718                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
35719                self.write_keyword("DATE_FORMAT");
35720                self.write("(");
35721                self.generate_expression(&e.this)?;
35722                self.write(", '");
35723                self.write(&e.format);
35724                self.write("'");
35725                self.write(")");
35726            }
35727            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
35728                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
35729                self.write_keyword("DATE_FORMAT");
35730                self.write("(");
35731                self.generate_expression(&e.this)?;
35732                self.write(", '");
35733                self.write(&e.format);
35734                self.write("'");
35735                self.write(")");
35736            }
35737            _ => {
35738                // Default: TIME_TO_STR(this, format)
35739                self.write_keyword("TIME_TO_STR");
35740                self.write("(");
35741                self.generate_expression(&e.this)?;
35742                self.write(", '");
35743                self.write(&e.format);
35744                self.write("'");
35745                self.write(")");
35746            }
35747        }
35748        Ok(())
35749    }
35750
35751    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
35752        match self.config.dialect {
35753            Some(DialectType::DuckDB) => {
35754                // DuckDB: EPOCH(x)
35755                self.write_keyword("EPOCH");
35756                self.write("(");
35757                self.generate_expression(&e.this)?;
35758                self.write(")");
35759            }
35760            Some(DialectType::Hive)
35761            | Some(DialectType::Spark)
35762            | Some(DialectType::Databricks)
35763            | Some(DialectType::Doris)
35764            | Some(DialectType::StarRocks)
35765            | Some(DialectType::Drill) => {
35766                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
35767                self.write_keyword("UNIX_TIMESTAMP");
35768                self.write("(");
35769                self.generate_expression(&e.this)?;
35770                self.write(")");
35771            }
35772            Some(DialectType::Presto) | Some(DialectType::Trino) => {
35773                // Presto: TO_UNIXTIME(x)
35774                self.write_keyword("TO_UNIXTIME");
35775                self.write("(");
35776                self.generate_expression(&e.this)?;
35777                self.write(")");
35778            }
35779            _ => {
35780                // Default: TIME_TO_UNIX(x)
35781                self.write_keyword("TIME_TO_UNIX");
35782                self.write("(");
35783                self.generate_expression(&e.this)?;
35784                self.write(")");
35785            }
35786        }
35787        Ok(())
35788    }
35789
35790    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
35791        match self.config.dialect {
35792            Some(DialectType::Hive) => {
35793                // Hive: TO_DATE(x)
35794                self.write_keyword("TO_DATE");
35795                self.write("(");
35796                self.generate_expression(&e.this)?;
35797                self.write(")");
35798            }
35799            _ => {
35800                // Default: TIME_STR_TO_DATE(x)
35801                self.write_keyword("TIME_STR_TO_DATE");
35802                self.write("(");
35803                self.generate_expression(&e.this)?;
35804                self.write(")");
35805            }
35806        }
35807        Ok(())
35808    }
35809
35810    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
35811        // TIME_TRUNC(this, unit)
35812        self.write_keyword("TIME_TRUNC");
35813        self.write("(");
35814        self.generate_expression(&e.this)?;
35815        self.write(", ");
35816        self.write_keyword(&e.unit);
35817        self.write(")");
35818        Ok(())
35819    }
35820
35821    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
35822        // Just output the unit name
35823        if let Some(unit) = &e.unit {
35824            self.write_keyword(unit);
35825        }
35826        Ok(())
35827    }
35828
35829    /// Generate a Timestamp function expression
35830    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
35831    /// For other dialects: TIMESTAMP('value')
35832    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
35833        use crate::dialects::DialectType;
35834        use crate::expressions::Literal;
35835
35836        match self.config.dialect {
35837            // Exasol uses TO_TIMESTAMP for Timestamp expressions
35838            Some(DialectType::Exasol) => {
35839                self.write_keyword("TO_TIMESTAMP");
35840                self.write("(");
35841                // Extract the string value from the expression if it's a string literal
35842                if let Some(this) = &e.this {
35843                    match this.as_ref() {
35844                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
35845                            let Literal::String(s) = lit.as_ref() else {
35846                                unreachable!()
35847                            };
35848                            self.write("'");
35849                            self.write(s);
35850                            self.write("'");
35851                        }
35852                        _ => {
35853                            self.generate_expression(this)?;
35854                        }
35855                    }
35856                }
35857                self.write(")");
35858            }
35859            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
35860            _ => {
35861                self.write_keyword("TIMESTAMP");
35862                self.write("(");
35863                if let Some(this) = &e.this {
35864                    self.generate_expression(this)?;
35865                }
35866                if let Some(zone) = &e.zone {
35867                    self.write(", ");
35868                    self.generate_expression(zone)?;
35869                }
35870                self.write(")");
35871            }
35872        }
35873        Ok(())
35874    }
35875
35876    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
35877        // TIMESTAMP_ADD(this, expression, unit)
35878        self.write_keyword("TIMESTAMP_ADD");
35879        self.write("(");
35880        self.generate_expression(&e.this)?;
35881        self.write(", ");
35882        self.generate_expression(&e.expression)?;
35883        if let Some(unit) = &e.unit {
35884            self.write(", ");
35885            self.write_keyword(unit);
35886        }
35887        self.write(")");
35888        Ok(())
35889    }
35890
35891    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
35892        // TIMESTAMP_DIFF(this, expression, unit)
35893        self.write_keyword("TIMESTAMP_DIFF");
35894        self.write("(");
35895        self.generate_expression(&e.this)?;
35896        self.write(", ");
35897        self.generate_expression(&e.expression)?;
35898        if let Some(unit) = &e.unit {
35899            self.write(", ");
35900            self.write_keyword(unit);
35901        }
35902        self.write(")");
35903        Ok(())
35904    }
35905
35906    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
35907        // TIMESTAMP_FROM_PARTS(this, expression)
35908        self.write_keyword("TIMESTAMP_FROM_PARTS");
35909        self.write("(");
35910        if let Some(this) = &e.this {
35911            self.generate_expression(this)?;
35912        }
35913        if let Some(expression) = &e.expression {
35914            self.write(", ");
35915            self.generate_expression(expression)?;
35916        }
35917        if let Some(zone) = &e.zone {
35918            self.write(", ");
35919            self.generate_expression(zone)?;
35920        }
35921        if let Some(milli) = &e.milli {
35922            self.write(", ");
35923            self.generate_expression(milli)?;
35924        }
35925        self.write(")");
35926        Ok(())
35927    }
35928
35929    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
35930        // TIMESTAMP_SUB(this, INTERVAL expression unit)
35931        self.write_keyword("TIMESTAMP_SUB");
35932        self.write("(");
35933        self.generate_expression(&e.this)?;
35934        self.write(", ");
35935        self.write_keyword("INTERVAL");
35936        self.write_space();
35937        self.generate_expression(&e.expression)?;
35938        if let Some(unit) = &e.unit {
35939            self.write_space();
35940            self.write_keyword(unit);
35941        }
35942        self.write(")");
35943        Ok(())
35944    }
35945
35946    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
35947        // TIMESTAMP_TZ_FROM_PARTS(...)
35948        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
35949        self.write("(");
35950        if let Some(zone) = &e.zone {
35951            self.generate_expression(zone)?;
35952        }
35953        self.write(")");
35954        Ok(())
35955    }
35956
35957    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
35958        // TO_BINARY(this, [format])
35959        self.write_keyword("TO_BINARY");
35960        self.write("(");
35961        self.generate_expression(&e.this)?;
35962        if let Some(format) = &e.format {
35963            self.write(", '");
35964            self.write(format);
35965            self.write("'");
35966        }
35967        self.write(")");
35968        Ok(())
35969    }
35970
35971    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
35972        // TO_BOOLEAN(this)
35973        self.write_keyword("TO_BOOLEAN");
35974        self.write("(");
35975        self.generate_expression(&e.this)?;
35976        self.write(")");
35977        Ok(())
35978    }
35979
35980    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
35981        // TO_CHAR(this, [format], [nlsparam])
35982        self.write_keyword("TO_CHAR");
35983        self.write("(");
35984        self.generate_expression(&e.this)?;
35985        if let Some(format) = &e.format {
35986            self.write(", '");
35987            self.write(format);
35988            self.write("'");
35989        }
35990        if let Some(nlsparam) = &e.nlsparam {
35991            self.write(", ");
35992            self.generate_expression(nlsparam)?;
35993        }
35994        self.write(")");
35995        Ok(())
35996    }
35997
35998    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
35999        // TO_DECFLOAT(this, [format])
36000        self.write_keyword("TO_DECFLOAT");
36001        self.write("(");
36002        self.generate_expression(&e.this)?;
36003        if let Some(format) = &e.format {
36004            self.write(", '");
36005            self.write(format);
36006            self.write("'");
36007        }
36008        self.write(")");
36009        Ok(())
36010    }
36011
36012    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
36013        // TO_DOUBLE(this, [format])
36014        self.write_keyword("TO_DOUBLE");
36015        self.write("(");
36016        self.generate_expression(&e.this)?;
36017        if let Some(format) = &e.format {
36018            self.write(", '");
36019            self.write(format);
36020            self.write("'");
36021        }
36022        self.write(")");
36023        Ok(())
36024    }
36025
36026    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
36027        // TO_FILE(this, path)
36028        self.write_keyword("TO_FILE");
36029        self.write("(");
36030        self.generate_expression(&e.this)?;
36031        if let Some(path) = &e.path {
36032            self.write(", ");
36033            self.generate_expression(path)?;
36034        }
36035        self.write(")");
36036        Ok(())
36037    }
36038
36039    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
36040        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
36041        // If safe flag is set, output TRY_TO_NUMBER
36042        let is_safe = e.safe.is_some();
36043        if is_safe {
36044            self.write_keyword("TRY_TO_NUMBER");
36045        } else {
36046            self.write_keyword("TO_NUMBER");
36047        }
36048        self.write("(");
36049        self.generate_expression(&e.this)?;
36050        if let Some(format) = &e.format {
36051            self.write(", ");
36052            self.generate_expression(format)?;
36053        }
36054        if let Some(nlsparam) = &e.nlsparam {
36055            self.write(", ");
36056            self.generate_expression(nlsparam)?;
36057        }
36058        if let Some(precision) = &e.precision {
36059            self.write(", ");
36060            self.generate_expression(precision)?;
36061        }
36062        if let Some(scale) = &e.scale {
36063            self.write(", ");
36064            self.generate_expression(scale)?;
36065        }
36066        self.write(")");
36067        Ok(())
36068    }
36069
36070    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
36071        // TO_TABLE this
36072        self.write_keyword("TO_TABLE");
36073        self.write_space();
36074        self.generate_expression(&e.this)?;
36075        Ok(())
36076    }
36077
36078    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
36079        // Check mark to determine the format
36080        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
36081            Expression::Identifier(id) => id.name.clone(),
36082            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
36083                let Literal::String(s) = lit.as_ref() else {
36084                    unreachable!()
36085                };
36086                s.clone()
36087            }
36088            _ => String::new(),
36089        });
36090
36091        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
36092        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
36093        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
36094            matches!(m.as_ref(), Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
36095        });
36096
36097        // For Presto/Trino: always use START TRANSACTION
36098        let use_start_transaction = matches!(
36099            self.config.dialect,
36100            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
36101        );
36102        // For most dialects: strip TRANSACTION keyword
36103        let strip_transaction = matches!(
36104            self.config.dialect,
36105            Some(DialectType::Snowflake)
36106                | Some(DialectType::PostgreSQL)
36107                | Some(DialectType::Redshift)
36108                | Some(DialectType::MySQL)
36109                | Some(DialectType::Hive)
36110                | Some(DialectType::Spark)
36111                | Some(DialectType::Databricks)
36112                | Some(DialectType::DuckDB)
36113                | Some(DialectType::Oracle)
36114                | Some(DialectType::Doris)
36115                | Some(DialectType::StarRocks)
36116                | Some(DialectType::Materialize)
36117                | Some(DialectType::ClickHouse)
36118        );
36119
36120        if is_start || use_start_transaction {
36121            // START TRANSACTION [modes]
36122            self.write_keyword("START TRANSACTION");
36123            if let Some(modes) = &e.modes {
36124                self.write_space();
36125                self.generate_expression(modes)?;
36126            }
36127        } else {
36128            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
36129            self.write_keyword("BEGIN");
36130
36131            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
36132            let is_kind = e.this.as_ref().map_or(false, |t| {
36133                if let Expression::Identifier(id) = t.as_ref() {
36134                    id.name.eq_ignore_ascii_case("DEFERRED")
36135                        || id.name.eq_ignore_ascii_case("IMMEDIATE")
36136                        || id.name.eq_ignore_ascii_case("EXCLUSIVE")
36137                } else {
36138                    false
36139                }
36140            });
36141
36142            // Output kind before TRANSACTION keyword
36143            if is_kind {
36144                if let Some(this) = &e.this {
36145                    self.write_space();
36146                    if let Expression::Identifier(id) = this.as_ref() {
36147                        self.write_keyword(&id.name);
36148                    }
36149                }
36150            }
36151
36152            // Output TRANSACTION keyword if it was present and target supports it
36153            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
36154                self.write_space();
36155                self.write_keyword("TRANSACTION");
36156            }
36157
36158            // Output transaction name (not kind)
36159            if !is_kind {
36160                if let Some(this) = &e.this {
36161                    self.write_space();
36162                    self.generate_expression(this)?;
36163                }
36164            }
36165
36166            // Output WITH MARK 'description' for TSQL
36167            if has_with_mark {
36168                self.write_space();
36169                self.write_keyword("WITH MARK");
36170                if let Some(Expression::Literal(lit)) = e.mark.as_deref() {
36171                    if let Literal::String(desc) = lit.as_ref() {
36172                        if !desc.is_empty() {
36173                            self.write_space();
36174                            self.write(&format!("'{}'", desc));
36175                        }
36176                    }
36177                }
36178            }
36179
36180            // Output modes (isolation levels, etc.)
36181            if let Some(modes) = &e.modes {
36182                self.write_space();
36183                self.generate_expression(modes)?;
36184            }
36185        }
36186        Ok(())
36187    }
36188
36189    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
36190        // TRANSFORM(this, expression)
36191        self.write_keyword("TRANSFORM");
36192        self.write("(");
36193        self.generate_expression(&e.this)?;
36194        self.write(", ");
36195        self.generate_expression(&e.expression)?;
36196        self.write(")");
36197        Ok(())
36198    }
36199
36200    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
36201        // TRANSFORM(expressions)
36202        self.write_keyword("TRANSFORM");
36203        self.write("(");
36204        if self.config.pretty && !e.expressions.is_empty() {
36205            self.indent_level += 1;
36206            for (i, expr) in e.expressions.iter().enumerate() {
36207                if i > 0 {
36208                    self.write(",");
36209                }
36210                self.write_newline();
36211                self.write_indent();
36212                self.generate_expression(expr)?;
36213            }
36214            self.indent_level -= 1;
36215            self.write_newline();
36216            self.write(")");
36217        } else {
36218            for (i, expr) in e.expressions.iter().enumerate() {
36219                if i > 0 {
36220                    self.write(", ");
36221                }
36222                self.generate_expression(expr)?;
36223            }
36224            self.write(")");
36225        }
36226        Ok(())
36227    }
36228
36229    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
36230        use crate::dialects::DialectType;
36231        // TRANSIENT is Snowflake-specific; skip for other dialects
36232        if let Some(this) = &e.this {
36233            self.generate_expression(this)?;
36234            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
36235                self.write_space();
36236            }
36237        }
36238        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
36239            self.write_keyword("TRANSIENT");
36240        }
36241        Ok(())
36242    }
36243
36244    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
36245        // TRANSLATE(this, from_, to)
36246        self.write_keyword("TRANSLATE");
36247        self.write("(");
36248        self.generate_expression(&e.this)?;
36249        if let Some(from) = &e.from_ {
36250            self.write(", ");
36251            self.generate_expression(from)?;
36252        }
36253        if let Some(to) = &e.to {
36254            self.write(", ");
36255            self.generate_expression(to)?;
36256        }
36257        self.write(")");
36258        Ok(())
36259    }
36260
36261    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
36262        // TRANSLATE(this USING expression)
36263        self.write_keyword("TRANSLATE");
36264        self.write("(");
36265        self.generate_expression(&e.this)?;
36266        self.write_space();
36267        self.write_keyword("USING");
36268        self.write_space();
36269        self.generate_expression(&e.expression)?;
36270        if e.with_error.is_some() {
36271            self.write_space();
36272            self.write_keyword("WITH ERROR");
36273        }
36274        self.write(")");
36275        Ok(())
36276    }
36277
36278    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
36279        // TRUNCATE TABLE table1, table2, ...
36280        self.write_keyword("TRUNCATE TABLE");
36281        self.write_space();
36282        for (i, expr) in e.expressions.iter().enumerate() {
36283            if i > 0 {
36284                self.write(", ");
36285            }
36286            self.generate_expression(expr)?;
36287        }
36288        Ok(())
36289    }
36290
36291    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
36292        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
36293        self.write_keyword("TRY_BASE64_DECODE_BINARY");
36294        self.write("(");
36295        self.generate_expression(&e.this)?;
36296        if let Some(alphabet) = &e.alphabet {
36297            self.write(", ");
36298            self.generate_expression(alphabet)?;
36299        }
36300        self.write(")");
36301        Ok(())
36302    }
36303
36304    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
36305        // TRY_BASE64_DECODE_STRING(this, [alphabet])
36306        self.write_keyword("TRY_BASE64_DECODE_STRING");
36307        self.write("(");
36308        self.generate_expression(&e.this)?;
36309        if let Some(alphabet) = &e.alphabet {
36310            self.write(", ");
36311            self.generate_expression(alphabet)?;
36312        }
36313        self.write(")");
36314        Ok(())
36315    }
36316
36317    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
36318        // TRY_TO_DECFLOAT(this, [format])
36319        self.write_keyword("TRY_TO_DECFLOAT");
36320        self.write("(");
36321        self.generate_expression(&e.this)?;
36322        if let Some(format) = &e.format {
36323            self.write(", '");
36324            self.write(format);
36325            self.write("'");
36326        }
36327        self.write(")");
36328        Ok(())
36329    }
36330
36331    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
36332        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
36333        self.write_keyword("TS_OR_DS_ADD");
36334        self.write("(");
36335        self.generate_expression(&e.this)?;
36336        self.write(", ");
36337        self.generate_expression(&e.expression)?;
36338        if let Some(unit) = &e.unit {
36339            self.write(", ");
36340            self.write_keyword(unit);
36341        }
36342        if let Some(return_type) = &e.return_type {
36343            self.write(", ");
36344            self.generate_expression(return_type)?;
36345        }
36346        self.write(")");
36347        Ok(())
36348    }
36349
36350    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
36351        // TS_OR_DS_DIFF(this, expression, [unit])
36352        self.write_keyword("TS_OR_DS_DIFF");
36353        self.write("(");
36354        self.generate_expression(&e.this)?;
36355        self.write(", ");
36356        self.generate_expression(&e.expression)?;
36357        if let Some(unit) = &e.unit {
36358            self.write(", ");
36359            self.write_keyword(unit);
36360        }
36361        self.write(")");
36362        Ok(())
36363    }
36364
36365    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
36366        let default_time_format = "%Y-%m-%d %H:%M:%S";
36367        let default_date_format = "%Y-%m-%d";
36368        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
36369            f != default_time_format && f != default_date_format
36370        });
36371
36372        if has_non_default_format {
36373            // With non-default format: dialect-specific handling
36374            let fmt = e.format.as_ref().unwrap();
36375            match self.config.dialect {
36376                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
36377                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
36378                    // STR_TO_DATE is the MySQL-native form of StrToTime
36379                    let str_to_time = crate::expressions::StrToTime {
36380                        this: Box::new((*e.this).clone()),
36381                        format: fmt.clone(),
36382                        zone: None,
36383                        safe: None,
36384                        target_type: None,
36385                    };
36386                    self.generate_str_to_time(&str_to_time)?;
36387                }
36388                Some(DialectType::Hive)
36389                | Some(DialectType::Spark)
36390                | Some(DialectType::Databricks) => {
36391                    // Hive/Spark: TO_DATE(x, java_fmt)
36392                    self.write_keyword("TO_DATE");
36393                    self.write("(");
36394                    self.generate_expression(&e.this)?;
36395                    self.write(", '");
36396                    self.write(&Self::strftime_to_java_format(fmt));
36397                    self.write("')");
36398                }
36399                Some(DialectType::Snowflake) => {
36400                    // Snowflake: TO_DATE(x, snowflake_fmt)
36401                    self.write_keyword("TO_DATE");
36402                    self.write("(");
36403                    self.generate_expression(&e.this)?;
36404                    self.write(", '");
36405                    self.write(&Self::strftime_to_snowflake_format(fmt));
36406                    self.write("')");
36407                }
36408                Some(DialectType::Doris) => {
36409                    // Doris: TO_DATE(x) - ignores format
36410                    self.write_keyword("TO_DATE");
36411                    self.write("(");
36412                    self.generate_expression(&e.this)?;
36413                    self.write(")");
36414                }
36415                _ => {
36416                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
36417                    self.write_keyword("CAST");
36418                    self.write("(");
36419                    let str_to_time = crate::expressions::StrToTime {
36420                        this: Box::new((*e.this).clone()),
36421                        format: fmt.clone(),
36422                        zone: None,
36423                        safe: None,
36424                        target_type: None,
36425                    };
36426                    self.generate_str_to_time(&str_to_time)?;
36427                    self.write_keyword(" AS ");
36428                    self.write_keyword("DATE");
36429                    self.write(")");
36430                }
36431            }
36432        } else {
36433            // Without format (or default format): simple date conversion
36434            match self.config.dialect {
36435                Some(DialectType::MySQL)
36436                | Some(DialectType::SQLite)
36437                | Some(DialectType::StarRocks) => {
36438                    // MySQL/SQLite/StarRocks: DATE(x)
36439                    self.write_keyword("DATE");
36440                    self.write("(");
36441                    self.generate_expression(&e.this)?;
36442                    self.write(")");
36443                }
36444                Some(DialectType::Hive)
36445                | Some(DialectType::Spark)
36446                | Some(DialectType::Databricks)
36447                | Some(DialectType::Snowflake)
36448                | Some(DialectType::Doris) => {
36449                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
36450                    self.write_keyword("TO_DATE");
36451                    self.write("(");
36452                    self.generate_expression(&e.this)?;
36453                    self.write(")");
36454                }
36455                Some(DialectType::Presto)
36456                | Some(DialectType::Trino)
36457                | Some(DialectType::Athena) => {
36458                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
36459                    self.write_keyword("CAST");
36460                    self.write("(");
36461                    self.write_keyword("CAST");
36462                    self.write("(");
36463                    self.generate_expression(&e.this)?;
36464                    self.write_keyword(" AS ");
36465                    self.write_keyword("TIMESTAMP");
36466                    self.write(")");
36467                    self.write_keyword(" AS ");
36468                    self.write_keyword("DATE");
36469                    self.write(")");
36470                }
36471                Some(DialectType::ClickHouse) => {
36472                    // ClickHouse: CAST(x AS Nullable(DATE))
36473                    self.write_keyword("CAST");
36474                    self.write("(");
36475                    self.generate_expression(&e.this)?;
36476                    self.write_keyword(" AS ");
36477                    self.write("Nullable(DATE)");
36478                    self.write(")");
36479                }
36480                _ => {
36481                    // Default: CAST(x AS DATE)
36482                    self.write_keyword("CAST");
36483                    self.write("(");
36484                    self.generate_expression(&e.this)?;
36485                    self.write_keyword(" AS ");
36486                    self.write_keyword("DATE");
36487                    self.write(")");
36488                }
36489            }
36490        }
36491        Ok(())
36492    }
36493
36494    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
36495        // TS_OR_DS_TO_TIME(this, [format])
36496        self.write_keyword("TS_OR_DS_TO_TIME");
36497        self.write("(");
36498        self.generate_expression(&e.this)?;
36499        if let Some(format) = &e.format {
36500            self.write(", '");
36501            self.write(format);
36502            self.write("'");
36503        }
36504        self.write(")");
36505        Ok(())
36506    }
36507
36508    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
36509        // UNHEX(this, [expression])
36510        self.write_keyword("UNHEX");
36511        self.write("(");
36512        self.generate_expression(&e.this)?;
36513        if let Some(expression) = &e.expression {
36514            self.write(", ");
36515            self.generate_expression(expression)?;
36516        }
36517        self.write(")");
36518        Ok(())
36519    }
36520
36521    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
36522        // U&this [UESCAPE escape]
36523        self.write("U&");
36524        self.generate_expression(&e.this)?;
36525        if let Some(escape) = &e.escape {
36526            self.write_space();
36527            self.write_keyword("UESCAPE");
36528            self.write_space();
36529            self.generate_expression(escape)?;
36530        }
36531        Ok(())
36532    }
36533
36534    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
36535        // UNIFORM(this, expression, [gen], [seed])
36536        self.write_keyword("UNIFORM");
36537        self.write("(");
36538        self.generate_expression(&e.this)?;
36539        self.write(", ");
36540        self.generate_expression(&e.expression)?;
36541        if let Some(gen) = &e.gen {
36542            self.write(", ");
36543            self.generate_expression(gen)?;
36544        }
36545        if let Some(seed) = &e.seed {
36546            self.write(", ");
36547            self.generate_expression(seed)?;
36548        }
36549        self.write(")");
36550        Ok(())
36551    }
36552
36553    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
36554        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
36555        self.write_keyword("UNIQUE");
36556        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
36557        if e.nulls.is_some() {
36558            self.write(" NULLS NOT DISTINCT");
36559        }
36560        if let Some(this) = &e.this {
36561            self.write_space();
36562            self.generate_expression(this)?;
36563        }
36564        if let Some(index_type) = &e.index_type {
36565            self.write(" USING ");
36566            self.generate_expression(index_type)?;
36567        }
36568        if let Some(on_conflict) = &e.on_conflict {
36569            self.write_space();
36570            self.generate_expression(on_conflict)?;
36571        }
36572        for opt in &e.options {
36573            self.write_space();
36574            self.generate_expression(opt)?;
36575        }
36576        Ok(())
36577    }
36578
36579    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
36580        // UNIQUE KEY (expressions)
36581        self.write_keyword("UNIQUE KEY");
36582        self.write(" (");
36583        for (i, expr) in e.expressions.iter().enumerate() {
36584            if i > 0 {
36585                self.write(", ");
36586            }
36587            self.generate_expression(expr)?;
36588        }
36589        self.write(")");
36590        Ok(())
36591    }
36592
36593    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
36594        // ROLLUP (r1(col1, col2), r2(col1))
36595        self.write_keyword("ROLLUP");
36596        self.write(" (");
36597        for (i, index) in e.expressions.iter().enumerate() {
36598            if i > 0 {
36599                self.write(", ");
36600            }
36601            self.generate_identifier(&index.name)?;
36602            self.write("(");
36603            for (j, col) in index.expressions.iter().enumerate() {
36604                if j > 0 {
36605                    self.write(", ");
36606                }
36607                self.generate_identifier(col)?;
36608            }
36609            self.write(")");
36610        }
36611        self.write(")");
36612        Ok(())
36613    }
36614
36615    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
36616        match self.config.dialect {
36617            Some(DialectType::DuckDB) => {
36618                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
36619                self.write_keyword("STRFTIME");
36620                self.write("(");
36621                self.write_keyword("TO_TIMESTAMP");
36622                self.write("(");
36623                self.generate_expression(&e.this)?;
36624                self.write("), '");
36625                if let Some(format) = &e.format {
36626                    self.write(format);
36627                }
36628                self.write("')");
36629            }
36630            Some(DialectType::Hive) => {
36631                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
36632                self.write_keyword("FROM_UNIXTIME");
36633                self.write("(");
36634                self.generate_expression(&e.this)?;
36635                if let Some(format) = &e.format {
36636                    if format != "yyyy-MM-dd HH:mm:ss" {
36637                        self.write(", '");
36638                        self.write(format);
36639                        self.write("'");
36640                    }
36641                }
36642                self.write(")");
36643            }
36644            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36645                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
36646                self.write_keyword("DATE_FORMAT");
36647                self.write("(");
36648                self.write_keyword("FROM_UNIXTIME");
36649                self.write("(");
36650                self.generate_expression(&e.this)?;
36651                self.write("), '");
36652                if let Some(format) = &e.format {
36653                    self.write(format);
36654                }
36655                self.write("')");
36656            }
36657            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
36658                // Spark: FROM_UNIXTIME(value, format)
36659                self.write_keyword("FROM_UNIXTIME");
36660                self.write("(");
36661                self.generate_expression(&e.this)?;
36662                if let Some(format) = &e.format {
36663                    self.write(", '");
36664                    self.write(format);
36665                    self.write("'");
36666                }
36667                self.write(")");
36668            }
36669            _ => {
36670                // Default: UNIX_TO_STR(this, [format])
36671                self.write_keyword("UNIX_TO_STR");
36672                self.write("(");
36673                self.generate_expression(&e.this)?;
36674                if let Some(format) = &e.format {
36675                    self.write(", '");
36676                    self.write(format);
36677                    self.write("'");
36678                }
36679                self.write(")");
36680            }
36681        }
36682        Ok(())
36683    }
36684
36685    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
36686        use crate::dialects::DialectType;
36687        let scale = e.scale.unwrap_or(0); // 0 = seconds
36688
36689        match self.config.dialect {
36690            Some(DialectType::Snowflake) => {
36691                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
36692                self.write_keyword("TO_TIMESTAMP");
36693                self.write("(");
36694                self.generate_expression(&e.this)?;
36695                if let Some(s) = e.scale {
36696                    if s > 0 {
36697                        self.write(", ");
36698                        self.write(&s.to_string());
36699                    }
36700                }
36701                self.write(")");
36702            }
36703            Some(DialectType::BigQuery) => {
36704                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
36705                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
36706                match scale {
36707                    0 => {
36708                        self.write_keyword("TIMESTAMP_SECONDS");
36709                        self.write("(");
36710                        self.generate_expression(&e.this)?;
36711                        self.write(")");
36712                    }
36713                    3 => {
36714                        self.write_keyword("TIMESTAMP_MILLIS");
36715                        self.write("(");
36716                        self.generate_expression(&e.this)?;
36717                        self.write(")");
36718                    }
36719                    6 => {
36720                        self.write_keyword("TIMESTAMP_MICROS");
36721                        self.write("(");
36722                        self.generate_expression(&e.this)?;
36723                        self.write(")");
36724                    }
36725                    _ => {
36726                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
36727                        self.write_keyword("TIMESTAMP_SECONDS");
36728                        self.write("(CAST(");
36729                        self.generate_expression(&e.this)?;
36730                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
36731                    }
36732                }
36733            }
36734            Some(DialectType::Spark) => {
36735                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
36736                // TIMESTAMP_MILLIS(value) for scale=3
36737                // TIMESTAMP_MICROS(value) for scale=6
36738                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
36739                match scale {
36740                    0 => {
36741                        self.write_keyword("CAST");
36742                        self.write("(");
36743                        self.write_keyword("FROM_UNIXTIME");
36744                        self.write("(");
36745                        self.generate_expression(&e.this)?;
36746                        self.write(") ");
36747                        self.write_keyword("AS TIMESTAMP");
36748                        self.write(")");
36749                    }
36750                    3 => {
36751                        self.write_keyword("TIMESTAMP_MILLIS");
36752                        self.write("(");
36753                        self.generate_expression(&e.this)?;
36754                        self.write(")");
36755                    }
36756                    6 => {
36757                        self.write_keyword("TIMESTAMP_MICROS");
36758                        self.write("(");
36759                        self.generate_expression(&e.this)?;
36760                        self.write(")");
36761                    }
36762                    _ => {
36763                        self.write_keyword("TIMESTAMP_SECONDS");
36764                        self.write("(");
36765                        self.generate_expression(&e.this)?;
36766                        self.write(&format!(" / POWER(10, {}))", scale));
36767                    }
36768                }
36769            }
36770            Some(DialectType::Databricks) => {
36771                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
36772                // TIMESTAMP_MILLIS(value) for scale=3
36773                // TIMESTAMP_MICROS(value) for scale=6
36774                match scale {
36775                    0 => {
36776                        self.write_keyword("CAST");
36777                        self.write("(");
36778                        self.write_keyword("FROM_UNIXTIME");
36779                        self.write("(");
36780                        self.generate_expression(&e.this)?;
36781                        self.write(") ");
36782                        self.write_keyword("AS TIMESTAMP");
36783                        self.write(")");
36784                    }
36785                    3 => {
36786                        self.write_keyword("TIMESTAMP_MILLIS");
36787                        self.write("(");
36788                        self.generate_expression(&e.this)?;
36789                        self.write(")");
36790                    }
36791                    6 => {
36792                        self.write_keyword("TIMESTAMP_MICROS");
36793                        self.write("(");
36794                        self.generate_expression(&e.this)?;
36795                        self.write(")");
36796                    }
36797                    _ => {
36798                        self.write_keyword("TIMESTAMP_SECONDS");
36799                        self.write("(");
36800                        self.generate_expression(&e.this)?;
36801                        self.write(&format!(" / POWER(10, {}))", scale));
36802                    }
36803                }
36804            }
36805            Some(DialectType::Hive) => {
36806                // Hive: FROM_UNIXTIME(value)
36807                if scale == 0 {
36808                    self.write_keyword("FROM_UNIXTIME");
36809                    self.write("(");
36810                    self.generate_expression(&e.this)?;
36811                    self.write(")");
36812                } else {
36813                    self.write_keyword("FROM_UNIXTIME");
36814                    self.write("(");
36815                    self.generate_expression(&e.this)?;
36816                    self.write(&format!(" / POWER(10, {})", scale));
36817                    self.write(")");
36818                }
36819            }
36820            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36821                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
36822                // FROM_UNIXTIME(value) for scale=0
36823                if scale == 0 {
36824                    self.write_keyword("FROM_UNIXTIME");
36825                    self.write("(");
36826                    self.generate_expression(&e.this)?;
36827                    self.write(")");
36828                } else {
36829                    self.write_keyword("FROM_UNIXTIME");
36830                    self.write("(CAST(");
36831                    self.generate_expression(&e.this)?;
36832                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
36833                }
36834            }
36835            Some(DialectType::DuckDB) => {
36836                // DuckDB: TO_TIMESTAMP(value) for scale=0
36837                // EPOCH_MS(value) for scale=3
36838                // MAKE_TIMESTAMP(value) for scale=6
36839                match scale {
36840                    0 => {
36841                        self.write_keyword("TO_TIMESTAMP");
36842                        self.write("(");
36843                        self.generate_expression(&e.this)?;
36844                        self.write(")");
36845                    }
36846                    3 => {
36847                        self.write_keyword("EPOCH_MS");
36848                        self.write("(");
36849                        self.generate_expression(&e.this)?;
36850                        self.write(")");
36851                    }
36852                    6 => {
36853                        self.write_keyword("MAKE_TIMESTAMP");
36854                        self.write("(");
36855                        self.generate_expression(&e.this)?;
36856                        self.write(")");
36857                    }
36858                    _ => {
36859                        self.write_keyword("TO_TIMESTAMP");
36860                        self.write("(");
36861                        self.generate_expression(&e.this)?;
36862                        self.write(&format!(" / POWER(10, {}))", scale));
36863                        self.write_keyword(" AT TIME ZONE");
36864                        self.write(" 'UTC'");
36865                    }
36866                }
36867            }
36868            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
36869                // Doris/StarRocks: FROM_UNIXTIME(value)
36870                self.write_keyword("FROM_UNIXTIME");
36871                self.write("(");
36872                self.generate_expression(&e.this)?;
36873                self.write(")");
36874            }
36875            Some(DialectType::Oracle) => {
36876                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
36877                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
36878                self.generate_expression(&e.this)?;
36879                self.write(" / 86400)");
36880            }
36881            Some(DialectType::Redshift) => {
36882                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
36883                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
36884                self.write("(TIMESTAMP 'epoch' + ");
36885                if scale == 0 {
36886                    self.generate_expression(&e.this)?;
36887                } else {
36888                    self.write("(");
36889                    self.generate_expression(&e.this)?;
36890                    self.write(&format!(" / POWER(10, {}))", scale));
36891                }
36892                self.write(" * INTERVAL '1 SECOND')");
36893            }
36894            Some(DialectType::Exasol) => {
36895                // Exasol: FROM_POSIX_TIME(value)
36896                self.write_keyword("FROM_POSIX_TIME");
36897                self.write("(");
36898                self.generate_expression(&e.this)?;
36899                self.write(")");
36900            }
36901            _ => {
36902                // Default: TO_TIMESTAMP(value[, scale])
36903                self.write_keyword("TO_TIMESTAMP");
36904                self.write("(");
36905                self.generate_expression(&e.this)?;
36906                if let Some(s) = e.scale {
36907                    self.write(", ");
36908                    self.write(&s.to_string());
36909                }
36910                self.write(")");
36911            }
36912        }
36913        Ok(())
36914    }
36915
36916    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
36917        // NAME col VALUE col1, col2, ...
36918        if !matches!(&*e.this, Expression::Null(_)) {
36919            self.write_keyword("NAME");
36920            self.write_space();
36921            self.generate_expression(&e.this)?;
36922        }
36923        if !e.expressions.is_empty() {
36924            self.write_space();
36925            self.write_keyword("VALUE");
36926            self.write_space();
36927            for (i, expr) in e.expressions.iter().enumerate() {
36928                if i > 0 {
36929                    self.write(", ");
36930                }
36931                self.generate_expression(expr)?;
36932            }
36933        }
36934        Ok(())
36935    }
36936
36937    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
36938        // this(expressions) or (this)(expressions)
36939        if e.wrapped.is_some() {
36940            self.write("(");
36941        }
36942        self.generate_expression(&e.this)?;
36943        if e.wrapped.is_some() {
36944            self.write(")");
36945        }
36946        self.write("(");
36947        for (i, expr) in e.expressions.iter().enumerate() {
36948            if i > 0 {
36949                self.write(", ");
36950            }
36951            self.generate_expression(expr)?;
36952        }
36953        self.write(")");
36954        Ok(())
36955    }
36956
36957    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
36958        // USING TEMPLATE this
36959        self.write_keyword("USING TEMPLATE");
36960        self.write_space();
36961        self.generate_expression(&e.this)?;
36962        Ok(())
36963    }
36964
36965    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
36966        // UTC_TIME
36967        self.write_keyword("UTC_TIME");
36968        Ok(())
36969    }
36970
36971    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
36972        if matches!(
36973            self.config.dialect,
36974            Some(crate::dialects::DialectType::ClickHouse)
36975        ) {
36976            self.write_keyword("CURRENT_TIMESTAMP");
36977            self.write("('UTC')");
36978        } else {
36979            self.write_keyword("UTC_TIMESTAMP");
36980        }
36981        Ok(())
36982    }
36983
36984    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
36985        use crate::dialects::DialectType;
36986        // Choose UUID function name based on target dialect
36987        let func_name = match self.config.dialect {
36988            Some(DialectType::Snowflake) => "UUID_STRING",
36989            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
36990            Some(DialectType::BigQuery) => "GENERATE_UUID",
36991            _ => {
36992                if let Some(name) = &e.name {
36993                    name.as_str()
36994                } else {
36995                    "UUID"
36996                }
36997            }
36998        };
36999        self.write_keyword(func_name);
37000        self.write("(");
37001        if let Some(this) = &e.this {
37002            self.generate_expression(this)?;
37003        }
37004        self.write(")");
37005        Ok(())
37006    }
37007
37008    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
37009        // MAP(key1, value1, key2, value2, ...)
37010        self.write_keyword("MAP");
37011        self.write("(");
37012        let mut first = true;
37013        for (k, v) in e.keys.iter().zip(e.values.iter()) {
37014            if !first {
37015                self.write(", ");
37016            }
37017            self.generate_expression(k)?;
37018            self.write(", ");
37019            self.generate_expression(v)?;
37020            first = false;
37021        }
37022        self.write(")");
37023        Ok(())
37024    }
37025
37026    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
37027        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
37028        self.write_keyword("VECTOR_SEARCH");
37029        self.write("(");
37030        self.generate_expression(&e.this)?;
37031        if let Some(col) = &e.column_to_search {
37032            self.write(", ");
37033            self.generate_expression(col)?;
37034        }
37035        if let Some(query_table) = &e.query_table {
37036            self.write(", ");
37037            self.generate_expression(query_table)?;
37038        }
37039        if let Some(query_col) = &e.query_column_to_search {
37040            self.write(", ");
37041            self.generate_expression(query_col)?;
37042        }
37043        if let Some(top_k) = &e.top_k {
37044            self.write(", ");
37045            self.generate_expression(top_k)?;
37046        }
37047        if let Some(dist_type) = &e.distance_type {
37048            self.write(", ");
37049            self.generate_expression(dist_type)?;
37050        }
37051        self.write(")");
37052        Ok(())
37053    }
37054
37055    fn generate_version(&mut self, e: &Version) -> Result<()> {
37056        // Python: f"FOR {expression.name} {kind} {expr}"
37057        // e.this = Identifier("TIMESTAMP" or "VERSION")
37058        // e.kind = "AS OF" (or "BETWEEN", etc.)
37059        // e.expression = the value expression
37060        // Hive does NOT use the FOR prefix for time travel
37061        use crate::dialects::DialectType;
37062        let skip_for = matches!(
37063            self.config.dialect,
37064            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
37065        );
37066        if !skip_for {
37067            self.write_keyword("FOR");
37068            self.write_space();
37069        }
37070        // Extract the name from this (which is an Identifier expression)
37071        match e.this.as_ref() {
37072            Expression::Identifier(ident) => {
37073                self.write_keyword(&ident.name);
37074            }
37075            _ => {
37076                self.generate_expression(&e.this)?;
37077            }
37078        }
37079        self.write_space();
37080        self.write_keyword(&e.kind);
37081        if let Some(expression) = &e.expression {
37082            self.write_space();
37083            self.generate_expression(expression)?;
37084        }
37085        Ok(())
37086    }
37087
37088    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
37089        // Python: return self.sql(expression, "this")
37090        self.generate_expression(&e.this)?;
37091        Ok(())
37092    }
37093
37094    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
37095        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
37096        if e.this.is_some() {
37097            self.write_keyword("NOT VOLATILE");
37098        } else {
37099            self.write_keyword("VOLATILE");
37100        }
37101        Ok(())
37102    }
37103
37104    fn generate_watermark_column_constraint(
37105        &mut self,
37106        e: &WatermarkColumnConstraint,
37107    ) -> Result<()> {
37108        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
37109        self.write_keyword("WATERMARK FOR");
37110        self.write_space();
37111        self.generate_expression(&e.this)?;
37112        self.write_space();
37113        self.write_keyword("AS");
37114        self.write_space();
37115        self.generate_expression(&e.expression)?;
37116        Ok(())
37117    }
37118
37119    fn generate_week(&mut self, e: &Week) -> Result<()> {
37120        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
37121        self.write_keyword("WEEK");
37122        self.write("(");
37123        self.generate_expression(&e.this)?;
37124        if let Some(mode) = &e.mode {
37125            self.write(", ");
37126            self.generate_expression(mode)?;
37127        }
37128        self.write(")");
37129        Ok(())
37130    }
37131
37132    fn generate_when(&mut self, e: &When) -> Result<()> {
37133        // Python: WHEN {matched}{source}{condition} THEN {then}
37134        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
37135        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
37136        self.write_keyword("WHEN");
37137        self.write_space();
37138
37139        // Check if matched
37140        if let Some(matched) = &e.matched {
37141            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
37142            match matched.as_ref() {
37143                Expression::Boolean(b) if b.value => {
37144                    self.write_keyword("MATCHED");
37145                }
37146                _ => {
37147                    self.write_keyword("NOT MATCHED");
37148                }
37149            }
37150        } else {
37151            self.write_keyword("NOT MATCHED");
37152        }
37153
37154        // BY SOURCE / BY TARGET
37155        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
37156        // BY TARGET is the default and typically omitted in output
37157        // Only emit if the dialect supports BY SOURCE syntax
37158        if self.config.matched_by_source {
37159            if let Some(source) = &e.source {
37160                if let Expression::Boolean(b) = source.as_ref() {
37161                    if b.value {
37162                        // BY SOURCE
37163                        self.write_space();
37164                        self.write_keyword("BY SOURCE");
37165                    }
37166                    // BY TARGET (b.value == false) is omitted as it's the default
37167                } else {
37168                    // For non-boolean source, output as BY SOURCE (legacy behavior)
37169                    self.write_space();
37170                    self.write_keyword("BY SOURCE");
37171                }
37172            }
37173        }
37174
37175        // Condition
37176        if let Some(condition) = &e.condition {
37177            self.write_space();
37178            self.write_keyword("AND");
37179            self.write_space();
37180            self.generate_expression(condition)?;
37181        }
37182
37183        self.write_space();
37184        self.write_keyword("THEN");
37185        self.write_space();
37186
37187        // Generate the then expression (could be INSERT, UPDATE, DELETE)
37188        // MERGE actions are stored as Tuples with the action keyword as first element
37189        self.generate_merge_action(&e.then)?;
37190
37191        Ok(())
37192    }
37193
37194    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
37195        match action {
37196            Expression::Tuple(tuple) => {
37197                let elements = &tuple.expressions;
37198                if elements.is_empty() {
37199                    return self.generate_expression(action);
37200                }
37201                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
37202                match &elements[0] {
37203                    Expression::Var(v) if v.this == "INSERT" => {
37204                        self.write_keyword("INSERT");
37205                        // Spark: INSERT * (insert all columns)
37206                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
37207                            self.write(" *");
37208                        } else {
37209                            let mut values_idx = 1;
37210                            // Check if second element is column list (Tuple)
37211                            if elements.len() > 1 {
37212                                if let Expression::Tuple(cols) = &elements[1] {
37213                                    // Could be columns or values - if there's a third element, second is columns
37214                                    if elements.len() > 2 {
37215                                        // Second is columns, third is values
37216                                        self.write(" (");
37217                                        for (i, col) in cols.expressions.iter().enumerate() {
37218                                            if i > 0 {
37219                                                self.write(", ");
37220                                            }
37221                                            // Strip MERGE target qualifiers from INSERT column list
37222                                            if !self.merge_strip_qualifiers.is_empty() {
37223                                                let stripped = self.strip_merge_qualifier(col);
37224                                                self.generate_expression(&stripped)?;
37225                                            } else {
37226                                                self.generate_expression(col)?;
37227                                            }
37228                                        }
37229                                        self.write(")");
37230                                        values_idx = 2;
37231                                    } else {
37232                                        // Only two elements: INSERT + values (no explicit columns)
37233                                        values_idx = 1;
37234                                    }
37235                                }
37236                            }
37237                            // Generate VALUES clause
37238                            if values_idx < elements.len() {
37239                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
37240                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
37241                                if !is_row {
37242                                    self.write_space();
37243                                    self.write_keyword("VALUES");
37244                                }
37245                                self.write(" ");
37246                                if let Expression::Tuple(vals) = &elements[values_idx] {
37247                                    self.write("(");
37248                                    for (i, val) in vals.expressions.iter().enumerate() {
37249                                        if i > 0 {
37250                                            self.write(", ");
37251                                        }
37252                                        self.generate_expression(val)?;
37253                                    }
37254                                    self.write(")");
37255                                } else {
37256                                    self.generate_expression(&elements[values_idx])?;
37257                                }
37258                            }
37259                        } // close else for INSERT * check
37260                    }
37261                    Expression::Var(v) if v.this == "UPDATE" => {
37262                        self.write_keyword("UPDATE");
37263                        // Spark: UPDATE * (update all columns)
37264                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
37265                            self.write(" *");
37266                        } else if elements.len() > 1 {
37267                            self.write_space();
37268                            self.write_keyword("SET");
37269                            // In pretty mode, put assignments on next line with extra indent
37270                            if self.config.pretty {
37271                                self.write_newline();
37272                                self.indent_level += 1;
37273                                self.write_indent();
37274                            } else {
37275                                self.write_space();
37276                            }
37277                            if let Expression::Tuple(assignments) = &elements[1] {
37278                                for (i, assignment) in assignments.expressions.iter().enumerate() {
37279                                    if i > 0 {
37280                                        if self.config.pretty {
37281                                            self.write(",");
37282                                            self.write_newline();
37283                                            self.write_indent();
37284                                        } else {
37285                                            self.write(", ");
37286                                        }
37287                                    }
37288                                    // Strip MERGE target qualifiers from left side of UPDATE SET
37289                                    if !self.merge_strip_qualifiers.is_empty() {
37290                                        self.generate_merge_set_assignment(assignment)?;
37291                                    } else {
37292                                        self.generate_expression(assignment)?;
37293                                    }
37294                                }
37295                            } else {
37296                                self.generate_expression(&elements[1])?;
37297                            }
37298                            if self.config.pretty {
37299                                self.indent_level -= 1;
37300                            }
37301                        }
37302                    }
37303                    _ => {
37304                        // Fallback: generic tuple generation
37305                        self.generate_expression(action)?;
37306                    }
37307                }
37308            }
37309            Expression::Var(v)
37310                if v.this == "INSERT"
37311                    || v.this == "UPDATE"
37312                    || v.this == "DELETE"
37313                    || v.this == "DO NOTHING" =>
37314            {
37315                self.write_keyword(&v.this);
37316            }
37317            _ => {
37318                self.generate_expression(action)?;
37319            }
37320        }
37321        Ok(())
37322    }
37323
37324    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
37325    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
37326        match assignment {
37327            Expression::Eq(eq) => {
37328                // Strip qualifier from the left side if it matches a MERGE target name
37329                let stripped_left = self.strip_merge_qualifier(&eq.left);
37330                self.generate_expression(&stripped_left)?;
37331                self.write(" = ");
37332                self.generate_expression(&eq.right)?;
37333                Ok(())
37334            }
37335            other => self.generate_expression(other),
37336        }
37337    }
37338
37339    /// Strip table qualifier from a column reference if it matches a MERGE target name
37340    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
37341        match expr {
37342            Expression::Column(col) => {
37343                if let Some(ref table_ident) = col.table {
37344                    if self
37345                        .merge_strip_qualifiers
37346                        .iter()
37347                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
37348                    {
37349                        // Strip the table qualifier
37350                        let mut col = col.clone();
37351                        col.table = None;
37352                        return Expression::Column(col);
37353                    }
37354                }
37355                expr.clone()
37356            }
37357            Expression::Dot(dot) => {
37358                // table.column -> column (strip qualifier)
37359                if let Expression::Identifier(id) = &dot.this {
37360                    if self
37361                        .merge_strip_qualifiers
37362                        .iter()
37363                        .any(|n| n.eq_ignore_ascii_case(&id.name))
37364                    {
37365                        return Expression::Identifier(dot.field.clone());
37366                    }
37367                }
37368                expr.clone()
37369            }
37370            _ => expr.clone(),
37371        }
37372    }
37373
37374    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
37375        // Python: return self.expressions(expression, sep=" ", indent=False)
37376        for (i, expr) in e.expressions.iter().enumerate() {
37377            if i > 0 {
37378                // In pretty mode, each WHEN clause on its own line
37379                if self.config.pretty {
37380                    self.write_newline();
37381                    self.write_indent();
37382                } else {
37383                    self.write_space();
37384                }
37385            }
37386            self.generate_expression(expr)?;
37387        }
37388        Ok(())
37389    }
37390
37391    fn generate_where(&mut self, e: &Where) -> Result<()> {
37392        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
37393        self.write_keyword("WHERE");
37394        self.write_space();
37395        self.generate_expression(&e.this)?;
37396        Ok(())
37397    }
37398
37399    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
37400        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
37401        self.write_keyword("WIDTH_BUCKET");
37402        self.write("(");
37403        self.generate_expression(&e.this)?;
37404        if let Some(min_value) = &e.min_value {
37405            self.write(", ");
37406            self.generate_expression(min_value)?;
37407        }
37408        if let Some(max_value) = &e.max_value {
37409            self.write(", ");
37410            self.generate_expression(max_value)?;
37411        }
37412        if let Some(num_buckets) = &e.num_buckets {
37413            self.write(", ");
37414            self.generate_expression(num_buckets)?;
37415        }
37416        self.write(")");
37417        Ok(())
37418    }
37419
37420    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
37421        // Window specification: PARTITION BY ... ORDER BY ... frame
37422        self.generate_window_spec(e)
37423    }
37424
37425    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
37426        // Window specification: PARTITION BY ... ORDER BY ... frame
37427        let mut has_content = false;
37428
37429        // PARTITION BY
37430        if !e.partition_by.is_empty() {
37431            self.write_keyword("PARTITION BY");
37432            self.write_space();
37433            for (i, expr) in e.partition_by.iter().enumerate() {
37434                if i > 0 {
37435                    self.write(", ");
37436                }
37437                self.generate_expression(expr)?;
37438            }
37439            has_content = true;
37440        }
37441
37442        // ORDER BY
37443        if !e.order_by.is_empty() {
37444            if has_content {
37445                self.write_space();
37446            }
37447            self.write_keyword("ORDER BY");
37448            self.write_space();
37449            for (i, ordered) in e.order_by.iter().enumerate() {
37450                if i > 0 {
37451                    self.write(", ");
37452                }
37453                self.generate_expression(&ordered.this)?;
37454                if ordered.desc {
37455                    self.write_space();
37456                    self.write_keyword("DESC");
37457                } else if ordered.explicit_asc {
37458                    self.write_space();
37459                    self.write_keyword("ASC");
37460                }
37461                if let Some(nulls_first) = ordered.nulls_first {
37462                    self.write_space();
37463                    self.write_keyword("NULLS");
37464                    self.write_space();
37465                    if nulls_first {
37466                        self.write_keyword("FIRST");
37467                    } else {
37468                        self.write_keyword("LAST");
37469                    }
37470                }
37471            }
37472            has_content = true;
37473        }
37474
37475        // Frame specification
37476        if let Some(frame) = &e.frame {
37477            if has_content {
37478                self.write_space();
37479            }
37480            self.generate_window_frame(frame)?;
37481        }
37482
37483        Ok(())
37484    }
37485
37486    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
37487        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
37488        self.write_keyword("WITH");
37489        self.write_space();
37490        if e.no.is_some() {
37491            self.write_keyword("NO");
37492            self.write_space();
37493        }
37494        self.write_keyword("DATA");
37495
37496        // statistics
37497        if let Some(statistics) = &e.statistics {
37498            self.write_space();
37499            self.write_keyword("AND");
37500            self.write_space();
37501            // Check if statistics is true or false
37502            match statistics.as_ref() {
37503                Expression::Boolean(b) if !b.value => {
37504                    self.write_keyword("NO");
37505                    self.write_space();
37506                }
37507                _ => {}
37508            }
37509            self.write_keyword("STATISTICS");
37510        }
37511        Ok(())
37512    }
37513
37514    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
37515        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
37516        self.write_keyword("WITH FILL");
37517
37518        if let Some(from_) = &e.from_ {
37519            self.write_space();
37520            self.write_keyword("FROM");
37521            self.write_space();
37522            self.generate_expression(from_)?;
37523        }
37524
37525        if let Some(to) = &e.to {
37526            self.write_space();
37527            self.write_keyword("TO");
37528            self.write_space();
37529            self.generate_expression(to)?;
37530        }
37531
37532        if let Some(step) = &e.step {
37533            self.write_space();
37534            self.write_keyword("STEP");
37535            self.write_space();
37536            self.generate_expression(step)?;
37537        }
37538
37539        if let Some(staleness) = &e.staleness {
37540            self.write_space();
37541            self.write_keyword("STALENESS");
37542            self.write_space();
37543            self.generate_expression(staleness)?;
37544        }
37545
37546        if let Some(interpolate) = &e.interpolate {
37547            self.write_space();
37548            self.write_keyword("INTERPOLATE");
37549            self.write(" (");
37550            // INTERPOLATE items use reversed alias format: name AS expression
37551            self.generate_interpolate_item(interpolate)?;
37552            self.write(")");
37553        }
37554
37555        Ok(())
37556    }
37557
37558    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
37559    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
37560        match expr {
37561            Expression::Alias(alias) => {
37562                // Output as: alias_name AS expression
37563                self.generate_identifier(&alias.alias)?;
37564                self.write_space();
37565                self.write_keyword("AS");
37566                self.write_space();
37567                self.generate_expression(&alias.this)?;
37568            }
37569            Expression::Tuple(tuple) => {
37570                for (i, item) in tuple.expressions.iter().enumerate() {
37571                    if i > 0 {
37572                        self.write(", ");
37573                    }
37574                    self.generate_interpolate_item(item)?;
37575                }
37576            }
37577            other => {
37578                self.generate_expression(other)?;
37579            }
37580        }
37581        Ok(())
37582    }
37583
37584    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
37585        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
37586        self.write_keyword("WITH JOURNAL TABLE");
37587        self.write("=");
37588        self.generate_expression(&e.this)?;
37589        Ok(())
37590    }
37591
37592    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
37593        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
37594        self.generate_expression(&e.this)?;
37595        self.write_space();
37596        self.write_keyword("WITH");
37597        self.write_space();
37598        self.write_keyword(&e.op);
37599        Ok(())
37600    }
37601
37602    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
37603        // Python: return f"WITH {self.expressions(expression, flat=True)}"
37604        self.write_keyword("WITH");
37605        self.write_space();
37606        for (i, expr) in e.expressions.iter().enumerate() {
37607            if i > 0 {
37608                self.write(", ");
37609            }
37610            self.generate_expression(expr)?;
37611        }
37612        Ok(())
37613    }
37614
37615    fn generate_with_schema_binding_property(
37616        &mut self,
37617        e: &WithSchemaBindingProperty,
37618    ) -> Result<()> {
37619        // Python: return f"WITH {self.sql(expression, 'this')}"
37620        self.write_keyword("WITH");
37621        self.write_space();
37622        self.generate_expression(&e.this)?;
37623        Ok(())
37624    }
37625
37626    fn generate_with_system_versioning_property(
37627        &mut self,
37628        e: &WithSystemVersioningProperty,
37629    ) -> Result<()> {
37630        // Python: complex logic for SYSTEM_VERSIONING with options
37631        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
37632        // or SYSTEM_VERSIONING=ON/OFF
37633        // with WITH(...) wrapper if with_ is set
37634
37635        let mut parts = Vec::new();
37636
37637        if let Some(this) = &e.this {
37638            // HISTORY_TABLE=...
37639            let mut s = String::from("HISTORY_TABLE=");
37640            let mut gen = Generator::new();
37641            gen.generate_expression(this)?;
37642            s.push_str(&gen.output);
37643            parts.push(s);
37644        }
37645
37646        if let Some(data_consistency) = &e.data_consistency {
37647            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
37648            let mut gen = Generator::new();
37649            gen.generate_expression(data_consistency)?;
37650            s.push_str(&gen.output);
37651            parts.push(s);
37652        }
37653
37654        if let Some(retention_period) = &e.retention_period {
37655            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
37656            let mut gen = Generator::new();
37657            gen.generate_expression(retention_period)?;
37658            s.push_str(&gen.output);
37659            parts.push(s);
37660        }
37661
37662        self.write_keyword("SYSTEM_VERSIONING");
37663        self.write("=");
37664
37665        if !parts.is_empty() {
37666            self.write_keyword("ON");
37667            self.write("(");
37668            self.write(&parts.join(", "));
37669            self.write(")");
37670        } else if e.on.is_some() {
37671            self.write_keyword("ON");
37672        } else {
37673            self.write_keyword("OFF");
37674        }
37675
37676        // Wrap in WITH(...) if with_ is set
37677        if e.with_.is_some() {
37678            let inner = self.output.clone();
37679            self.output.clear();
37680            self.write("WITH(");
37681            self.write(&inner);
37682            self.write(")");
37683        }
37684
37685        Ok(())
37686    }
37687
37688    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
37689        // Python: f"WITH ({self.expressions(expression, flat=True)})"
37690        self.write_keyword("WITH");
37691        self.write(" (");
37692        for (i, expr) in e.expressions.iter().enumerate() {
37693            if i > 0 {
37694                self.write(", ");
37695            }
37696            self.generate_expression(expr)?;
37697        }
37698        self.write(")");
37699        Ok(())
37700    }
37701
37702    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
37703        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
37704        // return self.func("XMLELEMENT", name, *expression.expressions)
37705        self.write_keyword("XMLELEMENT");
37706        self.write("(");
37707
37708        if e.evalname.is_some() {
37709            self.write_keyword("EVALNAME");
37710        } else {
37711            self.write_keyword("NAME");
37712        }
37713        self.write_space();
37714        self.generate_expression(&e.this)?;
37715
37716        for expr in &e.expressions {
37717            self.write(", ");
37718            self.generate_expression(expr)?;
37719        }
37720        self.write(")");
37721        Ok(())
37722    }
37723
37724    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
37725        // XMLGET(this, expression [, instance])
37726        self.write_keyword("XMLGET");
37727        self.write("(");
37728        self.generate_expression(&e.this)?;
37729        self.write(", ");
37730        self.generate_expression(&e.expression)?;
37731        if let Some(instance) = &e.instance {
37732            self.write(", ");
37733            self.generate_expression(instance)?;
37734        }
37735        self.write(")");
37736        Ok(())
37737    }
37738
37739    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
37740        // Python: this + optional (expr)
37741        self.generate_expression(&e.this)?;
37742        if let Some(expression) = &e.expression {
37743            self.write("(");
37744            self.generate_expression(expression)?;
37745            self.write(")");
37746        }
37747        Ok(())
37748    }
37749
37750    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
37751        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
37752        self.write_keyword("XMLTABLE");
37753        self.write("(");
37754
37755        if self.config.pretty {
37756            self.indent_level += 1;
37757            self.write_newline();
37758            self.write_indent();
37759            self.generate_expression(&e.this)?;
37760
37761            if let Some(passing) = &e.passing {
37762                self.write_newline();
37763                self.write_indent();
37764                self.write_keyword("PASSING");
37765                if let Expression::Tuple(tuple) = passing.as_ref() {
37766                    for expr in &tuple.expressions {
37767                        self.write_newline();
37768                        self.indent_level += 1;
37769                        self.write_indent();
37770                        self.generate_expression(expr)?;
37771                        self.indent_level -= 1;
37772                    }
37773                } else {
37774                    self.write_newline();
37775                    self.indent_level += 1;
37776                    self.write_indent();
37777                    self.generate_expression(passing)?;
37778                    self.indent_level -= 1;
37779                }
37780            }
37781
37782            if e.by_ref.is_some() {
37783                self.write_newline();
37784                self.write_indent();
37785                self.write_keyword("RETURNING SEQUENCE BY REF");
37786            }
37787
37788            if !e.columns.is_empty() {
37789                self.write_newline();
37790                self.write_indent();
37791                self.write_keyword("COLUMNS");
37792                for (i, col) in e.columns.iter().enumerate() {
37793                    self.write_newline();
37794                    self.indent_level += 1;
37795                    self.write_indent();
37796                    self.generate_expression(col)?;
37797                    self.indent_level -= 1;
37798                    if i < e.columns.len() - 1 {
37799                        self.write(",");
37800                    }
37801                }
37802            }
37803
37804            self.indent_level -= 1;
37805            self.write_newline();
37806            self.write_indent();
37807            self.write(")");
37808            return Ok(());
37809        }
37810
37811        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
37812        if let Some(namespaces) = &e.namespaces {
37813            self.write_keyword("XMLNAMESPACES");
37814            self.write("(");
37815            // Unwrap Tuple if present to avoid extra parentheses
37816            if let Expression::Tuple(tuple) = namespaces.as_ref() {
37817                for (i, expr) in tuple.expressions.iter().enumerate() {
37818                    if i > 0 {
37819                        self.write(", ");
37820                    }
37821                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
37822                    // See xmlnamespace_sql in generator.py
37823                    if !matches!(expr, Expression::Alias(_)) {
37824                        self.write_keyword("DEFAULT");
37825                        self.write_space();
37826                    }
37827                    self.generate_expression(expr)?;
37828                }
37829            } else {
37830                // Single namespace - check if DEFAULT
37831                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
37832                    self.write_keyword("DEFAULT");
37833                    self.write_space();
37834                }
37835                self.generate_expression(namespaces)?;
37836            }
37837            self.write("), ");
37838        }
37839
37840        // XPath expression
37841        self.generate_expression(&e.this)?;
37842
37843        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
37844        if let Some(passing) = &e.passing {
37845            self.write_space();
37846            self.write_keyword("PASSING");
37847            self.write_space();
37848            // Unwrap Tuple if present to avoid extra parentheses
37849            if let Expression::Tuple(tuple) = passing.as_ref() {
37850                for (i, expr) in tuple.expressions.iter().enumerate() {
37851                    if i > 0 {
37852                        self.write(", ");
37853                    }
37854                    self.generate_expression(expr)?;
37855                }
37856            } else {
37857                self.generate_expression(passing)?;
37858            }
37859        }
37860
37861        // RETURNING SEQUENCE BY REF
37862        if e.by_ref.is_some() {
37863            self.write_space();
37864            self.write_keyword("RETURNING SEQUENCE BY REF");
37865        }
37866
37867        // COLUMNS clause
37868        if !e.columns.is_empty() {
37869            self.write_space();
37870            self.write_keyword("COLUMNS");
37871            self.write_space();
37872            for (i, col) in e.columns.iter().enumerate() {
37873                if i > 0 {
37874                    self.write(", ");
37875                }
37876                self.generate_expression(col)?;
37877            }
37878        }
37879
37880        self.write(")");
37881        Ok(())
37882    }
37883
37884    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
37885        // Python: return self.connector_sql(expression, "XOR", stack)
37886        // Handles: this XOR expression or expressions joined by XOR
37887        if let Some(this) = &e.this {
37888            self.generate_expression(this)?;
37889            if let Some(expression) = &e.expression {
37890                self.write_space();
37891                self.write_keyword("XOR");
37892                self.write_space();
37893                self.generate_expression(expression)?;
37894            }
37895        }
37896
37897        // Handle multiple expressions
37898        for (i, expr) in e.expressions.iter().enumerate() {
37899            if i > 0 || e.this.is_some() {
37900                self.write_space();
37901                self.write_keyword("XOR");
37902                self.write_space();
37903            }
37904            self.generate_expression(expr)?;
37905        }
37906        Ok(())
37907    }
37908
37909    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
37910        // ZIPF(this, elementcount [, gen])
37911        self.write_keyword("ZIPF");
37912        self.write("(");
37913        self.generate_expression(&e.this)?;
37914        if let Some(elementcount) = &e.elementcount {
37915            self.write(", ");
37916            self.generate_expression(elementcount)?;
37917        }
37918        if let Some(gen) = &e.gen {
37919            self.write(", ");
37920            self.generate_expression(gen)?;
37921        }
37922        self.write(")");
37923        Ok(())
37924    }
37925}
37926
37927impl Default for Generator {
37928    fn default() -> Self {
37929        Self::new()
37930    }
37931}
37932
37933#[cfg(test)]
37934mod tests {
37935    use super::*;
37936    use crate::parser::Parser;
37937
37938    fn roundtrip(sql: &str) -> String {
37939        let ast = Parser::parse_sql(sql).unwrap();
37940        Generator::sql(&ast[0]).unwrap()
37941    }
37942
37943    #[test]
37944    fn test_simple_select() {
37945        let result = roundtrip("SELECT 1");
37946        assert_eq!(result, "SELECT 1");
37947    }
37948
37949    #[test]
37950    fn test_select_from() {
37951        let result = roundtrip("SELECT a, b FROM t");
37952        assert_eq!(result, "SELECT a, b FROM t");
37953    }
37954
37955    #[test]
37956    fn test_select_where() {
37957        let result = roundtrip("SELECT * FROM t WHERE x = 1");
37958        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
37959    }
37960
37961    #[test]
37962    fn test_select_join() {
37963        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
37964        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
37965    }
37966
37967    #[test]
37968    fn test_insert() {
37969        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
37970        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
37971    }
37972
37973    #[test]
37974    fn test_pretty_print() {
37975        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
37976        let result = Generator::pretty_sql(&ast[0]).unwrap();
37977        assert!(result.contains('\n'));
37978    }
37979
37980    #[test]
37981    fn test_window_function() {
37982        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
37983        assert_eq!(
37984            result,
37985            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
37986        );
37987    }
37988
37989    #[test]
37990    fn test_window_function_with_frame() {
37991        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
37992        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
37993    }
37994
37995    #[test]
37996    fn test_aggregate_with_filter() {
37997        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
37998        assert_eq!(
37999            result,
38000            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
38001        );
38002    }
38003
38004    #[test]
38005    fn test_subscript() {
38006        let result = roundtrip("SELECT arr[0]");
38007        assert_eq!(result, "SELECT arr[0]");
38008    }
38009
38010    // DDL tests
38011    #[test]
38012    fn test_create_table() {
38013        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
38014        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
38015    }
38016
38017    #[test]
38018    fn test_create_table_with_constraints() {
38019        let result = roundtrip(
38020            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
38021        );
38022        assert_eq!(
38023            result,
38024            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
38025        );
38026    }
38027
38028    #[test]
38029    fn test_create_table_if_not_exists() {
38030        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
38031        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
38032    }
38033
38034    #[test]
38035    fn test_drop_table() {
38036        let result = roundtrip("DROP TABLE users");
38037        assert_eq!(result, "DROP TABLE users");
38038    }
38039
38040    #[test]
38041    fn test_drop_table_if_exists_cascade() {
38042        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
38043        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
38044    }
38045
38046    #[test]
38047    fn test_alter_table_add_column() {
38048        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
38049        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
38050    }
38051
38052    #[test]
38053    fn test_alter_table_drop_column() {
38054        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
38055        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
38056    }
38057
38058    #[test]
38059    fn test_create_index() {
38060        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
38061        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
38062    }
38063
38064    #[test]
38065    fn test_create_unique_index() {
38066        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
38067        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
38068    }
38069
38070    #[test]
38071    fn test_drop_index() {
38072        let result = roundtrip("DROP INDEX idx_name");
38073        assert_eq!(result, "DROP INDEX idx_name");
38074    }
38075
38076    #[test]
38077    fn test_create_view() {
38078        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
38079        assert_eq!(
38080            result,
38081            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
38082        );
38083    }
38084
38085    #[test]
38086    fn test_drop_view() {
38087        let result = roundtrip("DROP VIEW active_users");
38088        assert_eq!(result, "DROP VIEW active_users");
38089    }
38090
38091    #[test]
38092    fn test_truncate() {
38093        let result = roundtrip("TRUNCATE TABLE users");
38094        assert_eq!(result, "TRUNCATE TABLE users");
38095    }
38096
38097    #[test]
38098    fn test_string_literal_escaping_default() {
38099        // Default: double single quotes
38100        let result = roundtrip("SELECT 'hello'");
38101        assert_eq!(result, "SELECT 'hello'");
38102
38103        // Single quotes are doubled
38104        let result = roundtrip("SELECT 'it''s a test'");
38105        assert_eq!(result, "SELECT 'it''s a test'");
38106    }
38107
38108    #[test]
38109    fn test_not_in_style_prefix_default_generic() {
38110        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
38111        assert_eq!(
38112            result,
38113            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
38114        );
38115    }
38116
38117    #[test]
38118    fn test_not_in_style_infix_generic_override() {
38119        let ast =
38120            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
38121                .unwrap();
38122        let config = GeneratorConfig {
38123            not_in_style: NotInStyle::Infix,
38124            ..Default::default()
38125        };
38126        let mut gen = Generator::with_config(config);
38127        let result = gen.generate(&ast[0]).unwrap();
38128        assert_eq!(
38129            result,
38130            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
38131        );
38132    }
38133
38134    #[test]
38135    fn test_string_literal_escaping_mysql() {
38136        use crate::dialects::DialectType;
38137
38138        let config = GeneratorConfig {
38139            dialect: Some(DialectType::MySQL),
38140            ..Default::default()
38141        };
38142
38143        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38144        let mut gen = Generator::with_config(config.clone());
38145        let result = gen.generate(&ast[0]).unwrap();
38146        assert_eq!(result, "SELECT 'hello'");
38147
38148        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
38149        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38150        let mut gen = Generator::with_config(config.clone());
38151        let result = gen.generate(&ast[0]).unwrap();
38152        assert_eq!(result, "SELECT 'it''s'");
38153    }
38154
38155    #[test]
38156    fn test_string_literal_escaping_postgres() {
38157        use crate::dialects::DialectType;
38158
38159        let config = GeneratorConfig {
38160            dialect: Some(DialectType::PostgreSQL),
38161            ..Default::default()
38162        };
38163
38164        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38165        let mut gen = Generator::with_config(config.clone());
38166        let result = gen.generate(&ast[0]).unwrap();
38167        assert_eq!(result, "SELECT 'hello'");
38168
38169        // PostgreSQL uses doubled quotes for regular strings
38170        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38171        let mut gen = Generator::with_config(config.clone());
38172        let result = gen.generate(&ast[0]).unwrap();
38173        assert_eq!(result, "SELECT 'it''s'");
38174    }
38175
38176    #[test]
38177    fn test_string_literal_escaping_bigquery() {
38178        use crate::dialects::DialectType;
38179
38180        let config = GeneratorConfig {
38181            dialect: Some(DialectType::BigQuery),
38182            ..Default::default()
38183        };
38184
38185        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
38186        let mut gen = Generator::with_config(config.clone());
38187        let result = gen.generate(&ast[0]).unwrap();
38188        assert_eq!(result, "SELECT 'hello'");
38189
38190        // BigQuery escapes single quotes with backslash
38191        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
38192        let mut gen = Generator::with_config(config.clone());
38193        let result = gen.generate(&ast[0]).unwrap();
38194        assert_eq!(result, "SELECT 'it\\'s'");
38195    }
38196
38197    #[test]
38198    fn test_generate_deep_and_chain_without_stack_growth() {
38199        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
38200            Expression::column("c0"),
38201            Expression::number(0),
38202        )));
38203
38204        for i in 1..2500 {
38205            let predicate = Expression::Eq(Box::new(BinaryOp::new(
38206                Expression::column(format!("c{i}")),
38207                Expression::number(i as i64),
38208            )));
38209            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
38210        }
38211
38212        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
38213        assert!(sql.contains("c2499 = 2499"), "{}", sql);
38214    }
38215
38216    #[test]
38217    fn test_generate_deep_or_chain_without_stack_growth() {
38218        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
38219            Expression::column("c0"),
38220            Expression::number(0),
38221        )));
38222
38223        for i in 1..2500 {
38224            let predicate = Expression::Eq(Box::new(BinaryOp::new(
38225                Expression::column(format!("c{i}")),
38226                Expression::number(i as i64),
38227            )));
38228            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
38229        }
38230
38231        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
38232        assert!(sql.contains("c2499 = 2499"), "{}", sql);
38233    }
38234}