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::guard::{enforce_generate_ast, ComplexityGuardOptions};
19use crate::DialectType;
20use serde::{Deserialize, Serialize};
21
22/// SQL code generator that converts an AST (`Expression`) back into a SQL string.
23///
24/// The generator walks the expression tree and emits dialect-specific SQL text.
25/// It supports pretty-printing with configurable indentation, identifier quoting,
26/// keyword casing, function name normalization, and 30+ SQL dialect variants.
27///
28/// # Usage
29///
30/// ```rust,ignore
31/// use polyglot_sql::generator::Generator;
32/// use polyglot_sql::parser::Parser;
33///
34/// let ast = Parser::parse_sql("SELECT 1")?;
35/// // Quick one-shot generation (default config):
36/// let sql = Generator::sql(&ast[0])?;
37///
38/// // Pretty-printed output:
39/// let pretty = Generator::pretty_sql(&ast[0])?;
40///
41/// // Custom config (e.g. for a specific dialect):
42/// let config = GeneratorConfig { pretty: true, ..GeneratorConfig::default() };
43/// let mut gen = Generator::with_config(config);
44/// let sql = gen.generate(&ast[0])?;
45/// ```
46pub struct Generator {
47    config: Arc<GeneratorConfig>,
48    output: String,
49    unsupported_messages: Vec<String>,
50    indent_level: usize,
51    /// Athena dialect: true when generating Hive-style DDL (uses backticks)
52    /// false when generating Trino-style DML/CREATE VIEW (uses double quotes)
53    athena_hive_context: bool,
54    /// SQLite: column names that should have PRIMARY KEY inlined (from single-column table constraints)
55    sqlite_inline_pk_columns: std::collections::HashSet<String>,
56    /// MERGE: table name/alias qualifiers to strip from UPDATE SET left side (for PostgreSQL)
57    merge_strip_qualifiers: Vec<String>,
58    /// ClickHouse: depth counter for Nullable wrapping context in CAST types.
59    /// 0 = not in cast context, 1 = top-level cast type, 2+ = inside container type.
60    /// Positive values indicate the type should be wrapped in Nullable (for non-container types).
61    /// Negative values indicate map key context (should NOT be wrapped).
62    clickhouse_nullable_depth: i32,
63}
64
65/// Controls how SQL function names are cased in generated output.
66///
67/// - `Upper` (default) -- `COUNT`, `SUM`, `COALESCE`
68/// - `Lower` -- `count`, `sum`, `coalesce`
69/// - `None` -- preserve the original casing from the parsed input
70#[derive(Debug, Clone, Copy, PartialEq, Default)]
71pub enum NormalizeFunctions {
72    /// Emit function names in UPPER CASE (default).
73    #[default]
74    Upper,
75    /// Emit function names in lower case.
76    Lower,
77    /// Preserve the original casing from the parsed input.
78    None,
79}
80
81/// Strategy for generating row-limiting clauses across SQL dialects.
82#[derive(Debug, Clone, Copy, PartialEq, Default)]
83pub enum LimitFetchStyle {
84    /// `LIMIT n` -- MySQL, PostgreSQL, DuckDB, and most modern dialects.
85    #[default]
86    Limit,
87    /// `TOP n` -- TSQL (SQL Server).
88    Top,
89    /// `FETCH FIRST n ROWS ONLY` -- ISO/ANSI SQL standard, Oracle, DB2.
90    FetchFirst,
91}
92
93/// Strategy for rendering negated IN predicates.
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
95pub enum NotInStyle {
96    /// Emit `NOT x IN (...)` in generic mode (current compatibility behavior).
97    #[default]
98    Prefix,
99    /// Emit `x NOT IN (...)` in generic mode (canonical SQL style).
100    Infix,
101}
102
103/// Controls how the generator reacts when it encounters unsupported output.
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
105#[serde(rename_all = "lowercase")]
106pub enum UnsupportedLevel {
107    /// Ignore unsupported diagnostics and continue generation.
108    Ignore,
109    /// Collect unsupported diagnostics and continue generation.
110    #[default]
111    Warn,
112    /// Collect unsupported diagnostics and raise after generation completes.
113    Raise,
114    /// Raise immediately when the first unsupported feature is encountered.
115    Immediate,
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119enum ConnectorOperator {
120    And,
121    Or,
122}
123
124impl ConnectorOperator {
125    fn keyword(self) -> &'static str {
126        match self {
127            Self::And => "AND",
128            Self::Or => "OR",
129        }
130    }
131}
132
133/// Identifier quote style (start/end characters)
134#[derive(Debug, Clone, Copy, PartialEq)]
135pub struct IdentifierQuoteStyle {
136    /// Start character for quoting identifiers (e.g., '"', '`', '[')
137    pub start: char,
138    /// End character for quoting identifiers (e.g., '"', '`', ']')
139    pub end: char,
140}
141
142impl Default for IdentifierQuoteStyle {
143    fn default() -> Self {
144        Self {
145            start: '"',
146            end: '"',
147        }
148    }
149}
150
151impl IdentifierQuoteStyle {
152    /// Double-quote style (PostgreSQL, Oracle, standard SQL)
153    pub const DOUBLE_QUOTE: Self = Self {
154        start: '"',
155        end: '"',
156    };
157    /// Backtick style (MySQL, BigQuery, Spark, Hive)
158    pub const BACKTICK: Self = Self {
159        start: '`',
160        end: '`',
161    };
162    /// Square bracket style (TSQL, SQLite)
163    pub const BRACKET: Self = Self {
164        start: '[',
165        end: ']',
166    };
167}
168
169/// Configuration for the SQL [`Generator`].
170///
171/// This is a comprehensive port of the Python sqlglot `Generator` class attributes.
172/// It controls every aspect of SQL output: formatting, quoting, dialect-specific
173/// syntax, feature support flags, and more.
174///
175/// Most users should start from `GeneratorConfig::default()` and override only the
176/// fields they need. Dialect-specific presets are applied automatically when
177/// `dialect` is set via the higher-level transpilation API.
178///
179/// # Key fields
180///
181/// | Field | Default | Purpose |
182/// |-------|---------|---------|
183/// | `dialect` | `None` | Target SQL dialect (e.g. PostgreSQL, MySQL, BigQuery) |
184/// | `pretty` | `false` | Enable multi-line, indented output |
185/// | `indent` | `"  "` | Indentation string used when `pretty` is true |
186/// | `max_text_width` | `80` | Soft line-width limit for pretty-printing |
187/// | `normalize_functions` | `Upper` | Function name casing (`Upper`, `Lower`, `None`) |
188/// | `identifier_quote_style` | `"…"` | Quote characters for identifiers |
189/// | `uppercase_keywords` | `true` | Whether SQL keywords are upper-cased |
190#[derive(Debug, Clone)]
191pub struct GeneratorConfig {
192    // ===== Basic formatting =====
193    /// Pretty print with indentation
194    pub pretty: bool,
195    /// Indentation string (default 2 spaces)
196    pub indent: &'static str,
197    /// Maximum text width before wrapping (default 80)
198    pub max_text_width: usize,
199    /// Quote identifier style (deprecated, use identifier_quote_style instead)
200    pub identifier_quote: char,
201    /// Identifier quote style with separate start/end characters
202    pub identifier_quote_style: IdentifierQuoteStyle,
203    /// Uppercase keywords
204    pub uppercase_keywords: bool,
205    /// Normalize identifiers to lowercase when generating
206    pub normalize_identifiers: bool,
207    /// Dialect type for dialect-specific generation
208    pub dialect: Option<crate::dialects::DialectType>,
209    /// Source dialect type (used during transpilation to distinguish identity vs cross-dialect)
210    pub source_dialect: Option<crate::dialects::DialectType>,
211    /// How unsupported generation should be handled.
212    pub unsupported_level: UnsupportedLevel,
213    /// Maximum number of unsupported diagnostics to include in raised errors.
214    pub max_unsupported: usize,
215    /// Complexity guard limits for recursive generation paths.
216    pub complexity_guard: ComplexityGuardOptions,
217    /// How to output function names (UPPER, lower, or as-is)
218    pub normalize_functions: NormalizeFunctions,
219    /// String escape character
220    pub string_escape: char,
221    /// Whether identifiers are case-sensitive
222    pub case_sensitive_identifiers: bool,
223    /// Whether unquoted identifiers can start with a digit
224    pub identifiers_can_start_with_digit: bool,
225    /// Whether to always quote identifiers regardless of reserved keyword status
226    /// Used by dialects like Athena/Presto that prefer quoted identifiers
227    pub always_quote_identifiers: bool,
228    /// How to render negated IN predicates in generic output.
229    pub not_in_style: NotInStyle,
230
231    // ===== Null handling =====
232    /// Whether null ordering (NULLS FIRST/LAST) is supported in ORDER BY
233    /// True: Full Support, false: No support
234    pub null_ordering_supported: bool,
235    /// Whether ignore nulls is inside the agg or outside
236    /// FIRST(x IGNORE NULLS) OVER vs FIRST(x) IGNORE NULLS OVER
237    pub ignore_nulls_in_func: bool,
238    /// Whether the NVL2 function is supported
239    pub nvl2_supported: bool,
240
241    // ===== Limit/Fetch =====
242    /// How to output LIMIT clauses
243    pub limit_fetch_style: LimitFetchStyle,
244    /// Whether to generate the limit as TOP <value> instead of LIMIT <value>
245    pub limit_is_top: bool,
246    /// Whether limit and fetch allows expressions or just literals
247    pub limit_only_literals: bool,
248
249    // ===== Interval =====
250    /// Whether INTERVAL uses single quoted string ('1 day' vs 1 DAY)
251    pub single_string_interval: bool,
252    /// Whether the plural form of date parts (e.g., "days") is supported in INTERVALs
253    pub interval_allows_plural_form: bool,
254
255    // ===== CTE =====
256    /// Whether WITH RECURSIVE keyword is required (vs just WITH for recursive CTEs)
257    pub cte_recursive_keyword_required: bool,
258
259    // ===== VALUES =====
260    /// Whether VALUES can be used as a table source
261    pub values_as_table: bool,
262    /// Wrap derived values in parens (standard but Spark doesn't support)
263    pub wrap_derived_values: bool,
264
265    // ===== TABLESAMPLE =====
266    /// Keyword for TABLESAMPLE seed: "SEED" or "REPEATABLE"
267    pub tablesample_seed_keyword: &'static str,
268    /// Whether parentheses are required around the table sample's expression
269    pub tablesample_requires_parens: bool,
270    /// Whether a table sample clause's size needs to be followed by ROWS keyword
271    pub tablesample_size_is_rows: bool,
272    /// The keyword(s) to use when generating a sample clause
273    pub tablesample_keywords: &'static str,
274    /// Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
275    pub tablesample_with_method: bool,
276    /// Whether the table alias comes after tablesample (Oracle, Hive)
277    pub alias_post_tablesample: bool,
278
279    // ===== Aggregate =====
280    /// Whether aggregate FILTER (WHERE ...) is supported
281    pub aggregate_filter_supported: bool,
282    /// Whether DISTINCT can be followed by multiple args in an AggFunc
283    pub multi_arg_distinct: bool,
284    /// Whether ANY/ALL quantifiers have no space before `(`: `ANY(` vs `ANY (`
285    pub quantified_no_paren_space: bool,
286    /// Whether MEDIAN(expr) is supported; if not, generates PERCENTILE_CONT
287    pub supports_median: bool,
288
289    // ===== SELECT =====
290    /// Whether SELECT ... INTO is supported
291    pub supports_select_into: bool,
292    /// Whether locking reads (SELECT ... FOR UPDATE/SHARE) are supported
293    pub locking_reads_supported: bool,
294
295    // ===== Table/Join =====
296    /// Whether a table is allowed to be renamed with a db
297    pub rename_table_with_db: bool,
298    /// Whether JOIN sides (LEFT, RIGHT) are supported with SEMI/ANTI join kinds
299    pub semi_anti_join_with_side: bool,
300    /// Whether named columns are allowed in table aliases
301    pub supports_table_alias_columns: bool,
302    /// Whether join hints should be generated
303    pub join_hints: bool,
304    /// Whether table hints should be generated
305    pub table_hints: bool,
306    /// Whether query hints should be generated
307    pub query_hints: bool,
308    /// What kind of separator to use for query hints
309    pub query_hint_sep: &'static str,
310    /// Whether Oracle-style (+) join markers are supported (Oracle, Exasol)
311    pub supports_column_join_marks: bool,
312
313    // ===== DDL =====
314    /// Whether CREATE INDEX USING method should have no space before column parens
315    /// true: `USING btree(col)`, false: `USING btree (col)`
316    pub index_using_no_space: bool,
317    /// Whether UNLOGGED tables can be created
318    pub supports_unlogged_tables: bool,
319    /// Whether CREATE TABLE LIKE statement is supported
320    pub supports_create_table_like: bool,
321    /// Whether the LikeProperty needs to be inside the schema clause
322    pub like_property_inside_schema: bool,
323    /// Whether the word COLUMN is included when adding a column with ALTER TABLE
324    pub alter_table_include_column_keyword: bool,
325    /// Whether CREATE TABLE .. COPY .. is supported (false = CLONE instead)
326    pub supports_table_copy: bool,
327    /// The syntax to use when altering the type of a column
328    pub alter_set_type: &'static str,
329    /// Whether to wrap <props> in AlterSet, e.g., ALTER ... SET (<props>)
330    pub alter_set_wrapped: bool,
331
332    // ===== Timestamp/Timezone =====
333    /// Whether TIMESTAMP WITH TIME ZONE is used (vs TIMESTAMPTZ)
334    pub tz_to_with_time_zone: bool,
335    /// Whether CONVERT_TIMEZONE() is supported
336    pub supports_convert_timezone: bool,
337
338    // ===== JSON =====
339    /// Whether the JSON extraction operators expect a value of type JSON
340    pub json_type_required_for_extraction: bool,
341    /// Whether bracketed keys like ["foo"] are supported in JSON paths
342    pub json_path_bracketed_key_supported: bool,
343    /// Whether to escape keys using single quotes in JSON paths
344    pub json_path_single_quote_escape: bool,
345    /// Whether to quote the generated expression of JsonPath
346    pub quote_json_path: bool,
347    /// What delimiter to use for separating JSON key/value pairs
348    pub json_key_value_pair_sep: &'static str,
349
350    // ===== COPY =====
351    /// Whether parameters from COPY statement are wrapped in parentheses
352    pub copy_params_are_wrapped: bool,
353    /// Whether values of params are set with "=" token or empty space
354    pub copy_params_eq_required: bool,
355    /// Whether COPY statement has INTO keyword
356    pub copy_has_into_keyword: bool,
357
358    // ===== Window functions =====
359    /// Whether EXCLUDE in window specification is supported
360    pub supports_window_exclude: bool,
361    /// UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
362    pub unnest_with_ordinality: bool,
363    /// Whether window frame keywords (ROWS/RANGE/GROUPS, PRECEDING/FOLLOWING) should be lowercase
364    /// Exasol uses lowercase for these specific keywords
365    pub lowercase_window_frame_keywords: bool,
366    /// Whether to normalize single-bound window frames to BETWEEN form
367    /// e.g., ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
368    pub normalize_window_frame_between: bool,
369
370    // ===== Array =====
371    /// Whether ARRAY_CONCAT can be generated with varlen args
372    pub array_concat_is_var_len: bool,
373    /// Whether exp.ArraySize should generate the dimension arg too
374    /// None -> Doesn't support, false -> optional, true -> required
375    pub array_size_dim_required: Option<bool>,
376    /// Whether any(f(x) for x in array) can be implemented
377    pub can_implement_array_any: bool,
378    /// Function used for array size
379    pub array_size_name: &'static str,
380
381    // ===== BETWEEN =====
382    /// Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN
383    pub supports_between_flags: bool,
384
385    // ===== Boolean =====
386    /// Whether comparing against booleans (e.g. x IS TRUE) is supported
387    pub is_bool_allowed: bool,
388    /// Whether conditions require booleans WHERE x = 0 vs WHERE x
389    pub ensure_bools: bool,
390
391    // ===== EXTRACT =====
392    /// Whether to generate an unquoted value for EXTRACT's date part argument
393    pub extract_allows_quotes: bool,
394    /// Whether to normalize date parts in EXTRACT
395    pub normalize_extract_date_parts: bool,
396
397    // ===== Other features =====
398    /// Whether the conditional TRY(expression) function is supported
399    pub try_supported: bool,
400    /// Whether the UESCAPE syntax in unicode strings is supported
401    pub supports_uescape: bool,
402    /// Whether the function TO_NUMBER is supported
403    pub supports_to_number: bool,
404    /// Whether CONCAT requires >1 arguments
405    pub supports_single_arg_concat: bool,
406    /// Whether LAST_DAY function supports a date part argument
407    pub last_day_supports_date_part: bool,
408    /// Whether a projection can explode into multiple rows
409    pub supports_exploding_projections: bool,
410    /// Whether UNIX_SECONDS(timestamp) is supported
411    pub supports_unix_seconds: bool,
412    /// Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
413    pub supports_like_quantifiers: bool,
414    /// Whether multi-argument DECODE(...) function is supported
415    pub supports_decode_case: bool,
416    /// Whether set op modifiers apply to the outer set op or select
417    pub set_op_modifiers: bool,
418    /// Whether FROM is supported in UPDATE statements
419    pub update_statement_supports_from: bool,
420
421    // ===== COLLATE =====
422    /// Whether COLLATE is a function instead of a binary operator
423    pub collate_is_func: bool,
424
425    // ===== INSERT =====
426    /// Whether to include "SET" keyword in "INSERT ... ON DUPLICATE KEY UPDATE"
427    pub duplicate_key_update_with_set: bool,
428    /// INSERT OVERWRITE TABLE x override
429    pub insert_overwrite: &'static str,
430
431    // ===== RETURNING =====
432    /// Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
433    pub returning_end: bool,
434
435    // ===== MERGE =====
436    /// Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
437    pub matched_by_source: bool,
438
439    // ===== CREATE FUNCTION =====
440    /// Whether create function uses an AS before the RETURN
441    pub create_function_return_as: bool,
442    /// Whether to use = instead of DEFAULT for parameter defaults (TSQL style)
443    pub parameter_default_equals: bool,
444
445    // ===== COMPUTED COLUMN =====
446    /// Whether to include the type of a computed column in the CREATE DDL
447    pub computed_column_with_type: bool,
448
449    // ===== UNPIVOT =====
450    /// Whether UNPIVOT aliases are Identifiers (false means they're Literals)
451    pub unpivot_aliases_are_identifiers: bool,
452
453    // ===== STAR =====
454    /// The keyword to use when generating a star projection with excluded columns
455    pub star_except: &'static str,
456
457    // ===== HEX =====
458    /// The HEX function name
459    pub hex_func: &'static str,
460
461    // ===== WITH =====
462    /// The keywords to use when prefixing WITH based properties
463    pub with_properties_prefix: &'static str,
464
465    // ===== PAD =====
466    /// Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional
467    pub pad_fill_pattern_is_required: bool,
468
469    // ===== INDEX =====
470    /// The string used for creating an index on a table
471    pub index_on: &'static str,
472
473    // ===== GROUPING =====
474    /// The separator for grouping sets and rollups
475    pub groupings_sep: &'static str,
476
477    // ===== STRUCT =====
478    /// Delimiters for STRUCT type
479    pub struct_delimiter: (&'static str, &'static str),
480    /// Whether Struct expressions use curly brace notation: {'key': value} (DuckDB)
481    pub struct_curly_brace_notation: bool,
482    /// Whether Array expressions omit the ARRAY keyword: [1, 2] instead of ARRAY[1, 2]
483    pub array_bracket_only: bool,
484    /// Separator between struct field name and type (": " for Hive, " " for others)
485    pub struct_field_sep: &'static str,
486
487    // ===== EXCEPT/INTERSECT =====
488    /// Whether EXCEPT and INTERSECT operations can return duplicates
489    pub except_intersect_support_all_clause: bool,
490
491    // ===== PARAMETERS/PLACEHOLDERS =====
492    /// Parameter token character (@ for TSQL, $ for PostgreSQL)
493    pub parameter_token: &'static str,
494    /// Named placeholder token (: for most, % for PostgreSQL)
495    pub named_placeholder_token: &'static str,
496
497    // ===== DATA TYPES =====
498    /// Whether data types support additional specifiers like CHAR or BYTE (oracle)
499    pub data_type_specifiers_allowed: bool,
500
501    // ===== COMMENT =====
502    /// Whether schema comments use `=` sign (COMMENT='value' vs COMMENT 'value')
503    /// StarRocks and Doris use naked COMMENT syntax without `=`
504    pub schema_comment_with_eq: bool,
505}
506
507impl Default for GeneratorConfig {
508    fn default() -> Self {
509        Self {
510            // ===== Basic formatting =====
511            pretty: false,
512            indent: "  ",
513            max_text_width: 80,
514            identifier_quote: '"',
515            identifier_quote_style: IdentifierQuoteStyle::DOUBLE_QUOTE,
516            uppercase_keywords: true,
517            normalize_identifiers: false,
518            dialect: None,
519            source_dialect: None,
520            unsupported_level: UnsupportedLevel::Warn,
521            max_unsupported: 3,
522            complexity_guard: ComplexityGuardOptions::default(),
523            normalize_functions: NormalizeFunctions::Upper,
524            string_escape: '\'',
525            case_sensitive_identifiers: false,
526            identifiers_can_start_with_digit: false,
527            always_quote_identifiers: false,
528            not_in_style: NotInStyle::Prefix,
529
530            // ===== Null handling =====
531            null_ordering_supported: true,
532            ignore_nulls_in_func: false,
533            nvl2_supported: true,
534
535            // ===== Limit/Fetch =====
536            limit_fetch_style: LimitFetchStyle::Limit,
537            limit_is_top: false,
538            limit_only_literals: false,
539
540            // ===== Interval =====
541            single_string_interval: false,
542            interval_allows_plural_form: true,
543
544            // ===== CTE =====
545            cte_recursive_keyword_required: true,
546
547            // ===== VALUES =====
548            values_as_table: true,
549            wrap_derived_values: true,
550
551            // ===== TABLESAMPLE =====
552            tablesample_seed_keyword: "SEED",
553            tablesample_requires_parens: true,
554            tablesample_size_is_rows: true,
555            tablesample_keywords: "TABLESAMPLE",
556            tablesample_with_method: true,
557            alias_post_tablesample: false,
558
559            // ===== Aggregate =====
560            aggregate_filter_supported: true,
561            multi_arg_distinct: true,
562            quantified_no_paren_space: false,
563            supports_median: true,
564
565            // ===== SELECT =====
566            supports_select_into: false,
567            locking_reads_supported: true,
568
569            // ===== Table/Join =====
570            rename_table_with_db: true,
571            semi_anti_join_with_side: true,
572            supports_table_alias_columns: true,
573            join_hints: true,
574            table_hints: true,
575            query_hints: true,
576            query_hint_sep: ", ",
577            supports_column_join_marks: false,
578
579            // ===== DDL =====
580            index_using_no_space: false,
581            supports_unlogged_tables: false,
582            supports_create_table_like: true,
583            like_property_inside_schema: false,
584            alter_table_include_column_keyword: true,
585            supports_table_copy: true,
586            alter_set_type: "SET DATA TYPE",
587            alter_set_wrapped: false,
588
589            // ===== Timestamp/Timezone =====
590            tz_to_with_time_zone: false,
591            supports_convert_timezone: false,
592
593            // ===== JSON =====
594            json_type_required_for_extraction: false,
595            json_path_bracketed_key_supported: true,
596            json_path_single_quote_escape: false,
597            quote_json_path: true,
598            json_key_value_pair_sep: ":",
599
600            // ===== COPY =====
601            copy_params_are_wrapped: true,
602            copy_params_eq_required: false,
603            copy_has_into_keyword: true,
604
605            // ===== Window functions =====
606            supports_window_exclude: false,
607            unnest_with_ordinality: true,
608            lowercase_window_frame_keywords: false,
609            normalize_window_frame_between: false,
610
611            // ===== Array =====
612            array_concat_is_var_len: true,
613            array_size_dim_required: None,
614            can_implement_array_any: false,
615            array_size_name: "ARRAY_LENGTH",
616
617            // ===== BETWEEN =====
618            supports_between_flags: false,
619
620            // ===== Boolean =====
621            is_bool_allowed: true,
622            ensure_bools: false,
623
624            // ===== EXTRACT =====
625            extract_allows_quotes: true,
626            normalize_extract_date_parts: false,
627
628            // ===== Other features =====
629            try_supported: true,
630            supports_uescape: true,
631            supports_to_number: true,
632            supports_single_arg_concat: true,
633            last_day_supports_date_part: true,
634            supports_exploding_projections: true,
635            supports_unix_seconds: false,
636            supports_like_quantifiers: true,
637            supports_decode_case: true,
638            set_op_modifiers: true,
639            update_statement_supports_from: true,
640
641            // ===== COLLATE =====
642            collate_is_func: false,
643
644            // ===== INSERT =====
645            duplicate_key_update_with_set: true,
646            insert_overwrite: " OVERWRITE TABLE",
647
648            // ===== RETURNING =====
649            returning_end: true,
650
651            // ===== MERGE =====
652            matched_by_source: true,
653
654            // ===== CREATE FUNCTION =====
655            create_function_return_as: true,
656            parameter_default_equals: false,
657
658            // ===== COMPUTED COLUMN =====
659            computed_column_with_type: true,
660
661            // ===== UNPIVOT =====
662            unpivot_aliases_are_identifiers: true,
663
664            // ===== STAR =====
665            star_except: "EXCEPT",
666
667            // ===== HEX =====
668            hex_func: "HEX",
669
670            // ===== WITH =====
671            with_properties_prefix: "WITH",
672
673            // ===== PAD =====
674            pad_fill_pattern_is_required: false,
675
676            // ===== INDEX =====
677            index_on: "ON",
678
679            // ===== GROUPING =====
680            groupings_sep: ",",
681
682            // ===== STRUCT =====
683            struct_delimiter: ("<", ">"),
684            struct_curly_brace_notation: false,
685            array_bracket_only: false,
686            struct_field_sep: " ",
687
688            // ===== EXCEPT/INTERSECT =====
689            except_intersect_support_all_clause: true,
690
691            // ===== PARAMETERS/PLACEHOLDERS =====
692            parameter_token: "@",
693            named_placeholder_token: ":",
694
695            // ===== DATA TYPES =====
696            data_type_specifiers_allowed: false,
697
698            // ===== COMMENT =====
699            schema_comment_with_eq: true,
700        }
701    }
702}
703
704/// SQL reserved keywords that require quoting when used as identifiers
705/// Based on ANSI SQL standards and common dialect-specific reserved words
706mod reserved_keywords {
707    use std::collections::HashSet;
708    use std::sync::LazyLock;
709
710    /// Standard SQL reserved keywords (ANSI SQL:2016)
711    pub static SQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
712        [
713            "all",
714            "alter",
715            "and",
716            "any",
717            "array",
718            "as",
719            "asc",
720            "at",
721            "authorization",
722            "begin",
723            "between",
724            "both",
725            "by",
726            "case",
727            "cast",
728            "check",
729            "collate",
730            "column",
731            "commit",
732            "constraint",
733            "create",
734            "cross",
735            "cube",
736            "current",
737            "current_date",
738            "current_time",
739            "current_timestamp",
740            "current_user",
741            "default",
742            "delete",
743            "desc",
744            "distinct",
745            "drop",
746            "else",
747            "end",
748            "escape",
749            "except",
750            "execute",
751            "exists",
752            "external",
753            "false",
754            "fetch",
755            "filter",
756            "for",
757            "foreign",
758            "from",
759            "full",
760            "function",
761            "grant",
762            "group",
763            "grouping",
764            "having",
765            "if",
766            "in",
767            "index",
768            "inner",
769            "insert",
770            "intersect",
771            "interval",
772            "into",
773            "is",
774            "join",
775            "key",
776            "leading",
777            "left",
778            "like",
779            "limit",
780            "local",
781            "localtime",
782            "localtimestamp",
783            "match",
784            "merge",
785            "natural",
786            "no",
787            "not",
788            "null",
789            "of",
790            "offset",
791            "on",
792            "only",
793            "or",
794            "order",
795            "outer",
796            "over",
797            "partition",
798            "primary",
799            "procedure",
800            "range",
801            "references",
802            "right",
803            "rollback",
804            "rollup",
805            "row",
806            "rows",
807            "select",
808            "session_user",
809            "set",
810            "some",
811            "table",
812            "tablesample",
813            "then",
814            "to",
815            "trailing",
816            "true",
817            "truncate",
818            "union",
819            "unique",
820            "unknown",
821            "update",
822            "user",
823            "using",
824            "values",
825            "view",
826            "when",
827            "where",
828            "window",
829            "with",
830        ]
831        .into_iter()
832        .collect()
833    });
834
835    /// BigQuery-specific reserved keywords
836    /// Based on: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords
837    pub static BIGQUERY_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
838        let mut set = SQL_RESERVED.clone();
839        set.extend([
840            "assert_rows_modified",
841            "at",
842            "contains",
843            "cube",
844            "current",
845            "define",
846            "enum",
847            "escape",
848            "exclude",
849            "following",
850            "for",
851            "groups",
852            "hash",
853            "ignore",
854            "lateral",
855            "lookup",
856            "new",
857            "no",
858            "nulls",
859            "of",
860            "over",
861            "preceding",
862            "proto",
863            "qualify",
864            "recursive",
865            "respect",
866            "struct",
867            "tablesample",
868            "treat",
869            "unbounded",
870            "unnest",
871            "window",
872            "within",
873        ]);
874        // BigQuery does NOT reserve these keywords - they can be used as identifiers unquoted
875        set.remove("grant");
876        set.remove("key");
877        set.remove("index");
878        set.remove("offset");
879        set.remove("values");
880        set.remove("table");
881        set
882    });
883
884    /// MySQL-specific reserved keywords
885    pub static MYSQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
886        let mut set = SQL_RESERVED.clone();
887        set.extend([
888            "accessible",
889            "add",
890            "analyze",
891            "asensitive",
892            "before",
893            "bigint",
894            "binary",
895            "blob",
896            "call",
897            "cascade",
898            "change",
899            "char",
900            "character",
901            "condition",
902            "continue",
903            "convert",
904            "current_date",
905            "current_time",
906            "current_timestamp",
907            "current_user",
908            "cursor",
909            "database",
910            "databases",
911            "day_hour",
912            "day_microsecond",
913            "day_minute",
914            "day_second",
915            "dec",
916            "decimal",
917            "declare",
918            "delayed",
919            "describe",
920            "deterministic",
921            "distinctrow",
922            "div",
923            "double",
924            "dual",
925            "each",
926            "elseif",
927            "enclosed",
928            "escaped",
929            "exit",
930            "explain",
931            "float",
932            "float4",
933            "float8",
934            "force",
935            "get",
936            "high_priority",
937            "hour_microsecond",
938            "hour_minute",
939            "hour_second",
940            "ignore",
941            "infile",
942            "inout",
943            "insensitive",
944            "int",
945            "int1",
946            "int2",
947            "int3",
948            "int4",
949            "int8",
950            "integer",
951            "iterate",
952            "keys",
953            "kill",
954            "leave",
955            "linear",
956            "lines",
957            "load",
958            "lock",
959            "long",
960            "longblob",
961            "longtext",
962            "loop",
963            "low_priority",
964            "master_ssl_verify_server_cert",
965            "maxvalue",
966            "mediumblob",
967            "mediumint",
968            "mediumtext",
969            "middleint",
970            "minute_microsecond",
971            "minute_second",
972            "mod",
973            "modifies",
974            "no_write_to_binlog",
975            "numeric",
976            "optimize",
977            "option",
978            "optionally",
979            "out",
980            "outfile",
981            "precision",
982            "purge",
983            "read",
984            "reads",
985            "real",
986            "regexp",
987            "release",
988            "rename",
989            "repeat",
990            "replace",
991            "require",
992            "resignal",
993            "restrict",
994            "return",
995            "revoke",
996            "rlike",
997            "schema",
998            "schemas",
999            "second_microsecond",
1000            "sensitive",
1001            "separator",
1002            "show",
1003            "signal",
1004            "smallint",
1005            "spatial",
1006            "specific",
1007            "sql",
1008            "sql_big_result",
1009            "sql_calc_found_rows",
1010            "sql_small_result",
1011            "sqlexception",
1012            "sqlstate",
1013            "sqlwarning",
1014            "ssl",
1015            "starting",
1016            "straight_join",
1017            "terminated",
1018            "text",
1019            "tinyblob",
1020            "tinyint",
1021            "tinytext",
1022            "trigger",
1023            "undo",
1024            "unlock",
1025            "unsigned",
1026            "usage",
1027            "utc_date",
1028            "utc_time",
1029            "utc_timestamp",
1030            "varbinary",
1031            "varchar",
1032            "varcharacter",
1033            "varying",
1034            "while",
1035            "write",
1036            "xor",
1037            "year_month",
1038            "zerofill",
1039        ]);
1040        set.remove("table");
1041        set
1042    });
1043
1044    /// Doris-specific reserved keywords
1045    /// Extends MySQL reserved with additional Doris-specific words
1046    pub static DORIS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1047        let mut set = MYSQL_RESERVED.clone();
1048        set.extend([
1049            "aggregate",
1050            "anti",
1051            "array",
1052            "backend",
1053            "backup",
1054            "begin",
1055            "bitmap",
1056            "boolean",
1057            "broker",
1058            "buckets",
1059            "cached",
1060            "cancel",
1061            "cast",
1062            "catalog",
1063            "charset",
1064            "cluster",
1065            "collation",
1066            "columns",
1067            "comment",
1068            "commit",
1069            "config",
1070            "connection",
1071            "count",
1072            "current",
1073            "data",
1074            "date",
1075            "datetime",
1076            "day",
1077            "deferred",
1078            "distributed",
1079            "dynamic",
1080            "enable",
1081            "end",
1082            "events",
1083            "export",
1084            "external",
1085            "fields",
1086            "first",
1087            "follower",
1088            "format",
1089            "free",
1090            "frontend",
1091            "full",
1092            "functions",
1093            "global",
1094            "grants",
1095            "hash",
1096            "help",
1097            "hour",
1098            "install",
1099            "intermediate",
1100            "json",
1101            "label",
1102            "last",
1103            "less",
1104            "level",
1105            "link",
1106            "local",
1107            "location",
1108            "max",
1109            "merge",
1110            "min",
1111            "minute",
1112            "modify",
1113            "month",
1114            "name",
1115            "names",
1116            "negative",
1117            "nulls",
1118            "observer",
1119            "offset",
1120            "only",
1121            "open",
1122            "overwrite",
1123            "password",
1124            "path",
1125            "plan",
1126            "plugin",
1127            "plugins",
1128            "policy",
1129            "process",
1130            "properties",
1131            "property",
1132            "query",
1133            "quota",
1134            "recover",
1135            "refresh",
1136            "repair",
1137            "replica",
1138            "repository",
1139            "resource",
1140            "restore",
1141            "resume",
1142            "role",
1143            "roles",
1144            "rollback",
1145            "rollup",
1146            "routine",
1147            "sample",
1148            "second",
1149            "semi",
1150            "session",
1151            "signed",
1152            "snapshot",
1153            "start",
1154            "stats",
1155            "status",
1156            "stop",
1157            "stream",
1158            "string",
1159            "sum",
1160            "tables",
1161            "tablet",
1162            "temporary",
1163            "text",
1164            "timestamp",
1165            "transaction",
1166            "trash",
1167            "trim",
1168            "truncate",
1169            "type",
1170            "user",
1171            "value",
1172            "variables",
1173            "verbose",
1174            "version",
1175            "view",
1176            "warnings",
1177            "week",
1178            "work",
1179            "year",
1180        ]);
1181        set
1182    });
1183
1184    /// PostgreSQL-specific reserved keywords
1185    pub static POSTGRES_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1186        let mut set = SQL_RESERVED.clone();
1187        set.extend([
1188            "analyse",
1189            "analyze",
1190            "asymmetric",
1191            "binary",
1192            "collation",
1193            "concurrently",
1194            "current_catalog",
1195            "current_role",
1196            "current_schema",
1197            "deferrable",
1198            "do",
1199            "freeze",
1200            "ilike",
1201            "initially",
1202            "isnull",
1203            "lateral",
1204            "notnull",
1205            "placing",
1206            "returning",
1207            "similar",
1208            "symmetric",
1209            "variadic",
1210            "verbose",
1211        ]);
1212        // PostgreSQL doesn't require quoting for these keywords
1213        set.remove("default");
1214        set.remove("interval");
1215        set.remove("match");
1216        set.remove("offset");
1217        set.remove("table");
1218        set
1219    });
1220
1221    /// Redshift-specific reserved keywords
1222    /// Based on: https://docs.aws.amazon.com/redshift/latest/dg/r_pg_keywords.html
1223    /// Note: `index` is NOT reserved in Redshift (unlike PostgreSQL)
1224    pub static REDSHIFT_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1225        [
1226            "aes128",
1227            "aes256",
1228            "all",
1229            "allowoverwrite",
1230            "analyse",
1231            "analyze",
1232            "and",
1233            "any",
1234            "array",
1235            "as",
1236            "asc",
1237            "authorization",
1238            "az64",
1239            "backup",
1240            "between",
1241            "binary",
1242            "blanksasnull",
1243            "both",
1244            "bytedict",
1245            "bzip2",
1246            "case",
1247            "cast",
1248            "check",
1249            "collate",
1250            "column",
1251            "constraint",
1252            "create",
1253            "credentials",
1254            "cross",
1255            "current_date",
1256            "current_time",
1257            "current_timestamp",
1258            "current_user",
1259            "current_user_id",
1260            "default",
1261            "deferrable",
1262            "deflate",
1263            "defrag",
1264            "delta",
1265            "delta32k",
1266            "desc",
1267            "disable",
1268            "distinct",
1269            "do",
1270            "else",
1271            "emptyasnull",
1272            "enable",
1273            "encode",
1274            "encrypt",
1275            "encryption",
1276            "end",
1277            "except",
1278            "explicit",
1279            "false",
1280            "for",
1281            "foreign",
1282            "freeze",
1283            "from",
1284            "full",
1285            "globaldict256",
1286            "globaldict64k",
1287            "grant",
1288            "group",
1289            "gzip",
1290            "having",
1291            "identity",
1292            "ignore",
1293            "ilike",
1294            "in",
1295            "initially",
1296            "inner",
1297            "intersect",
1298            "interval",
1299            "into",
1300            "is",
1301            "isnull",
1302            "join",
1303            "leading",
1304            "left",
1305            "like",
1306            "limit",
1307            "localtime",
1308            "localtimestamp",
1309            "lun",
1310            "luns",
1311            "lzo",
1312            "lzop",
1313            "minus",
1314            "mostly16",
1315            "mostly32",
1316            "mostly8",
1317            "natural",
1318            "new",
1319            "not",
1320            "notnull",
1321            "null",
1322            "nulls",
1323            "off",
1324            "offline",
1325            "offset",
1326            "oid",
1327            "old",
1328            "on",
1329            "only",
1330            "open",
1331            "or",
1332            "order",
1333            "outer",
1334            "overlaps",
1335            "parallel",
1336            "partition",
1337            "percent",
1338            "permissions",
1339            "pivot",
1340            "placing",
1341            "primary",
1342            "raw",
1343            "readratio",
1344            "recover",
1345            "references",
1346            "rejectlog",
1347            "resort",
1348            "respect",
1349            "restore",
1350            "right",
1351            "select",
1352            "session_user",
1353            "similar",
1354            "snapshot",
1355            "some",
1356            "sysdate",
1357            "system",
1358            "table",
1359            "tag",
1360            "tdes",
1361            "text255",
1362            "text32k",
1363            "then",
1364            "timestamp",
1365            "to",
1366            "top",
1367            "trailing",
1368            "true",
1369            "truncatecolumns",
1370            "type",
1371            "union",
1372            "unique",
1373            "unnest",
1374            "unpivot",
1375            "user",
1376            "using",
1377            "verbose",
1378            "wallet",
1379            "when",
1380            "where",
1381            "with",
1382            "without",
1383        ]
1384        .into_iter()
1385        .collect()
1386    });
1387
1388    /// DuckDB-specific reserved keywords
1389    pub static DUCKDB_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1390        let mut set = POSTGRES_RESERVED.clone();
1391        set.extend([
1392            "anti",
1393            "asof",
1394            "columns",
1395            "describe",
1396            "groups",
1397            "macro",
1398            "pivot",
1399            "pivot_longer",
1400            "pivot_wider",
1401            "qualify",
1402            "replace",
1403            "respect",
1404            "semi",
1405            "show",
1406            "table",
1407            "unpivot",
1408        ]);
1409        set.remove("at");
1410        set.remove("key");
1411        set.remove("range");
1412        set.remove("row");
1413        set.remove("values");
1414        set
1415    });
1416
1417    /// Presto/Trino/Athena-specific reserved keywords
1418    pub static PRESTO_TRINO_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1419        let mut set = SQL_RESERVED.clone();
1420        set.extend([
1421            "alter",
1422            "and",
1423            "as",
1424            "between",
1425            "by",
1426            "case",
1427            "cast",
1428            "constraint",
1429            "create",
1430            "cross",
1431            "cube",
1432            "current_catalog",
1433            "current_date",
1434            "current_path",
1435            "current_role",
1436            "current_schema",
1437            "current_time",
1438            "current_timestamp",
1439            "current_user",
1440            "deallocate",
1441            "delete",
1442            "describe",
1443            "distinct",
1444            "drop",
1445            "else",
1446            "end",
1447            "escape",
1448            "except",
1449            "execute",
1450            "exists",
1451            "extract",
1452            "false",
1453            "for",
1454            "from",
1455            "full",
1456            "group",
1457            "grouping",
1458            "having",
1459            "in",
1460            "inner",
1461            "insert",
1462            "intersect",
1463            "into",
1464            "is",
1465            "join",
1466            "json_array",
1467            "json_exists",
1468            "json_object",
1469            "json_query",
1470            "json_table",
1471            "json_value",
1472            "left",
1473            "like",
1474            "listagg",
1475            "localtime",
1476            "localtimestamp",
1477            "natural",
1478            "normalize",
1479            "not",
1480            "null",
1481            "on",
1482            "or",
1483            "order",
1484            "outer",
1485            "prepare",
1486            "recursive",
1487            "right",
1488            "rollup",
1489            "select",
1490            "skip",
1491            "table",
1492            "then",
1493            "trim",
1494            "true",
1495            "uescape",
1496            "union",
1497            "unnest",
1498            "using",
1499            "values",
1500            "when",
1501            "where",
1502            "with",
1503        ]);
1504        // Match sqlglot behavior for Presto/Trino: KEY does not require identifier quoting.
1505        set.remove("key");
1506        set
1507    });
1508
1509    /// StarRocks-specific reserved keywords
1510    /// Based on: https://docs.starrocks.io/docs/sql-reference/sql-statements/keywords/#reserved-keywords
1511    pub static STARROCKS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1512        [
1513            "add",
1514            "all",
1515            "alter",
1516            "analyze",
1517            "and",
1518            "array",
1519            "as",
1520            "asc",
1521            "between",
1522            "bigint",
1523            "bitmap",
1524            "both",
1525            "by",
1526            "case",
1527            "char",
1528            "character",
1529            "check",
1530            "collate",
1531            "column",
1532            "compaction",
1533            "convert",
1534            "create",
1535            "cross",
1536            "cube",
1537            "current_date",
1538            "current_role",
1539            "current_time",
1540            "current_timestamp",
1541            "current_user",
1542            "database",
1543            "databases",
1544            "decimal",
1545            "decimalv2",
1546            "decimal32",
1547            "decimal64",
1548            "decimal128",
1549            "default",
1550            "deferred",
1551            "delete",
1552            "dense_rank",
1553            "desc",
1554            "describe",
1555            "distinct",
1556            "double",
1557            "drop",
1558            "dual",
1559            "else",
1560            "except",
1561            "exists",
1562            "explain",
1563            "false",
1564            "first_value",
1565            "float",
1566            "for",
1567            "force",
1568            "from",
1569            "full",
1570            "function",
1571            "grant",
1572            "group",
1573            "grouping",
1574            "grouping_id",
1575            "groups",
1576            "having",
1577            "hll",
1578            "host",
1579            "if",
1580            "ignore",
1581            "immediate",
1582            "in",
1583            "index",
1584            "infile",
1585            "inner",
1586            "insert",
1587            "int",
1588            "integer",
1589            "intersect",
1590            "into",
1591            "is",
1592            "join",
1593            "json",
1594            "key",
1595            "keys",
1596            "kill",
1597            "lag",
1598            "largeint",
1599            "last_value",
1600            "lateral",
1601            "lead",
1602            "left",
1603            "like",
1604            "limit",
1605            "load",
1606            "localtime",
1607            "localtimestamp",
1608            "maxvalue",
1609            "minus",
1610            "mod",
1611            "not",
1612            "ntile",
1613            "null",
1614            "on",
1615            "or",
1616            "order",
1617            "outer",
1618            "outfile",
1619            "over",
1620            "partition",
1621            "percentile",
1622            "primary",
1623            "procedure",
1624            "qualify",
1625            "range",
1626            "rank",
1627            "read",
1628            "regexp",
1629            "release",
1630            "rename",
1631            "replace",
1632            "revoke",
1633            "right",
1634            "rlike",
1635            "row",
1636            "row_number",
1637            "rows",
1638            "schema",
1639            "schemas",
1640            "select",
1641            "set",
1642            "set_var",
1643            "show",
1644            "smallint",
1645            "system",
1646            "table",
1647            "terminated",
1648            "text",
1649            "then",
1650            "tinyint",
1651            "to",
1652            "true",
1653            "union",
1654            "unique",
1655            "unsigned",
1656            "update",
1657            "use",
1658            "using",
1659            "values",
1660            "varchar",
1661            "when",
1662            "where",
1663            "with",
1664        ]
1665        .into_iter()
1666        .collect()
1667    });
1668
1669    /// SingleStore-specific reserved keywords
1670    /// Based on: https://docs.singlestore.com/cloud/reference/sql-reference/restricted-keywords/list-of-restricted-keywords/
1671    pub static SINGLESTORE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1672        let mut set = MYSQL_RESERVED.clone();
1673        set.extend([
1674            // Additional SingleStore reserved keywords from Python sqlglot
1675            // NOTE: "all" is excluded because ORDER BY ALL needs ALL unquoted
1676            "abs",
1677            "account",
1678            "acos",
1679            "adddate",
1680            "addtime",
1681            "admin",
1682            "aes_decrypt",
1683            "aes_encrypt",
1684            "aggregate",
1685            "aggregates",
1686            "aggregator",
1687            "anti_join",
1688            "any_value",
1689            "approx_count_distinct",
1690            "approx_percentile",
1691            "arrange",
1692            "arrangement",
1693            "asin",
1694            "atan",
1695            "atan2",
1696            "attach",
1697            "autostats",
1698            "avro",
1699            "background",
1700            "backup",
1701            "batch",
1702            "batches",
1703            "boot_strapping",
1704            "ceil",
1705            "ceiling",
1706            "coercibility",
1707            "columnar",
1708            "columnstore",
1709            "compile",
1710            "concurrent",
1711            "connection_id",
1712            "cos",
1713            "cot",
1714            "current_security_groups",
1715            "current_security_roles",
1716            "dayname",
1717            "dayofmonth",
1718            "dayofweek",
1719            "dayofyear",
1720            "degrees",
1721            "dot_product",
1722            "dump",
1723            "durability",
1724            "earliest",
1725            "echo",
1726            "election",
1727            "euclidean_distance",
1728            "exp",
1729            "extractor",
1730            "extractors",
1731            "floor",
1732            "foreground",
1733            "found_rows",
1734            "from_base64",
1735            "from_days",
1736            "from_unixtime",
1737            "fs",
1738            "fulltext",
1739            "gc",
1740            "gcs",
1741            "geography",
1742            "geography_area",
1743            "geography_contains",
1744            "geography_distance",
1745            "geography_intersects",
1746            "geography_latitude",
1747            "geography_length",
1748            "geography_longitude",
1749            "geographypoint",
1750            "geography_point",
1751            "geography_within_distance",
1752            "geometry",
1753            "geometry_area",
1754            "geometry_contains",
1755            "geometry_distance",
1756            "geometry_filter",
1757            "geometry_intersects",
1758            "geometry_length",
1759            "geometrypoint",
1760            "geometry_point",
1761            "geometry_within_distance",
1762            "geometry_x",
1763            "geometry_y",
1764            "greatest",
1765            "groups",
1766            "group_concat",
1767            "gzip",
1768            "hdfs",
1769            "hex",
1770            "highlight",
1771            "ifnull",
1772            "ilike",
1773            "inet_aton",
1774            "inet_ntoa",
1775            "inet6_aton",
1776            "inet6_ntoa",
1777            "initcap",
1778            "instr",
1779            "interpreter_mode",
1780            "isnull",
1781            "json",
1782            "json_agg",
1783            "json_array_contains_double",
1784            "json_array_contains_json",
1785            "json_array_contains_string",
1786            "json_delete_key",
1787            "json_extract_double",
1788            "json_extract_json",
1789            "json_extract_string",
1790            "json_extract_bigint",
1791            "json_get_type",
1792            "json_length",
1793            "json_set_double",
1794            "json_set_json",
1795            "json_set_string",
1796            "kafka",
1797            "lag",
1798            "last_day",
1799            "last_insert_id",
1800            "latest",
1801            "lcase",
1802            "lead",
1803            "leaf",
1804            "least",
1805            "leaves",
1806            "length",
1807            "license",
1808            "links",
1809            "llvm",
1810            "ln",
1811            "load",
1812            "locate",
1813            "log",
1814            "log10",
1815            "log2",
1816            "lpad",
1817            "lz4",
1818            "management",
1819            "match",
1820            "mbc",
1821            "md5",
1822            "median",
1823            "memsql",
1824            "memsql_deserialize",
1825            "memsql_serialize",
1826            "metadata",
1827            "microsecond",
1828            "minute",
1829            "model",
1830            "monthname",
1831            "months_between",
1832            "mpl",
1833            "namespace",
1834            "node",
1835            "noparam",
1836            "now",
1837            "nth_value",
1838            "ntile",
1839            "nullcols",
1840            "nullif",
1841            "object",
1842            "octet_length",
1843            "offsets",
1844            "online",
1845            "optimizer",
1846            "orphan",
1847            "parquet",
1848            "partitions",
1849            "pause",
1850            "percentile_cont",
1851            "percentile_disc",
1852            "periodic",
1853            "persisted",
1854            "pi",
1855            "pipeline",
1856            "pipelines",
1857            "plancache",
1858            "plugins",
1859            "pool",
1860            "pools",
1861            "pow",
1862            "power",
1863            "process",
1864            "processlist",
1865            "profile",
1866            "profiles",
1867            "quarter",
1868            "queries",
1869            "query",
1870            "radians",
1871            "rand",
1872            "record",
1873            "reduce",
1874            "redundancy",
1875            "regexp_match",
1876            "regexp_substr",
1877            "remote",
1878            "replication",
1879            "resource",
1880            "resource_pool",
1881            "restore",
1882            "retry",
1883            "role",
1884            "roles",
1885            "round",
1886            "rpad",
1887            "rtrim",
1888            "running",
1889            "s3",
1890            "scalar",
1891            "sec_to_time",
1892            "second",
1893            "security_lists_intersect",
1894            "semi_join",
1895            "sha",
1896            "sha1",
1897            "sha2",
1898            "shard",
1899            "sharded",
1900            "sharded_id",
1901            "sigmoid",
1902            "sign",
1903            "sin",
1904            "skip",
1905            "sleep",
1906            "snapshot",
1907            "soname",
1908            "sparse",
1909            "spatial_check_index",
1910            "split",
1911            "sqrt",
1912            "standalone",
1913            "std",
1914            "stddev",
1915            "stddev_pop",
1916            "stddev_samp",
1917            "stop",
1918            "str_to_date",
1919            "subdate",
1920            "substr",
1921            "substring_index",
1922            "success",
1923            "synchronize",
1924            "table_checksum",
1925            "tan",
1926            "task",
1927            "timediff",
1928            "time_bucket",
1929            "time_format",
1930            "time_to_sec",
1931            "timestampadd",
1932            "timestampdiff",
1933            "to_base64",
1934            "to_char",
1935            "to_date",
1936            "to_days",
1937            "to_json",
1938            "to_number",
1939            "to_seconds",
1940            "to_timestamp",
1941            "tracelogs",
1942            "transform",
1943            "trim",
1944            "trunc",
1945            "truncate",
1946            "ucase",
1947            "unhex",
1948            "unix_timestamp",
1949            "utc_date",
1950            "utc_time",
1951            "utc_timestamp",
1952            "vacuum",
1953            "variance",
1954            "var_pop",
1955            "var_samp",
1956            "vector_sub",
1957            "voting",
1958            "week",
1959            "weekday",
1960            "weekofyear",
1961            "workload",
1962            "year",
1963        ]);
1964        // Remove "all" because ORDER BY ALL needs ALL unquoted
1965        set.remove("all");
1966        set
1967    });
1968
1969    /// SQLite-specific reserved keywords
1970    /// SQLite has a very minimal set of reserved keywords - most things can be used as identifiers unquoted
1971    /// Reference: https://www.sqlite.org/lang_keywords.html
1972    pub static SQLITE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1973        // SQLite only truly reserves these - everything else can be used as identifier unquoted
1974        [
1975            "abort",
1976            "action",
1977            "add",
1978            "after",
1979            "all",
1980            "alter",
1981            "always",
1982            "analyze",
1983            "and",
1984            "as",
1985            "asc",
1986            "attach",
1987            "autoincrement",
1988            "before",
1989            "begin",
1990            "between",
1991            "by",
1992            "cascade",
1993            "case",
1994            "cast",
1995            "check",
1996            "collate",
1997            "column",
1998            "commit",
1999            "conflict",
2000            "constraint",
2001            "create",
2002            "cross",
2003            "current",
2004            "current_date",
2005            "current_time",
2006            "current_timestamp",
2007            "database",
2008            "default",
2009            "deferrable",
2010            "deferred",
2011            "delete",
2012            "desc",
2013            "detach",
2014            "distinct",
2015            "do",
2016            "drop",
2017            "each",
2018            "else",
2019            "end",
2020            "escape",
2021            "except",
2022            "exclude",
2023            "exclusive",
2024            "exists",
2025            "explain",
2026            "fail",
2027            "filter",
2028            "first",
2029            "following",
2030            "for",
2031            "foreign",
2032            "from",
2033            "full",
2034            "generated",
2035            "glob",
2036            "group",
2037            "groups",
2038            "having",
2039            "if",
2040            "ignore",
2041            "immediate",
2042            "in",
2043            "index",
2044            "indexed",
2045            "initially",
2046            "inner",
2047            "insert",
2048            "instead",
2049            "intersect",
2050            "into",
2051            "is",
2052            "isnull",
2053            "join",
2054            "key",
2055            "last",
2056            "left",
2057            "like",
2058            "limit",
2059            "natural",
2060            "no",
2061            "not",
2062            "nothing",
2063            "notnull",
2064            "null",
2065            "nulls",
2066            "of",
2067            "offset",
2068            "on",
2069            "or",
2070            "order",
2071            "others",
2072            "outer",
2073            "partition",
2074            "plan",
2075            "pragma",
2076            "preceding",
2077            "primary",
2078            "query",
2079            "raise",
2080            "range",
2081            "recursive",
2082            "references",
2083            "regexp",
2084            "reindex",
2085            "release",
2086            "rename",
2087            "replace",
2088            "restrict",
2089            "returning",
2090            "right",
2091            "rollback",
2092            "row",
2093            "rows",
2094            "savepoint",
2095            "select",
2096            "set",
2097            "table",
2098            "temp",
2099            "temporary",
2100            "then",
2101            "ties",
2102            "to",
2103            "transaction",
2104            "trigger",
2105            "unbounded",
2106            "union",
2107            "unique",
2108            "update",
2109            "using",
2110            "vacuum",
2111            "values",
2112            "view",
2113            "virtual",
2114            "when",
2115            "where",
2116            "window",
2117            "with",
2118            "without",
2119        ]
2120        .into_iter()
2121        .collect()
2122    });
2123}
2124
2125impl Generator {
2126    /// Create a new generator with the default configuration.
2127    ///
2128    /// Equivalent to `Generator::with_config(GeneratorConfig::default())`.
2129    /// Uses uppercase keywords, double-quote identifier quoting, no pretty-printing,
2130    /// and no dialect-specific transformations.
2131    pub fn new() -> Self {
2132        Self::with_config(GeneratorConfig::default())
2133    }
2134
2135    /// Create a generator with a custom [`GeneratorConfig`].
2136    ///
2137    /// Use this when you need dialect-specific output, pretty-printing, or other
2138    /// non-default settings.
2139    pub fn with_config(config: GeneratorConfig) -> Self {
2140        Self::with_arc_config(Arc::new(config))
2141    }
2142
2143    /// Create a generator from a shared [`Arc<GeneratorConfig>`].
2144    ///
2145    /// This avoids cloning the configuration when multiple generators share the
2146    /// same settings (e.g. during transpilation). The [`Arc`] is cheap to clone.
2147    pub(crate) fn with_arc_config(config: Arc<GeneratorConfig>) -> Self {
2148        Self {
2149            config,
2150            output: String::new(),
2151            unsupported_messages: Vec::new(),
2152            indent_level: 0,
2153            athena_hive_context: false,
2154            sqlite_inline_pk_columns: std::collections::HashSet::new(),
2155            merge_strip_qualifiers: Vec::new(),
2156            clickhouse_nullable_depth: 0,
2157        }
2158    }
2159
2160    /// Add column aliases to a query expression for TSQL SELECT INTO.
2161    /// This ensures that unaliased columns get explicit aliases (e.g., `a` -> `a AS a`).
2162    /// Recursively processes all SELECT expressions in the query tree.
2163    fn add_column_aliases_to_query(expr: Expression) -> Expression {
2164        match expr {
2165            Expression::Select(mut select) => {
2166                // Add aliases to all select expressions that don't already have them
2167                select.expressions = select
2168                    .expressions
2169                    .into_iter()
2170                    .map(|e| Self::add_alias_to_expression(e))
2171                    .collect();
2172
2173                // Recursively process subqueries in FROM clause
2174                if let Some(ref mut from) = select.from {
2175                    from.expressions = from
2176                        .expressions
2177                        .iter()
2178                        .cloned()
2179                        .map(|e| Self::add_column_aliases_to_query(e))
2180                        .collect();
2181                }
2182
2183                Expression::Select(select)
2184            }
2185            Expression::Subquery(mut sq) => {
2186                sq.this = Self::add_column_aliases_to_query(sq.this);
2187                Expression::Subquery(sq)
2188            }
2189            Expression::Paren(mut p) => {
2190                p.this = Self::add_column_aliases_to_query(p.this);
2191                Expression::Paren(p)
2192            }
2193            // For other expressions (Union, Intersect, etc.), pass through
2194            other => other,
2195        }
2196    }
2197
2198    /// Add an alias to a single select expression if it doesn't already have one.
2199    /// Returns the expression with alias (e.g., `a` -> `a AS a`).
2200    fn add_alias_to_expression(expr: Expression) -> Expression {
2201        use crate::expressions::Alias;
2202
2203        match &expr {
2204            // Already aliased - just return it
2205            Expression::Alias(_) => expr,
2206
2207            // Column reference: add alias from column name
2208            Expression::Column(col) => Expression::Alias(Box::new(Alias {
2209                this: expr.clone(),
2210                alias: col.name.clone(),
2211                column_aliases: Vec::new(),
2212                alias_explicit_as: false,
2213                alias_keyword: None,
2214                pre_alias_comments: Vec::new(),
2215                trailing_comments: Vec::new(),
2216                inferred_type: None,
2217            })),
2218
2219            // Identifier: add alias from identifier name
2220            Expression::Identifier(ident) => Expression::Alias(Box::new(Alias {
2221                this: expr.clone(),
2222                alias: ident.clone(),
2223                column_aliases: Vec::new(),
2224                alias_explicit_as: false,
2225                alias_keyword: None,
2226                pre_alias_comments: Vec::new(),
2227                trailing_comments: Vec::new(),
2228                inferred_type: None,
2229            })),
2230
2231            // Subquery: recursively process and add alias if inner returns a named column
2232            Expression::Subquery(sq) => {
2233                let processed = Self::add_column_aliases_to_query(Expression::Subquery(sq.clone()));
2234                // Subqueries that are already aliased keep their alias
2235                if sq.alias.is_some() {
2236                    processed
2237                } else {
2238                    // If there's no alias, keep it as-is (let TSQL handle it)
2239                    processed
2240                }
2241            }
2242
2243            // Star expressions (*) - don't alias
2244            Expression::Star(_) => expr,
2245
2246            // For other expressions, don't add an alias
2247            // (function calls, literals, etc. would need explicit aliases anyway)
2248            _ => expr,
2249        }
2250    }
2251
2252    /// Try to evaluate a constant arithmetic expression to a number literal.
2253    /// Returns the evaluated result if the expression is a constant arithmetic expression,
2254    /// otherwise returns the original expression.
2255    fn try_evaluate_constant(expr: &Expression) -> Option<i64> {
2256        match expr {
2257            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
2258                let Literal::Number(n) = lit.as_ref() else {
2259                    unreachable!()
2260                };
2261                n.parse::<i64>().ok()
2262            }
2263            Expression::Add(op) => {
2264                let left = Self::try_evaluate_constant(&op.left)?;
2265                let right = Self::try_evaluate_constant(&op.right)?;
2266                Some(left + right)
2267            }
2268            Expression::Sub(op) => {
2269                let left = Self::try_evaluate_constant(&op.left)?;
2270                let right = Self::try_evaluate_constant(&op.right)?;
2271                Some(left - right)
2272            }
2273            Expression::Mul(op) => {
2274                let left = Self::try_evaluate_constant(&op.left)?;
2275                let right = Self::try_evaluate_constant(&op.right)?;
2276                Some(left * right)
2277            }
2278            Expression::Div(op) => {
2279                let left = Self::try_evaluate_constant(&op.left)?;
2280                let right = Self::try_evaluate_constant(&op.right)?;
2281                if right != 0 {
2282                    Some(left / right)
2283                } else {
2284                    None
2285                }
2286            }
2287            Expression::Paren(p) => Self::try_evaluate_constant(&p.this),
2288            _ => None,
2289        }
2290    }
2291
2292    /// Check if an identifier is a reserved keyword for the current dialect
2293    fn is_reserved_keyword(&self, name: &str) -> bool {
2294        use crate::dialects::DialectType;
2295        let mut buf = [0u8; 128];
2296        let lower_ref: &str = if name.len() <= 128 {
2297            for (i, b) in name.bytes().enumerate() {
2298                buf[i] = b.to_ascii_lowercase();
2299            }
2300            // SAFETY: input is valid UTF-8 and ASCII lowercase preserves that
2301            std::str::from_utf8(&buf[..name.len()]).unwrap_or(name)
2302        } else {
2303            return false;
2304        };
2305
2306        match self.config.dialect {
2307            Some(DialectType::BigQuery) => reserved_keywords::BIGQUERY_RESERVED.contains(lower_ref),
2308            Some(DialectType::MySQL) | Some(DialectType::TiDB) => {
2309                reserved_keywords::MYSQL_RESERVED.contains(lower_ref)
2310            }
2311            Some(DialectType::Doris) => reserved_keywords::DORIS_RESERVED.contains(lower_ref),
2312            Some(DialectType::SingleStore) => {
2313                reserved_keywords::SINGLESTORE_RESERVED.contains(lower_ref)
2314            }
2315            Some(DialectType::StarRocks) => {
2316                reserved_keywords::STARROCKS_RESERVED.contains(lower_ref)
2317            }
2318            Some(DialectType::PostgreSQL)
2319            | Some(DialectType::CockroachDB)
2320            | Some(DialectType::Materialize)
2321            | Some(DialectType::RisingWave) => {
2322                reserved_keywords::POSTGRES_RESERVED.contains(lower_ref)
2323            }
2324            Some(DialectType::Redshift) => reserved_keywords::REDSHIFT_RESERVED.contains(lower_ref),
2325            // Snowflake: Python sqlglot has RESERVED_KEYWORDS = set() for Snowflake,
2326            // meaning it never quotes identifiers based on reserved word status.
2327            Some(DialectType::Snowflake) => false,
2328            // ClickHouse: don't quote reserved keywords to preserve identity output
2329            Some(DialectType::ClickHouse) => false,
2330            Some(DialectType::DuckDB) => reserved_keywords::DUCKDB_RESERVED.contains(lower_ref),
2331            // Teradata: Python sqlglot has RESERVED_KEYWORDS = set() for Teradata
2332            Some(DialectType::Teradata) => false,
2333            // TSQL, Fabric, Oracle, Spark, Hive, Solr: Python sqlglot has no RESERVED_KEYWORDS for these dialects, so don't quote identifiers
2334            Some(DialectType::TSQL)
2335            | Some(DialectType::Fabric)
2336            | Some(DialectType::Oracle)
2337            | Some(DialectType::Spark)
2338            | Some(DialectType::Databricks)
2339            | Some(DialectType::Hive)
2340            | Some(DialectType::Solr) => false,
2341            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
2342                reserved_keywords::PRESTO_TRINO_RESERVED.contains(lower_ref)
2343            }
2344            Some(DialectType::SQLite) => reserved_keywords::SQLITE_RESERVED.contains(lower_ref),
2345            // For Generic dialect or None, don't add extra quoting to preserve identity
2346            Some(DialectType::Generic) | None => false,
2347            // For other dialects, use standard SQL reserved keywords
2348            _ => reserved_keywords::SQL_RESERVED.contains(lower_ref),
2349        }
2350    }
2351
2352    /// Normalize function name based on dialect settings
2353    fn normalize_func_name<'a>(&self, name: &'a str) -> Cow<'a, str> {
2354        match self.config.normalize_functions {
2355            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
2356            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
2357            NormalizeFunctions::None => Cow::Borrowed(name),
2358        }
2359    }
2360
2361    /// Generate a SQL string from an AST expression.
2362    ///
2363    /// This is the primary generation method. It clears any previous internal state,
2364    /// walks the expression tree, and returns the resulting SQL text. The output
2365    /// respects the [`GeneratorConfig`] that was supplied at construction time.
2366    ///
2367    /// The generator can be reused across multiple calls; each call to `generate`
2368    /// resets the internal buffer.
2369    pub fn generate(&mut self, expr: &Expression) -> Result<String> {
2370        self.output.clear();
2371        self.unsupported_messages.clear();
2372        enforce_generate_ast(expr, &self.config.complexity_guard)?;
2373        self.generate_expression(expr)?;
2374        if self.config.unsupported_level == UnsupportedLevel::Raise
2375            && !self.unsupported_messages.is_empty()
2376        {
2377            return Err(crate::error::Error::generate(
2378                self.format_unsupported_messages(),
2379            ));
2380        }
2381        Ok(std::mem::take(&mut self.output))
2382    }
2383
2384    /// Returns the unsupported diagnostics collected during the most recent generate call.
2385    pub fn unsupported_messages(&self) -> &[String] {
2386        &self.unsupported_messages
2387    }
2388
2389    fn unsupported(&mut self, message: impl Into<String>) -> Result<()> {
2390        let message = message.into();
2391        if self.config.unsupported_level == UnsupportedLevel::Immediate {
2392            return Err(crate::error::Error::generate(message));
2393        }
2394        self.unsupported_messages.push(message);
2395        Ok(())
2396    }
2397
2398    fn write_unsupported_comment(&mut self, message: &str) -> Result<()> {
2399        self.unsupported(message.to_string())?;
2400        self.write("/* ");
2401        self.write(message);
2402        self.write(" */");
2403        Ok(())
2404    }
2405
2406    fn format_unsupported_messages(&self) -> String {
2407        let limit = self.config.max_unsupported.max(1);
2408        if self.unsupported_messages.len() <= limit {
2409            return self.unsupported_messages.join("; ");
2410        }
2411
2412        let mut messages = self
2413            .unsupported_messages
2414            .iter()
2415            .take(limit)
2416            .cloned()
2417            .collect::<Vec<_>>();
2418        messages.push(format!(
2419            "... and {} more",
2420            self.unsupported_messages.len() - limit
2421        ));
2422        messages.join("; ")
2423    }
2424
2425    /// Convenience: generate SQL with the default configuration (no dialect, compact output).
2426    ///
2427    /// This is a static helper that creates a throwaway `Generator` internally.
2428    /// For repeated generation, prefer constructing a `Generator` once and calling
2429    /// [`generate`](Self::generate) on it.
2430    pub fn sql(expr: &Expression) -> Result<String> {
2431        let mut gen = Generator::new();
2432        gen.generate(expr)
2433    }
2434
2435    /// Convenience: generate SQL with pretty-printing enabled (indented, multi-line).
2436    ///
2437    /// Produces human-readable output with newlines and indentation. A trailing
2438    /// semicolon is appended automatically if not already present.
2439    pub fn pretty_sql(expr: &Expression) -> Result<String> {
2440        let config = GeneratorConfig {
2441            pretty: true,
2442            ..Default::default()
2443        };
2444        let mut gen = Generator::with_config(config);
2445        let mut sql = gen.generate(expr)?;
2446        // Add semicolon for pretty output
2447        if !sql.ends_with(';') {
2448            sql.push(';');
2449        }
2450        Ok(sql)
2451    }
2452
2453    fn generate_expression(&mut self, expr: &Expression) -> Result<()> {
2454        #[cfg(feature = "stacker")]
2455        {
2456            let red_zone = if cfg!(debug_assertions) {
2457                4 * 1024 * 1024
2458            } else {
2459                1024 * 1024
2460            };
2461            stacker::maybe_grow(red_zone, 8 * 1024 * 1024, || {
2462                self.generate_expression_inner(expr)
2463            })
2464        }
2465        #[cfg(not(feature = "stacker"))]
2466        {
2467            self.generate_expression_inner(expr)
2468        }
2469    }
2470
2471    fn generate_expression_inner(&mut self, expr: &Expression) -> Result<()> {
2472        match expr {
2473            Expression::Select(select) => self.generate_select(select),
2474            Expression::Union(union) => self.generate_union(union),
2475            Expression::Intersect(intersect) => self.generate_intersect(intersect),
2476            Expression::Except(except) => self.generate_except(except),
2477            Expression::Insert(insert) => self.generate_insert(insert),
2478            Expression::Update(update) => self.generate_update(update),
2479            Expression::Delete(delete) => self.generate_delete(delete),
2480            Expression::Literal(lit) => self.generate_literal(lit),
2481            Expression::Boolean(b) => self.generate_boolean(b),
2482            Expression::Null(_) => {
2483                self.write_keyword("NULL");
2484                Ok(())
2485            }
2486            Expression::Identifier(id) => self.generate_identifier(id),
2487            Expression::Column(col) => self.generate_column(col),
2488            Expression::Pseudocolumn(pc) => self.generate_pseudocolumn(pc),
2489            Expression::Connect(c) => self.generate_connect_expr(c),
2490            Expression::Prior(p) => self.generate_prior(p),
2491            Expression::ConnectByRoot(cbr) => self.generate_connect_by_root(cbr),
2492            Expression::MatchRecognize(mr) => self.generate_match_recognize(mr),
2493            Expression::Table(table) => self.generate_table(table),
2494            Expression::StageReference(sr) => self.generate_stage_reference(sr),
2495            Expression::HistoricalData(hd) => self.generate_historical_data(hd),
2496            Expression::JoinedTable(jt) => self.generate_joined_table(jt),
2497            Expression::Star(star) => self.generate_star(star),
2498            Expression::BracedWildcard(expr) => self.generate_braced_wildcard(expr),
2499            Expression::Alias(alias) => self.generate_alias(alias),
2500            Expression::Cast(cast) => self.generate_cast(cast),
2501            Expression::Collation(coll) => self.generate_collation(coll),
2502            Expression::Case(case) => self.generate_case(case),
2503            Expression::Function(func) => self.generate_function(func),
2504            Expression::FunctionEmits(fe) => self.generate_function_emits(fe),
2505            Expression::AggregateFunction(func) => self.generate_aggregate_function(func),
2506            Expression::WindowFunction(wf) => self.generate_window_function(wf),
2507            Expression::WithinGroup(wg) => self.generate_within_group(wg),
2508            Expression::Interval(interval) => self.generate_interval(interval),
2509
2510            // String functions
2511            Expression::ConcatWs(f) => self.generate_concat_ws(f),
2512            Expression::Substring(f) => self.generate_substring(f),
2513            Expression::Upper(f) => self.generate_unary_func("UPPER", f),
2514            Expression::Lower(f) => self.generate_unary_func("LOWER", f),
2515            Expression::Length(f) => self.generate_unary_func("LENGTH", f),
2516            Expression::Trim(f) => self.generate_trim(f),
2517            Expression::LTrim(f) => self.generate_simple_func("LTRIM", &f.this),
2518            Expression::RTrim(f) => self.generate_simple_func("RTRIM", &f.this),
2519            Expression::Replace(f) => self.generate_replace(f),
2520            Expression::Reverse(f) => self.generate_simple_func("REVERSE", &f.this),
2521            Expression::Left(f) => self.generate_left_right("LEFT", f),
2522            Expression::Right(f) => self.generate_left_right("RIGHT", f),
2523            Expression::Repeat(f) => self.generate_repeat(f),
2524            Expression::Lpad(f) => self.generate_pad("LPAD", f),
2525            Expression::Rpad(f) => self.generate_pad("RPAD", f),
2526            Expression::Split(f) => self.generate_split(f),
2527            Expression::RegexpLike(f) => self.generate_regexp_like(f),
2528            Expression::RegexpReplace(f) => self.generate_regexp_replace(f),
2529            Expression::RegexpExtract(f) => self.generate_regexp_extract(f),
2530            Expression::Overlay(f) => self.generate_overlay(f),
2531
2532            // Math functions
2533            Expression::Abs(f) => self.generate_simple_func("ABS", &f.this),
2534            Expression::Round(f) => self.generate_round(f),
2535            Expression::Floor(f) => self.generate_floor(f),
2536            Expression::Ceil(f) => self.generate_ceil(f),
2537            Expression::Power(f) => self.generate_power(f),
2538            Expression::Sqrt(f) => self.generate_sqrt_cbrt(f, "SQRT", "|/"),
2539            Expression::Cbrt(f) => self.generate_sqrt_cbrt(f, "CBRT", "||/"),
2540            Expression::Ln(f) => self.generate_simple_func("LN", &f.this),
2541            Expression::Log(f) => self.generate_log(f),
2542            Expression::Exp(f) => self.generate_simple_func("EXP", &f.this),
2543            Expression::Sign(f) => self.generate_simple_func("SIGN", &f.this),
2544            Expression::Greatest(f) => self.generate_vararg_func("GREATEST", &f.expressions),
2545            Expression::Least(f) => self.generate_vararg_func("LEAST", &f.expressions),
2546
2547            // Date/time functions
2548            Expression::CurrentDate(_) => {
2549                self.write_keyword("CURRENT_DATE");
2550                Ok(())
2551            }
2552            Expression::CurrentTime(f) => self.generate_current_time(f),
2553            Expression::CurrentTimestamp(f) => self.generate_current_timestamp(f),
2554            Expression::AtTimeZone(f) => self.generate_at_time_zone(f),
2555            Expression::DateAdd(f) => self.generate_date_add(f, "DATE_ADD"),
2556            Expression::DateSub(f) => self.generate_date_add(f, "DATE_SUB"),
2557            Expression::DateDiff(f) => self.generate_datediff(f),
2558            Expression::DateTrunc(f) => self.generate_date_trunc(f),
2559            Expression::Extract(f) => self.generate_extract(f),
2560            Expression::ToDate(f) => self.generate_to_date(f),
2561            Expression::ToTimestamp(f) => self.generate_to_timestamp(f),
2562
2563            // Control flow functions
2564            Expression::Coalesce(f) => {
2565                // Use original function name if preserved (COALESCE, IFNULL)
2566                let func_name = f.original_name.as_deref().unwrap_or("COALESCE");
2567                self.generate_vararg_func(func_name, &f.expressions)
2568            }
2569            Expression::NullIf(f) => self.generate_binary_func("NULLIF", &f.this, &f.expression),
2570            Expression::IfFunc(f) => self.generate_if_func(f),
2571            Expression::IfNull(f) => self.generate_ifnull(f),
2572            Expression::Nvl(f) => self.generate_nvl(f),
2573            Expression::Nvl2(f) => self.generate_nvl2(f),
2574
2575            // Type conversion
2576            Expression::TryCast(cast) => self.generate_try_cast(cast),
2577            Expression::SafeCast(cast) => self.generate_safe_cast(cast),
2578
2579            // Typed aggregate functions
2580            Expression::Count(f) => self.generate_count(f),
2581            Expression::Sum(f) => self.generate_agg_func("SUM", f),
2582            Expression::Avg(f) => self.generate_agg_func("AVG", f),
2583            Expression::Min(f) => self.generate_agg_func("MIN", f),
2584            Expression::Max(f) => self.generate_agg_func("MAX", f),
2585            Expression::GroupConcat(f) => self.generate_group_concat(f),
2586            Expression::StringAgg(f) => self.generate_string_agg(f),
2587            Expression::ListAgg(f) => self.generate_listagg(f),
2588            Expression::ArrayAgg(f) => {
2589                // Allow cross-dialect transforms to override the function name
2590                // (e.g., COLLECT_LIST for Spark)
2591                let override_name = f
2592                    .name
2593                    .as_ref()
2594                    .filter(|n| !n.eq_ignore_ascii_case("ARRAY_AGG"))
2595                    .map(|n| n.to_ascii_uppercase());
2596                match override_name {
2597                    Some(name) => self.generate_agg_func(&name, f),
2598                    None => self.generate_agg_func("ARRAY_AGG", f),
2599                }
2600            }
2601            Expression::ArrayConcatAgg(f) => self.generate_agg_func("ARRAY_CONCAT_AGG", f),
2602            Expression::CountIf(f) => self.generate_agg_func("COUNT_IF", f),
2603            Expression::SumIf(f) => self.generate_sum_if(f),
2604            Expression::Stddev(f) => self.generate_agg_func("STDDEV", f),
2605            Expression::StddevPop(f) => self.generate_agg_func("STDDEV_POP", f),
2606            Expression::StddevSamp(f) => self.generate_stddev_samp(f),
2607            Expression::Variance(f) => self.generate_agg_func("VARIANCE", f),
2608            Expression::VarPop(f) => {
2609                let name = if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
2610                    "VARIANCE_POP"
2611                } else {
2612                    "VAR_POP"
2613                };
2614                self.generate_agg_func(name, f)
2615            }
2616            Expression::VarSamp(f) => self.generate_agg_func("VAR_SAMP", f),
2617            Expression::Skewness(f) => {
2618                let name = match self.config.dialect {
2619                    Some(DialectType::Snowflake) => "SKEW",
2620                    _ => "SKEWNESS",
2621                };
2622                self.generate_agg_func(name, f)
2623            }
2624            Expression::Median(f) => self.generate_agg_func("MEDIAN", f),
2625            Expression::Mode(f) => self.generate_agg_func("MODE", f),
2626            Expression::First(f) => self.generate_agg_func_with_ignore_nulls_bool("FIRST", f),
2627            Expression::Last(f) => self.generate_agg_func_with_ignore_nulls_bool("LAST", f),
2628            Expression::AnyValue(f) => self.generate_agg_func("ANY_VALUE", f),
2629            Expression::ApproxDistinct(f) => {
2630                match self.config.dialect {
2631                    Some(DialectType::Hive)
2632                    | Some(DialectType::Spark)
2633                    | Some(DialectType::Databricks)
2634                    | Some(DialectType::BigQuery) => {
2635                        // These dialects use APPROX_COUNT_DISTINCT (single arg only)
2636                        self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2637                    }
2638                    Some(DialectType::Redshift) => {
2639                        // Redshift uses APPROXIMATE COUNT(DISTINCT expr)
2640                        self.write_keyword("APPROXIMATE COUNT");
2641                        self.write("(");
2642                        self.write_keyword("DISTINCT");
2643                        self.write(" ");
2644                        self.generate_expression(&f.this)?;
2645                        self.write(")");
2646                        Ok(())
2647                    }
2648                    _ => self.generate_agg_func("APPROX_DISTINCT", f),
2649                }
2650            }
2651            Expression::ApproxCountDistinct(f) => {
2652                self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2653            }
2654            Expression::ApproxPercentile(f) => self.generate_approx_percentile(f),
2655            Expression::Percentile(f) => self.generate_percentile("PERCENTILE", f),
2656            Expression::LogicalAnd(f) => {
2657                let name = match self.config.dialect {
2658                    Some(DialectType::Snowflake) => "BOOLAND_AGG",
2659                    Some(DialectType::Spark)
2660                    | Some(DialectType::Databricks)
2661                    | Some(DialectType::PostgreSQL)
2662                    | Some(DialectType::DuckDB)
2663                    | Some(DialectType::Redshift) => "BOOL_AND",
2664                    Some(DialectType::Oracle)
2665                    | Some(DialectType::SQLite)
2666                    | Some(DialectType::MySQL) => "MIN",
2667                    _ => "BOOL_AND",
2668                };
2669                self.generate_agg_func(name, f)
2670            }
2671            Expression::LogicalOr(f) => {
2672                let name = match self.config.dialect {
2673                    Some(DialectType::Snowflake) => "BOOLOR_AGG",
2674                    Some(DialectType::Spark)
2675                    | Some(DialectType::Databricks)
2676                    | Some(DialectType::PostgreSQL)
2677                    | Some(DialectType::DuckDB)
2678                    | Some(DialectType::Redshift) => "BOOL_OR",
2679                    Some(DialectType::Oracle)
2680                    | Some(DialectType::SQLite)
2681                    | Some(DialectType::MySQL) => "MAX",
2682                    _ => "BOOL_OR",
2683                };
2684                self.generate_agg_func(name, f)
2685            }
2686
2687            // Typed window functions
2688            Expression::RowNumber(_) => {
2689                if self.config.dialect == Some(DialectType::ClickHouse) {
2690                    self.write("row_number");
2691                } else {
2692                    self.write_keyword("ROW_NUMBER");
2693                }
2694                self.write("()");
2695                Ok(())
2696            }
2697            Expression::Rank(r) => {
2698                self.write_keyword("RANK");
2699                self.write("(");
2700                // Oracle hypothetical rank args: RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2701                if !r.args.is_empty() {
2702                    for (i, arg) in r.args.iter().enumerate() {
2703                        if i > 0 {
2704                            self.write(", ");
2705                        }
2706                        self.generate_expression(arg)?;
2707                    }
2708                } else if let Some(order_by) = &r.order_by {
2709                    // DuckDB: RANK(ORDER BY col)
2710                    self.write_keyword(" ORDER BY ");
2711                    for (i, ob) in order_by.iter().enumerate() {
2712                        if i > 0 {
2713                            self.write(", ");
2714                        }
2715                        self.generate_ordered(ob)?;
2716                    }
2717                }
2718                self.write(")");
2719                Ok(())
2720            }
2721            Expression::DenseRank(dr) => {
2722                self.write_keyword("DENSE_RANK");
2723                self.write("(");
2724                // Oracle hypothetical rank args: DENSE_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2725                for (i, arg) in dr.args.iter().enumerate() {
2726                    if i > 0 {
2727                        self.write(", ");
2728                    }
2729                    self.generate_expression(arg)?;
2730                }
2731                self.write(")");
2732                Ok(())
2733            }
2734            Expression::NTile(f) => self.generate_ntile(f),
2735            Expression::Lead(f) => self.generate_lead_lag("LEAD", f),
2736            Expression::Lag(f) => self.generate_lead_lag("LAG", f),
2737            Expression::FirstValue(f) => {
2738                self.generate_value_func_with_ignore_nulls_bool("FIRST_VALUE", f)
2739            }
2740            Expression::LastValue(f) => {
2741                self.generate_value_func_with_ignore_nulls_bool("LAST_VALUE", f)
2742            }
2743            Expression::NthValue(f) => self.generate_nth_value(f),
2744            Expression::PercentRank(pr) => {
2745                self.write_keyword("PERCENT_RANK");
2746                self.write("(");
2747                // Oracle hypothetical rank args: PERCENT_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2748                if !pr.args.is_empty() {
2749                    for (i, arg) in pr.args.iter().enumerate() {
2750                        if i > 0 {
2751                            self.write(", ");
2752                        }
2753                        self.generate_expression(arg)?;
2754                    }
2755                } else if let Some(order_by) = &pr.order_by {
2756                    // DuckDB: PERCENT_RANK(ORDER BY col)
2757                    self.write_keyword(" ORDER BY ");
2758                    for (i, ob) in order_by.iter().enumerate() {
2759                        if i > 0 {
2760                            self.write(", ");
2761                        }
2762                        self.generate_ordered(ob)?;
2763                    }
2764                }
2765                self.write(")");
2766                Ok(())
2767            }
2768            Expression::CumeDist(cd) => {
2769                self.write_keyword("CUME_DIST");
2770                self.write("(");
2771                // Oracle hypothetical rank args: CUME_DIST(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2772                if !cd.args.is_empty() {
2773                    for (i, arg) in cd.args.iter().enumerate() {
2774                        if i > 0 {
2775                            self.write(", ");
2776                        }
2777                        self.generate_expression(arg)?;
2778                    }
2779                } else if let Some(order_by) = &cd.order_by {
2780                    // DuckDB: CUME_DIST(ORDER BY col)
2781                    self.write_keyword(" ORDER BY ");
2782                    for (i, ob) in order_by.iter().enumerate() {
2783                        if i > 0 {
2784                            self.write(", ");
2785                        }
2786                        self.generate_ordered(ob)?;
2787                    }
2788                }
2789                self.write(")");
2790                Ok(())
2791            }
2792            Expression::PercentileCont(f) => self.generate_percentile("PERCENTILE_CONT", f),
2793            Expression::PercentileDisc(f) => self.generate_percentile("PERCENTILE_DISC", f),
2794
2795            // Additional string functions
2796            Expression::Contains(f) => {
2797                self.generate_binary_func("CONTAINS", &f.this, &f.expression)
2798            }
2799            Expression::StartsWith(f) => {
2800                let name = match self.config.dialect {
2801                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "STARTSWITH",
2802                    _ => "STARTS_WITH",
2803                };
2804                self.generate_binary_func(name, &f.this, &f.expression)
2805            }
2806            Expression::EndsWith(f) => {
2807                let name = match self.config.dialect {
2808                    Some(DialectType::Snowflake) => "ENDSWITH",
2809                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "ENDSWITH",
2810                    Some(DialectType::ClickHouse) => "endsWith",
2811                    _ => "ENDS_WITH",
2812                };
2813                self.generate_binary_func(name, &f.this, &f.expression)
2814            }
2815            Expression::Position(f) => self.generate_position(f),
2816            Expression::Initcap(f) => match self.config.dialect {
2817                Some(DialectType::Presto)
2818                | Some(DialectType::Trino)
2819                | Some(DialectType::Athena) => {
2820                    self.write_keyword("REGEXP_REPLACE");
2821                    self.write("(");
2822                    self.generate_expression(&f.this)?;
2823                    self.write(", '(\\w)(\\w*)', x -> UPPER(x[1]) || LOWER(x[2]))");
2824                    Ok(())
2825                }
2826                _ => self.generate_simple_func("INITCAP", &f.this),
2827            },
2828            Expression::Ascii(f) => self.generate_simple_func("ASCII", &f.this),
2829            Expression::Chr(f) => self.generate_simple_func("CHR", &f.this),
2830            Expression::CharFunc(f) => self.generate_char_func(f),
2831            Expression::Soundex(f) => self.generate_simple_func("SOUNDEX", &f.this),
2832            Expression::Levenshtein(f) => {
2833                self.generate_binary_func("LEVENSHTEIN", &f.this, &f.expression)
2834            }
2835
2836            // Additional math functions
2837            Expression::ModFunc(f) => self.generate_mod_func(f),
2838            Expression::Random(_) => {
2839                self.write_keyword("RANDOM");
2840                self.write("()");
2841                Ok(())
2842            }
2843            Expression::Rand(f) => self.generate_rand(f),
2844            Expression::TruncFunc(f) => self.generate_truncate_func(f),
2845            Expression::Pi(_) => {
2846                self.write_keyword("PI");
2847                self.write("()");
2848                Ok(())
2849            }
2850            Expression::Radians(f) => self.generate_simple_func("RADIANS", &f.this),
2851            Expression::Degrees(f) => self.generate_simple_func("DEGREES", &f.this),
2852            Expression::Sin(f) => self.generate_simple_func("SIN", &f.this),
2853            Expression::Cos(f) => self.generate_simple_func("COS", &f.this),
2854            Expression::Tan(f) => self.generate_simple_func("TAN", &f.this),
2855            Expression::Asin(f) => self.generate_simple_func("ASIN", &f.this),
2856            Expression::Acos(f) => self.generate_simple_func("ACOS", &f.this),
2857            Expression::Atan(f) => self.generate_simple_func("ATAN", &f.this),
2858            Expression::Atan2(f) => {
2859                let name = f.original_name.as_deref().unwrap_or("ATAN2");
2860                self.generate_binary_func(name, &f.this, &f.expression)
2861            }
2862
2863            // Control flow
2864            Expression::Decode(f) => self.generate_decode(f),
2865
2866            // Additional date/time functions
2867            Expression::DateFormat(f) => self.generate_date_format("DATE_FORMAT", f),
2868            Expression::FormatDate(f) => self.generate_date_format("FORMAT_DATE", f),
2869            Expression::Year(f) => self.generate_simple_func("YEAR", &f.this),
2870            Expression::Month(f) => self.generate_simple_func("MONTH", &f.this),
2871            Expression::Day(f) => self.generate_simple_func("DAY", &f.this),
2872            Expression::Hour(f) => self.generate_simple_func("HOUR", &f.this),
2873            Expression::Minute(f) => self.generate_simple_func("MINUTE", &f.this),
2874            Expression::Second(f) => self.generate_simple_func("SECOND", &f.this),
2875            Expression::DayOfWeek(f) => {
2876                let name = match self.config.dialect {
2877                    Some(DialectType::Presto)
2878                    | Some(DialectType::Trino)
2879                    | Some(DialectType::Athena) => "DAY_OF_WEEK",
2880                    Some(DialectType::DuckDB) => "ISODOW",
2881                    _ => "DAYOFWEEK",
2882                };
2883                self.generate_simple_func(name, &f.this)
2884            }
2885            Expression::DayOfMonth(f) => {
2886                let name = match self.config.dialect {
2887                    Some(DialectType::Presto)
2888                    | Some(DialectType::Trino)
2889                    | Some(DialectType::Athena) => "DAY_OF_MONTH",
2890                    _ => "DAYOFMONTH",
2891                };
2892                self.generate_simple_func(name, &f.this)
2893            }
2894            Expression::DayOfYear(f) => {
2895                let name = match self.config.dialect {
2896                    Some(DialectType::Presto)
2897                    | Some(DialectType::Trino)
2898                    | Some(DialectType::Athena) => "DAY_OF_YEAR",
2899                    _ => "DAYOFYEAR",
2900                };
2901                self.generate_simple_func(name, &f.this)
2902            }
2903            Expression::WeekOfYear(f) => {
2904                // Python sqlglot default is WEEK_OF_YEAR; Hive/DuckDB/Spark/MySQL override to WEEKOFYEAR
2905                let name = match self.config.dialect {
2906                    Some(DialectType::Hive)
2907                    | Some(DialectType::DuckDB)
2908                    | Some(DialectType::Spark)
2909                    | Some(DialectType::Databricks)
2910                    | Some(DialectType::MySQL) => "WEEKOFYEAR",
2911                    _ => "WEEK_OF_YEAR",
2912                };
2913                self.generate_simple_func(name, &f.this)
2914            }
2915            Expression::Quarter(f) => self.generate_simple_func("QUARTER", &f.this),
2916            Expression::AddMonths(f) => {
2917                self.generate_binary_func("ADD_MONTHS", &f.this, &f.expression)
2918            }
2919            Expression::MonthsBetween(f) => {
2920                self.generate_binary_func("MONTHS_BETWEEN", &f.this, &f.expression)
2921            }
2922            Expression::LastDay(f) => self.generate_last_day(f),
2923            Expression::NextDay(f) => self.generate_binary_func("NEXT_DAY", &f.this, &f.expression),
2924            Expression::Epoch(f) => self.generate_simple_func("EPOCH", &f.this),
2925            Expression::EpochMs(f) => self.generate_simple_func("EPOCH_MS", &f.this),
2926            Expression::FromUnixtime(f) => self.generate_from_unixtime(f),
2927            Expression::UnixTimestamp(f) => self.generate_unix_timestamp(f),
2928            Expression::MakeDate(f) => self.generate_make_date(f),
2929            Expression::MakeTimestamp(f) => self.generate_make_timestamp(f),
2930            Expression::TimestampTrunc(f) => self.generate_date_trunc(f),
2931
2932            // Array functions
2933            Expression::ArrayFunc(f) => self.generate_array_constructor(f),
2934            Expression::ArrayLength(f) => self.generate_simple_func("ARRAY_LENGTH", &f.this),
2935            Expression::ArraySize(f) => self.generate_simple_func("ARRAY_SIZE", &f.this),
2936            Expression::Cardinality(f) => self.generate_simple_func("CARDINALITY", &f.this),
2937            Expression::ArrayContains(f) => {
2938                self.generate_binary_func("ARRAY_CONTAINS", &f.this, &f.expression)
2939            }
2940            Expression::ArrayPosition(f) => {
2941                self.generate_binary_func("ARRAY_POSITION", &f.this, &f.expression)
2942            }
2943            Expression::ArrayAppend(f) => {
2944                self.generate_binary_func("ARRAY_APPEND", &f.this, &f.expression)
2945            }
2946            Expression::ArrayPrepend(f) => {
2947                self.generate_binary_func("ARRAY_PREPEND", &f.this, &f.expression)
2948            }
2949            Expression::ArrayConcat(f) => self.generate_vararg_func("ARRAY_CONCAT", &f.expressions),
2950            Expression::ArraySort(f) => self.generate_array_sort(f),
2951            Expression::ArrayReverse(f) => self.generate_simple_func("ARRAY_REVERSE", &f.this),
2952            Expression::ArrayDistinct(f) => self.generate_simple_func("ARRAY_DISTINCT", &f.this),
2953            Expression::ArrayJoin(f) => self.generate_array_join("ARRAY_JOIN", f),
2954            Expression::ArrayToString(f) => self.generate_array_join("ARRAY_TO_STRING", f),
2955            Expression::Unnest(f) => self.generate_unnest(f),
2956            Expression::Explode(f) => self.generate_simple_func("EXPLODE", &f.this),
2957            Expression::ExplodeOuter(f) => self.generate_simple_func("EXPLODE_OUTER", &f.this),
2958            Expression::ArrayFilter(f) => self.generate_array_filter(f),
2959            Expression::ArrayTransform(f) => self.generate_array_transform(f),
2960            Expression::ArrayFlatten(f) => self.generate_simple_func("FLATTEN", &f.this),
2961            Expression::ArrayCompact(f) => {
2962                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
2963                    // DuckDB: ARRAY_COMPACT(arr) -> LIST_FILTER(arr, _u -> NOT _u IS NULL)
2964                    self.write("LIST_FILTER(");
2965                    self.generate_expression(&f.this)?;
2966                    self.write(", _u -> NOT _u IS NULL)");
2967                    Ok(())
2968                } else {
2969                    self.generate_simple_func("ARRAY_COMPACT", &f.this)
2970                }
2971            }
2972            Expression::ArrayIntersect(f) => {
2973                let func_name = f.original_name.as_deref().unwrap_or("ARRAY_INTERSECT");
2974                self.generate_vararg_func(func_name, &f.expressions)
2975            }
2976            Expression::ArrayUnion(f) => {
2977                self.generate_binary_func("ARRAY_UNION", &f.this, &f.expression)
2978            }
2979            Expression::ArrayExcept(f) => {
2980                self.generate_binary_func("ARRAY_EXCEPT", &f.this, &f.expression)
2981            }
2982            Expression::ArrayRemove(f) => {
2983                self.generate_binary_func("ARRAY_REMOVE", &f.this, &f.expression)
2984            }
2985            Expression::ArrayZip(f) => self.generate_vararg_func("ARRAYS_ZIP", &f.expressions),
2986            Expression::Sequence(f) => self.generate_sequence("SEQUENCE", f),
2987            Expression::Generate(f) => self.generate_sequence("GENERATE_SERIES", f),
2988
2989            // Struct functions
2990            Expression::StructFunc(f) => self.generate_struct_constructor(f),
2991            Expression::StructExtract(f) => self.generate_struct_extract(f),
2992            Expression::NamedStruct(f) => self.generate_named_struct(f),
2993
2994            // Map functions
2995            Expression::MapFunc(f) => self.generate_map_constructor(f),
2996            Expression::MapFromEntries(f) => self.generate_simple_func("MAP_FROM_ENTRIES", &f.this),
2997            Expression::MapFromArrays(f) => {
2998                self.generate_binary_func("MAP_FROM_ARRAYS", &f.this, &f.expression)
2999            }
3000            Expression::MapKeys(f) => self.generate_simple_func("MAP_KEYS", &f.this),
3001            Expression::MapValues(f) => self.generate_simple_func("MAP_VALUES", &f.this),
3002            Expression::MapContainsKey(f) => {
3003                self.generate_binary_func("MAP_CONTAINS_KEY", &f.this, &f.expression)
3004            }
3005            Expression::MapConcat(f) => self.generate_vararg_func("MAP_CONCAT", &f.expressions),
3006            Expression::ElementAt(f) => {
3007                self.generate_binary_func("ELEMENT_AT", &f.this, &f.expression)
3008            }
3009            Expression::TransformKeys(f) => self.generate_transform_func("TRANSFORM_KEYS", f),
3010            Expression::TransformValues(f) => self.generate_transform_func("TRANSFORM_VALUES", f),
3011
3012            // JSON functions
3013            Expression::JsonExtract(f) => self.generate_json_extract("JSON_EXTRACT", f),
3014            Expression::JsonExtractScalar(f) => {
3015                self.generate_json_extract("JSON_EXTRACT_SCALAR", f)
3016            }
3017            Expression::JsonExtractPath(f) => self.generate_json_path("JSON_EXTRACT_PATH", f),
3018            Expression::JsonArray(f) => self.generate_vararg_func("JSON_ARRAY", &f.expressions),
3019            Expression::JsonObject(f) => self.generate_json_object(f),
3020            Expression::JsonQuery(f) => self.generate_json_extract("JSON_QUERY", f),
3021            Expression::JsonValue(f) => self.generate_json_extract("JSON_VALUE", f),
3022            Expression::JsonArrayLength(f) => {
3023                self.generate_simple_func("JSON_ARRAY_LENGTH", &f.this)
3024            }
3025            Expression::JsonKeys(f) => self.generate_simple_func("JSON_KEYS", &f.this),
3026            Expression::JsonType(f) => self.generate_simple_func("JSON_TYPE", &f.this),
3027            Expression::ParseJson(f) => {
3028                let name = match self.config.dialect {
3029                    Some(DialectType::Presto)
3030                    | Some(DialectType::Trino)
3031                    | Some(DialectType::Athena) => "JSON_PARSE",
3032                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
3033                        // PostgreSQL: CAST(x AS JSON)
3034                        self.write_keyword("CAST");
3035                        self.write("(");
3036                        self.generate_expression(&f.this)?;
3037                        self.write_keyword(" AS ");
3038                        self.write_keyword("JSON");
3039                        self.write(")");
3040                        return Ok(());
3041                    }
3042                    Some(DialectType::Hive)
3043                    | Some(DialectType::Spark)
3044                    | Some(DialectType::MySQL)
3045                    | Some(DialectType::SingleStore)
3046                    | Some(DialectType::TiDB)
3047                    | Some(DialectType::TSQL) => {
3048                        // Hive/Spark/MySQL/TSQL: just emit the string literal
3049                        self.generate_expression(&f.this)?;
3050                        return Ok(());
3051                    }
3052                    Some(DialectType::DuckDB) => "JSON",
3053                    _ => "PARSE_JSON",
3054                };
3055                self.generate_simple_func(name, &f.this)
3056            }
3057            Expression::ToJson(f) => self.generate_simple_func("TO_JSON", &f.this),
3058            Expression::JsonSet(f) => self.generate_json_modify("JSON_SET", f),
3059            Expression::JsonInsert(f) => self.generate_json_modify("JSON_INSERT", f),
3060            Expression::JsonRemove(f) => self.generate_json_path("JSON_REMOVE", f),
3061            Expression::JsonMergePatch(f) => {
3062                self.generate_binary_func("JSON_MERGE_PATCH", &f.this, &f.expression)
3063            }
3064            Expression::JsonArrayAgg(f) => self.generate_json_array_agg(f),
3065            Expression::JsonObjectAgg(f) => self.generate_json_object_agg(f),
3066
3067            // Type casting/conversion
3068            Expression::Convert(f) => self.generate_convert(f),
3069            Expression::Typeof(f) => self.generate_simple_func("TYPEOF", &f.this),
3070
3071            // Additional expressions
3072            Expression::Lambda(f) => self.generate_lambda(f),
3073            Expression::Parameter(f) => self.generate_parameter(f),
3074            Expression::Placeholder(f) => self.generate_placeholder(f),
3075            Expression::NamedArgument(f) => self.generate_named_argument(f),
3076            Expression::TableArgument(f) => self.generate_table_argument(f),
3077            Expression::SqlComment(f) => self.generate_sql_comment(f),
3078
3079            // Additional predicates
3080            Expression::NullSafeEq(op) => self.generate_null_safe_eq(op),
3081            Expression::NullSafeNeq(op) => self.generate_null_safe_neq(op),
3082            Expression::Glob(op) => self.generate_binary_op(op, "GLOB"),
3083            Expression::SimilarTo(f) => self.generate_similar_to(f),
3084            Expression::Any(f) => self.generate_quantified("ANY", f),
3085            Expression::All(f) => self.generate_quantified("ALL", f),
3086            Expression::Overlaps(f) => self.generate_overlaps(f),
3087
3088            // Bitwise operations
3089            Expression::BitwiseLeftShift(op) => {
3090                if matches!(
3091                    self.config.dialect,
3092                    Some(DialectType::Presto) | Some(DialectType::Trino)
3093                ) {
3094                    self.write_keyword("BITWISE_LEFT_SHIFT");
3095                    self.write("(");
3096                    self.generate_expression(&op.left)?;
3097                    self.write(", ");
3098                    self.generate_expression(&op.right)?;
3099                    self.write(")");
3100                    Ok(())
3101                } else if matches!(
3102                    self.config.dialect,
3103                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3104                ) {
3105                    self.write_keyword("SHIFTLEFT");
3106                    self.write("(");
3107                    self.generate_expression(&op.left)?;
3108                    self.write(", ");
3109                    self.generate_expression(&op.right)?;
3110                    self.write(")");
3111                    Ok(())
3112                } else {
3113                    self.generate_binary_op(op, "<<")
3114                }
3115            }
3116            Expression::BitwiseRightShift(op) => {
3117                if matches!(
3118                    self.config.dialect,
3119                    Some(DialectType::Presto) | Some(DialectType::Trino)
3120                ) {
3121                    self.write_keyword("BITWISE_RIGHT_SHIFT");
3122                    self.write("(");
3123                    self.generate_expression(&op.left)?;
3124                    self.write(", ");
3125                    self.generate_expression(&op.right)?;
3126                    self.write(")");
3127                    Ok(())
3128                } else if matches!(
3129                    self.config.dialect,
3130                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3131                ) {
3132                    self.write_keyword("SHIFTRIGHT");
3133                    self.write("(");
3134                    self.generate_expression(&op.left)?;
3135                    self.write(", ");
3136                    self.generate_expression(&op.right)?;
3137                    self.write(")");
3138                    Ok(())
3139                } else {
3140                    self.generate_binary_op(op, ">>")
3141                }
3142            }
3143            Expression::BitwiseAndAgg(f) => self.generate_agg_func("BIT_AND", f),
3144            Expression::BitwiseOrAgg(f) => self.generate_agg_func("BIT_OR", f),
3145            Expression::BitwiseXorAgg(f) => self.generate_agg_func("BIT_XOR", f),
3146
3147            // Array/struct/map access
3148            Expression::Subscript(s) => self.generate_subscript(s),
3149            Expression::Dot(d) => self.generate_dot_access(d),
3150            Expression::MethodCall(m) => self.generate_method_call(m),
3151            Expression::ArraySlice(s) => self.generate_array_slice(s),
3152
3153            Expression::And(op) => self.generate_connector_op(op, ConnectorOperator::And),
3154            Expression::Or(op) => self.generate_connector_op(op, ConnectorOperator::Or),
3155            Expression::Add(op) => self.generate_binary_op(op, "+"),
3156            Expression::Sub(op) => self.generate_binary_op(op, "-"),
3157            Expression::Mul(op) => self.generate_binary_op(op, "*"),
3158            Expression::Div(op) => self.generate_binary_op(op, "/"),
3159            Expression::IntDiv(f) => {
3160                use crate::dialects::DialectType;
3161                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
3162                    // DuckDB uses // operator for integer division
3163                    self.generate_expression(&f.this)?;
3164                    self.write(" // ");
3165                    self.generate_expression(&f.expression)?;
3166                    Ok(())
3167                } else if matches!(
3168                    self.config.dialect,
3169                    Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
3170                ) {
3171                    // Hive/Spark use DIV as an infix operator
3172                    self.generate_expression(&f.this)?;
3173                    self.write(" ");
3174                    self.write_keyword("DIV");
3175                    self.write(" ");
3176                    self.generate_expression(&f.expression)?;
3177                    Ok(())
3178                } else {
3179                    // Other dialects use DIV function
3180                    self.write_keyword("DIV");
3181                    self.write("(");
3182                    self.generate_expression(&f.this)?;
3183                    self.write(", ");
3184                    self.generate_expression(&f.expression)?;
3185                    self.write(")");
3186                    Ok(())
3187                }
3188            }
3189            Expression::Mod(op) => {
3190                if matches!(self.config.dialect, Some(DialectType::Teradata)) {
3191                    self.generate_binary_op(op, "MOD")
3192                } else {
3193                    self.generate_binary_op(op, "%")
3194                }
3195            }
3196            Expression::Eq(op) => self.generate_binary_op(op, "="),
3197            Expression::Neq(op) => self.generate_binary_op(op, "<>"),
3198            Expression::Lt(op) => self.generate_binary_op(op, "<"),
3199            Expression::Lte(op) => self.generate_binary_op(op, "<="),
3200            Expression::Gt(op) => self.generate_binary_op(op, ">"),
3201            Expression::Gte(op) => self.generate_binary_op(op, ">="),
3202            Expression::Like(op) => self.generate_like_op(op, "LIKE"),
3203            Expression::ILike(op) => self.generate_like_op(op, "ILIKE"),
3204            Expression::Match(op) => self.generate_binary_op(op, "MATCH"),
3205            Expression::Concat(op) => {
3206                // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
3207                if self.config.dialect == Some(DialectType::Solr) {
3208                    self.generate_binary_op(op, "OR")
3209                } else if self.config.dialect == Some(DialectType::MySQL) {
3210                    self.generate_mysql_concat_from_concat(op)
3211                } else {
3212                    self.generate_binary_op(op, "||")
3213                }
3214            }
3215            Expression::BitwiseAnd(op) => {
3216                // Presto/Trino use BITWISE_AND function
3217                if matches!(
3218                    self.config.dialect,
3219                    Some(DialectType::Presto) | Some(DialectType::Trino)
3220                ) {
3221                    self.write_keyword("BITWISE_AND");
3222                    self.write("(");
3223                    self.generate_expression(&op.left)?;
3224                    self.write(", ");
3225                    self.generate_expression(&op.right)?;
3226                    self.write(")");
3227                    Ok(())
3228                } else {
3229                    self.generate_binary_op(op, "&")
3230                }
3231            }
3232            Expression::BitwiseOr(op) => {
3233                // Presto/Trino use BITWISE_OR function
3234                if matches!(
3235                    self.config.dialect,
3236                    Some(DialectType::Presto) | Some(DialectType::Trino)
3237                ) {
3238                    self.write_keyword("BITWISE_OR");
3239                    self.write("(");
3240                    self.generate_expression(&op.left)?;
3241                    self.write(", ");
3242                    self.generate_expression(&op.right)?;
3243                    self.write(")");
3244                    Ok(())
3245                } else {
3246                    self.generate_binary_op(op, "|")
3247                }
3248            }
3249            Expression::BitwiseXor(op) => {
3250                // Presto/Trino use BITWISE_XOR function, PostgreSQL uses #, others use ^
3251                if matches!(
3252                    self.config.dialect,
3253                    Some(DialectType::Presto) | Some(DialectType::Trino)
3254                ) {
3255                    self.write_keyword("BITWISE_XOR");
3256                    self.write("(");
3257                    self.generate_expression(&op.left)?;
3258                    self.write(", ");
3259                    self.generate_expression(&op.right)?;
3260                    self.write(")");
3261                    Ok(())
3262                } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
3263                    self.generate_binary_op(op, "#")
3264                } else {
3265                    self.generate_binary_op(op, "^")
3266                }
3267            }
3268            Expression::Adjacent(op) => self.generate_binary_op(op, "-|-"),
3269            Expression::TsMatch(op) => self.generate_binary_op(op, "@@"),
3270            Expression::PropertyEQ(op) => self.generate_binary_op(op, ":="),
3271            Expression::ArrayContainsAll(op) => self.generate_binary_op(op, "@>"),
3272            Expression::ArrayContainedBy(op) => self.generate_binary_op(op, "<@"),
3273            Expression::ArrayOverlaps(op) => self.generate_binary_op(op, "&&"),
3274            Expression::JSONBContainsAllTopKeys(op) => self.generate_binary_op(op, "?&"),
3275            Expression::JSONBContainsAnyTopKeys(op) => self.generate_binary_op(op, "?|"),
3276            Expression::JSONBContains(f) => {
3277                // PostgreSQL JSONB contains key/path operators: a ? b, a @? b
3278                self.generate_expression(&f.this)?;
3279                self.write_space();
3280                self.write(f.original_name.as_deref().unwrap_or("?"));
3281                self.write_space();
3282                self.generate_expression(&f.expression)
3283            }
3284            Expression::JSONBDeleteAtPath(op) => self.generate_binary_op(op, "#-"),
3285            Expression::ExtendsLeft(op) => self.generate_binary_op(op, "&<"),
3286            Expression::ExtendsRight(op) => self.generate_binary_op(op, "&>"),
3287            Expression::Not(op) => match &op.this {
3288                Expression::Like(like) => self.generate_like_op_negated(like, "LIKE"),
3289                Expression::ILike(like) => self.generate_like_op_negated(like, "ILIKE"),
3290                _ => self.generate_unary_op(op, "NOT"),
3291            },
3292            Expression::Neg(op) => self.generate_unary_op(op, "-"),
3293            Expression::BitwiseNot(op) => {
3294                // Presto/Trino use BITWISE_NOT function
3295                if matches!(
3296                    self.config.dialect,
3297                    Some(DialectType::Presto) | Some(DialectType::Trino)
3298                ) {
3299                    self.write_keyword("BITWISE_NOT");
3300                    self.write("(");
3301                    self.generate_expression(&op.this)?;
3302                    self.write(")");
3303                    Ok(())
3304                } else {
3305                    self.generate_unary_op(op, "~")
3306                }
3307            }
3308            Expression::In(in_expr) => self.generate_in(in_expr),
3309            Expression::Between(between) => self.generate_between(between),
3310            Expression::IsNull(is_null) => self.generate_is_null(is_null),
3311            Expression::IsTrue(is_true) => self.generate_is_true(is_true),
3312            Expression::IsFalse(is_false) => self.generate_is_false(is_false),
3313            Expression::IsJson(is_json) => self.generate_is_json(is_json),
3314            Expression::Is(is_expr) => self.generate_is(is_expr),
3315            Expression::Exists(exists) => self.generate_exists(exists),
3316            Expression::MemberOf(member_of) => self.generate_member_of(member_of),
3317            Expression::Subquery(subquery) => self.generate_subquery(subquery),
3318            Expression::Paren(paren) => {
3319                // JoinedTable already outputs its own parentheses, so don't double-wrap
3320                let skip_parens = matches!(&paren.this, Expression::JoinedTable(_));
3321
3322                if !skip_parens {
3323                    self.write("(");
3324                    if self.config.pretty {
3325                        self.write_newline();
3326                        self.indent_level += 1;
3327                        self.write_indent();
3328                    }
3329                }
3330                self.generate_expression(&paren.this)?;
3331                if !skip_parens {
3332                    if self.config.pretty {
3333                        self.write_newline();
3334                        self.indent_level -= 1;
3335                        self.write_indent();
3336                    }
3337                    self.write(")");
3338                }
3339                // Output trailing comments after closing paren
3340                for comment in &paren.trailing_comments {
3341                    self.write(" ");
3342                    self.write_formatted_comment(comment);
3343                }
3344                Ok(())
3345            }
3346            Expression::Array(arr) => self.generate_array(arr),
3347            Expression::Tuple(tuple) => self.generate_tuple(tuple),
3348            Expression::PipeOperator(pipe) => self.generate_pipe_operator(pipe),
3349            Expression::Ordered(ordered) => self.generate_ordered(ordered),
3350            Expression::DataType(dt) => self.generate_data_type(dt),
3351            Expression::Raw(raw) => {
3352                self.write(&raw.sql);
3353                Ok(())
3354            }
3355            Expression::CreateTask(task) => self.generate_create_task(task),
3356            Expression::TryCatch(try_catch) => self.generate_try_catch(try_catch),
3357            Expression::Command(cmd) => {
3358                self.write(&cmd.this);
3359                if matches!(self.config.dialect, Some(DialectType::ClickHouse))
3360                    && cmd
3361                        .this
3362                        .trim_start()
3363                        .get(..7)
3364                        .is_some_and(|prefix| prefix.eq_ignore_ascii_case("EXPLAIN"))
3365                {
3366                    for _ in 0..Self::missing_closing_parens_outside_quotes(&cmd.this) {
3367                        self.write(")");
3368                    }
3369                }
3370                Ok(())
3371            }
3372            Expression::Kill(kill) => {
3373                self.write_keyword("KILL");
3374                if let Some(kind) = &kill.kind {
3375                    self.write_space();
3376                    self.write_keyword(kind);
3377                }
3378                self.write_space();
3379                self.generate_expression(&kill.this)?;
3380                Ok(())
3381            }
3382            Expression::Prepare(prepare) => self.generate_prepare(prepare),
3383            Expression::Execute(exec) => {
3384                self.write_keyword("EXECUTE");
3385                self.write_space();
3386                self.generate_expression(&exec.this)?;
3387                if exec.prepared {
3388                    if !exec.arguments.is_empty() {
3389                        self.write("(");
3390                        for (i, argument) in exec.arguments.iter().enumerate() {
3391                            if i > 0 {
3392                                self.write(", ");
3393                            }
3394                            self.generate_expression(argument)?;
3395                        }
3396                        self.write(")");
3397                    }
3398                    return Ok(());
3399                }
3400                for (i, param) in exec.parameters.iter().enumerate() {
3401                    if i == 0 {
3402                        self.write_space();
3403                    } else {
3404                        self.write(", ");
3405                    }
3406                    self.write(&param.name);
3407                    // Only write = value for named parameters (not positional)
3408                    if !param.positional {
3409                        self.write(" = ");
3410                        self.generate_expression(&param.value)?;
3411                    }
3412                    if param.output {
3413                        self.write_space();
3414                        self.write_keyword("OUTPUT");
3415                    }
3416                }
3417                if let Some(ref suffix) = exec.suffix {
3418                    self.write_space();
3419                    self.write(suffix);
3420                }
3421                Ok(())
3422            }
3423            Expression::Annotated(annotated) => {
3424                self.generate_expression(&annotated.this)?;
3425                for comment in &annotated.trailing_comments {
3426                    self.write(" ");
3427                    self.write_formatted_comment(comment);
3428                }
3429                Ok(())
3430            }
3431
3432            // DDL statements
3433            Expression::CreateTable(ct) => self.generate_create_table(ct),
3434            Expression::DropTable(dt) => self.generate_drop_table(dt),
3435            Expression::Undrop(u) => self.generate_undrop(u),
3436            Expression::AlterTable(at) => self.generate_alter_table(at),
3437            Expression::CreateIndex(ci) => self.generate_create_index(ci),
3438            Expression::DropIndex(di) => self.generate_drop_index(di),
3439            Expression::CreateView(cv) => self.generate_create_view(cv),
3440            Expression::DropView(dv) => self.generate_drop_view(dv),
3441            Expression::AlterView(av) => self.generate_alter_view(av),
3442            Expression::AlterIndex(ai) => self.generate_alter_index(ai),
3443            Expression::Truncate(tr) => self.generate_truncate(tr),
3444            Expression::Use(u) => self.generate_use(u),
3445            // Phase 4: Additional DDL statements
3446            Expression::CreateSchema(cs) => self.generate_create_schema(cs),
3447            Expression::DropSchema(ds) => self.generate_drop_schema(ds),
3448            Expression::DropNamespace(dn) => self.generate_drop_namespace(dn),
3449            Expression::CreateDatabase(cd) => self.generate_create_database(cd),
3450            Expression::DropDatabase(dd) => self.generate_drop_database(dd),
3451            Expression::CreateFunction(cf) => self.generate_create_function(cf),
3452            Expression::DropFunction(df) => self.generate_drop_function(df),
3453            Expression::CreateProcedure(cp) => self.generate_create_procedure(cp),
3454            Expression::DropProcedure(dp) => self.generate_drop_procedure(dp),
3455            Expression::CreateSequence(cs) => self.generate_create_sequence(cs),
3456            Expression::CreateSynonym(cs) => {
3457                self.write_keyword("CREATE SYNONYM");
3458                self.write_space();
3459                self.generate_table(&cs.name)?;
3460                self.write_space();
3461                self.write_keyword("FOR");
3462                self.write_space();
3463                self.generate_table(&cs.target)?;
3464                Ok(())
3465            }
3466            Expression::DropSequence(ds) => self.generate_drop_sequence(ds),
3467            Expression::AlterSequence(als) => self.generate_alter_sequence(als),
3468            Expression::CreateTrigger(ct) => self.generate_create_trigger(ct),
3469            Expression::DropTrigger(dt) => self.generate_drop_trigger(dt),
3470            Expression::CreateType(ct) => self.generate_create_type(ct),
3471            Expression::DropType(dt) => self.generate_drop_type(dt),
3472            Expression::Describe(d) => self.generate_describe(d),
3473            Expression::Show(s) => self.generate_show(s),
3474
3475            // CACHE/UNCACHE/LOAD TABLE (Spark/Hive)
3476            Expression::Cache(c) => self.generate_cache(c),
3477            Expression::Uncache(u) => self.generate_uncache(u),
3478            Expression::LoadData(l) => self.generate_load_data(l),
3479            Expression::Pragma(p) => self.generate_pragma(p),
3480            Expression::Grant(g) => self.generate_grant(g),
3481            Expression::Revoke(r) => self.generate_revoke(r),
3482            Expression::Comment(c) => self.generate_comment(c),
3483            Expression::SetStatement(s) => self.generate_set_statement(s),
3484
3485            // PIVOT/UNPIVOT
3486            Expression::Pivot(pivot) => self.generate_pivot(pivot),
3487            Expression::Unpivot(unpivot) => self.generate_unpivot(unpivot),
3488
3489            // VALUES table constructor
3490            Expression::Values(values) => self.generate_values(values),
3491
3492            // === BATCH-GENERATED MATCH ARMS (481 variants) ===
3493            Expression::AIAgg(e) => self.generate_ai_agg(e),
3494            Expression::AIClassify(e) => self.generate_ai_classify(e),
3495            Expression::AddPartition(e) => self.generate_add_partition(e),
3496            Expression::AlgorithmProperty(e) => self.generate_algorithm_property(e),
3497            Expression::Aliases(e) => self.generate_aliases(e),
3498            Expression::AllowedValuesProperty(e) => self.generate_allowed_values_property(e),
3499            Expression::AlterColumn(e) => self.generate_alter_column(e),
3500            Expression::AlterSession(e) => self.generate_alter_session(e),
3501            Expression::AlterSet(e) => self.generate_alter_set(e),
3502            Expression::AlterSortKey(e) => self.generate_alter_sort_key(e),
3503            Expression::Analyze(e) => self.generate_analyze(e),
3504            Expression::AnalyzeDelete(e) => self.generate_analyze_delete(e),
3505            Expression::AnalyzeHistogram(e) => self.generate_analyze_histogram(e),
3506            Expression::AnalyzeListChainedRows(e) => self.generate_analyze_list_chained_rows(e),
3507            Expression::AnalyzeSample(e) => self.generate_analyze_sample(e),
3508            Expression::AnalyzeStatistics(e) => self.generate_analyze_statistics(e),
3509            Expression::AnalyzeValidate(e) => self.generate_analyze_validate(e),
3510            Expression::AnalyzeWith(e) => self.generate_analyze_with(e),
3511            Expression::Anonymous(e) => self.generate_anonymous(e),
3512            Expression::AnonymousAggFunc(e) => self.generate_anonymous_agg_func(e),
3513            Expression::Apply(e) => self.generate_apply(e),
3514            Expression::ApproxPercentileEstimate(e) => self.generate_approx_percentile_estimate(e),
3515            Expression::ApproxQuantile(e) => self.generate_approx_quantile(e),
3516            Expression::ApproxQuantiles(e) => self.generate_approx_quantiles(e),
3517            Expression::ApproxTopK(e) => self.generate_approx_top_k(e),
3518            Expression::ApproxTopKAccumulate(e) => self.generate_approx_top_k_accumulate(e),
3519            Expression::ApproxTopKCombine(e) => self.generate_approx_top_k_combine(e),
3520            Expression::ApproxTopKEstimate(e) => self.generate_approx_top_k_estimate(e),
3521            Expression::ApproxTopSum(e) => self.generate_approx_top_sum(e),
3522            Expression::ArgMax(e) => self.generate_arg_max(e),
3523            Expression::ArgMin(e) => self.generate_arg_min(e),
3524            Expression::ArrayAll(e) => self.generate_array_all(e),
3525            Expression::ArrayAny(e) => self.generate_array_any(e),
3526            Expression::ArrayConstructCompact(e) => self.generate_array_construct_compact(e),
3527            Expression::ArraySum(e) => self.generate_array_sum(e),
3528            Expression::AtIndex(e) => self.generate_at_index(e),
3529            Expression::Attach(e) => self.generate_attach(e),
3530            Expression::AttachOption(e) => self.generate_attach_option(e),
3531            Expression::AutoIncrementProperty(e) => self.generate_auto_increment_property(e),
3532            Expression::AutoRefreshProperty(e) => self.generate_auto_refresh_property(e),
3533            Expression::BackupProperty(e) => self.generate_backup_property(e),
3534            Expression::Base64DecodeBinary(e) => self.generate_base64_decode_binary(e),
3535            Expression::Base64DecodeString(e) => self.generate_base64_decode_string(e),
3536            Expression::Base64Encode(e) => self.generate_base64_encode(e),
3537            Expression::BlockCompressionProperty(e) => self.generate_block_compression_property(e),
3538            Expression::Booland(e) => self.generate_booland(e),
3539            Expression::Boolor(e) => self.generate_boolor(e),
3540            Expression::BuildProperty(e) => self.generate_build_property(e),
3541            Expression::ByteString(e) => self.generate_byte_string(e),
3542            Expression::CaseSpecificColumnConstraint(e) => {
3543                self.generate_case_specific_column_constraint(e)
3544            }
3545            Expression::CastToStrType(e) => self.generate_cast_to_str_type(e),
3546            Expression::Changes(e) => self.generate_changes(e),
3547            Expression::CharacterSetColumnConstraint(e) => {
3548                self.generate_character_set_column_constraint(e)
3549            }
3550            Expression::CharacterSetProperty(e) => self.generate_character_set_property(e),
3551            Expression::CheckColumnConstraint(e) => self.generate_check_column_constraint(e),
3552            Expression::AssumeColumnConstraint(e) => self.generate_assume_column_constraint(e),
3553            Expression::CheckJson(e) => self.generate_check_json(e),
3554            Expression::CheckXml(e) => self.generate_check_xml(e),
3555            Expression::ChecksumProperty(e) => self.generate_checksum_property(e),
3556            Expression::Clone(e) => self.generate_clone(e),
3557            Expression::ClusterBy(e) => self.generate_cluster_by(e),
3558            Expression::ClusterByColumnsProperty(e) => self.generate_cluster_by_columns_property(e),
3559            Expression::ClusteredByProperty(e) => self.generate_clustered_by_property(e),
3560            Expression::CollateProperty(e) => self.generate_collate_property(e),
3561            Expression::ColumnConstraint(e) => self.generate_column_constraint(e),
3562            Expression::ColumnDef(e) => self.generate_column_def_expr(e),
3563            Expression::ColumnPosition(e) => self.generate_column_position(e),
3564            Expression::ColumnPrefix(e) => self.generate_column_prefix(e),
3565            Expression::Columns(e) => self.generate_columns(e),
3566            Expression::CombinedAggFunc(e) => self.generate_combined_agg_func(e),
3567            Expression::CombinedParameterizedAgg(e) => self.generate_combined_parameterized_agg(e),
3568            Expression::Commit(e) => self.generate_commit(e),
3569            Expression::Comprehension(e) => self.generate_comprehension(e),
3570            Expression::Compress(e) => self.generate_compress(e),
3571            Expression::CompressColumnConstraint(e) => self.generate_compress_column_constraint(e),
3572            Expression::ComputedColumnConstraint(e) => self.generate_computed_column_constraint(e),
3573            Expression::ConditionalInsert(e) => self.generate_conditional_insert(e),
3574            Expression::Constraint(e) => self.generate_constraint(e),
3575            Expression::ConvertTimezone(e) => self.generate_convert_timezone(e),
3576            Expression::ConvertToCharset(e) => self.generate_convert_to_charset(e),
3577            Expression::Copy(e) => self.generate_copy(e),
3578            Expression::CopyParameter(e) => self.generate_copy_parameter(e),
3579            Expression::Corr(e) => self.generate_corr(e),
3580            Expression::CosineDistance(e) => self.generate_cosine_distance(e),
3581            Expression::CovarPop(e) => self.generate_covar_pop(e),
3582            Expression::CovarSamp(e) => self.generate_covar_samp(e),
3583            Expression::Credentials(e) => self.generate_credentials(e),
3584            Expression::CredentialsProperty(e) => self.generate_credentials_property(e),
3585            Expression::Cte(e) => self.generate_cte(e),
3586            Expression::Cube(e) => self.generate_cube(e),
3587            Expression::CurrentDatetime(e) => self.generate_current_datetime(e),
3588            Expression::CurrentSchema(e) => self.generate_current_schema(e),
3589            Expression::CurrentSchemas(e) => self.generate_current_schemas(e),
3590            Expression::CurrentUser(e) => self.generate_current_user(e),
3591            Expression::DPipe(e) => self.generate_d_pipe(e),
3592            Expression::DataBlocksizeProperty(e) => self.generate_data_blocksize_property(e),
3593            Expression::DataDeletionProperty(e) => self.generate_data_deletion_property(e),
3594            Expression::Date(e) => self.generate_date_func(e),
3595            Expression::DateBin(e) => self.generate_date_bin(e),
3596            Expression::DateFormatColumnConstraint(e) => {
3597                self.generate_date_format_column_constraint(e)
3598            }
3599            Expression::DateFromParts(e) => self.generate_date_from_parts(e),
3600            Expression::Datetime(e) => self.generate_datetime(e),
3601            Expression::DatetimeAdd(e) => self.generate_datetime_add(e),
3602            Expression::DatetimeDiff(e) => self.generate_datetime_diff(e),
3603            Expression::DatetimeSub(e) => self.generate_datetime_sub(e),
3604            Expression::DatetimeTrunc(e) => self.generate_datetime_trunc(e),
3605            Expression::Dayname(e) => self.generate_dayname(e),
3606            Expression::Declare(e) => self.generate_declare(e),
3607            Expression::DeclareItem(e) => self.generate_declare_item(e),
3608            Expression::DecodeCase(e) => self.generate_decode_case(e),
3609            Expression::DecompressBinary(e) => self.generate_decompress_binary(e),
3610            Expression::DecompressString(e) => self.generate_decompress_string(e),
3611            Expression::Decrypt(e) => self.generate_decrypt(e),
3612            Expression::DecryptRaw(e) => self.generate_decrypt_raw(e),
3613            Expression::DefaultColumnConstraint(e) => {
3614                self.write_keyword("DEFAULT");
3615                self.write_space();
3616                self.generate_expression(&e.this)?;
3617                if let Some(ref col) = e.for_column {
3618                    self.write_space();
3619                    self.write_keyword("FOR");
3620                    self.write_space();
3621                    self.generate_identifier(col)?;
3622                }
3623                Ok(())
3624            }
3625            Expression::DefinerProperty(e) => self.generate_definer_property(e),
3626            Expression::Detach(e) => self.generate_detach(e),
3627            Expression::DictProperty(e) => self.generate_dict_property(e),
3628            Expression::DictRange(e) => self.generate_dict_range(e),
3629            Expression::Directory(e) => self.generate_directory(e),
3630            Expression::DistKeyProperty(e) => self.generate_dist_key_property(e),
3631            Expression::DistStyleProperty(e) => self.generate_dist_style_property(e),
3632            Expression::DistributeBy(e) => self.generate_distribute_by(e),
3633            Expression::DistributedByProperty(e) => self.generate_distributed_by_property(e),
3634            Expression::DotProduct(e) => self.generate_dot_product(e),
3635            Expression::DropPartition(e) => self.generate_drop_partition(e),
3636            Expression::DuplicateKeyProperty(e) => self.generate_duplicate_key_property(e),
3637            Expression::Elt(e) => self.generate_elt(e),
3638            Expression::Encode(e) => self.generate_encode(e),
3639            Expression::EncodeProperty(e) => self.generate_encode_property(e),
3640            Expression::Encrypt(e) => self.generate_encrypt(e),
3641            Expression::EncryptRaw(e) => self.generate_encrypt_raw(e),
3642            Expression::EngineProperty(e) => self.generate_engine_property(e),
3643            Expression::EnviromentProperty(e) => self.generate_enviroment_property(e),
3644            Expression::EphemeralColumnConstraint(e) => {
3645                self.generate_ephemeral_column_constraint(e)
3646            }
3647            Expression::EqualNull(e) => self.generate_equal_null(e),
3648            Expression::EuclideanDistance(e) => self.generate_euclidean_distance(e),
3649            Expression::ExecuteAsProperty(e) => self.generate_execute_as_property(e),
3650            Expression::Export(e) => self.generate_export(e),
3651            Expression::ExternalProperty(e) => self.generate_external_property(e),
3652            Expression::FallbackProperty(e) => self.generate_fallback_property(e),
3653            Expression::FarmFingerprint(e) => self.generate_farm_fingerprint(e),
3654            Expression::FeaturesAtTime(e) => self.generate_features_at_time(e),
3655            Expression::Fetch(e) => self.generate_fetch(e),
3656            Expression::FileFormatProperty(e) => self.generate_file_format_property(e),
3657            Expression::Filter(e) => self.generate_filter(e),
3658            Expression::Float64(e) => self.generate_float64(e),
3659            Expression::ForIn(e) => self.generate_for_in(e),
3660            Expression::ForeignKey(e) => self.generate_foreign_key(e),
3661            Expression::Format(e) => self.generate_format(e),
3662            Expression::FormatPhrase(e) => self.generate_format_phrase(e),
3663            Expression::FreespaceProperty(e) => self.generate_freespace_property(e),
3664            Expression::From(e) => self.generate_from(e),
3665            Expression::FromBase(e) => self.generate_from_base(e),
3666            Expression::FromTimeZone(e) => self.generate_from_time_zone(e),
3667            Expression::GapFill(e) => self.generate_gap_fill(e),
3668            Expression::GenerateDateArray(e) => self.generate_generate_date_array(e),
3669            Expression::GenerateEmbedding(e) => self.generate_generate_embedding(e),
3670            Expression::GenerateSeries(e) => self.generate_generate_series(e),
3671            Expression::GenerateTimestampArray(e) => self.generate_generate_timestamp_array(e),
3672            Expression::GeneratedAsIdentityColumnConstraint(e) => {
3673                self.generate_generated_as_identity_column_constraint(e)
3674            }
3675            Expression::GeneratedAsRowColumnConstraint(e) => {
3676                self.generate_generated_as_row_column_constraint(e)
3677            }
3678            Expression::Get(e) => self.generate_get(e),
3679            Expression::GetExtract(e) => self.generate_get_extract(e),
3680            Expression::Getbit(e) => self.generate_getbit(e),
3681            Expression::GrantPrincipal(e) => self.generate_grant_principal(e),
3682            Expression::GrantPrivilege(e) => self.generate_grant_privilege(e),
3683            Expression::Group(e) => self.generate_group(e),
3684            Expression::GroupBy(e) => self.generate_group_by(e),
3685            Expression::Grouping(e) => self.generate_grouping(e),
3686            Expression::GroupingId(e) => self.generate_grouping_id(e),
3687            Expression::GroupingSets(e) => self.generate_grouping_sets(e),
3688            Expression::HashAgg(e) => self.generate_hash_agg(e),
3689            Expression::Having(e) => self.generate_having(e),
3690            Expression::HavingMax(e) => self.generate_having_max(e),
3691            Expression::Heredoc(e) => self.generate_heredoc(e),
3692            Expression::HexEncode(e) => self.generate_hex_encode(e),
3693            Expression::Hll(e) => self.generate_hll(e),
3694            Expression::InOutColumnConstraint(e) => self.generate_in_out_column_constraint(e),
3695            Expression::IncludeProperty(e) => self.generate_include_property(e),
3696            Expression::Index(e) => self.generate_index(e),
3697            Expression::IndexColumnConstraint(e) => self.generate_index_column_constraint(e),
3698            Expression::IndexConstraintOption(e) => self.generate_index_constraint_option(e),
3699            Expression::IndexParameters(e) => self.generate_index_parameters(e),
3700            Expression::IndexTableHint(e) => self.generate_index_table_hint(e),
3701            Expression::InheritsProperty(e) => self.generate_inherits_property(e),
3702            Expression::InputModelProperty(e) => self.generate_input_model_property(e),
3703            Expression::InputOutputFormat(e) => self.generate_input_output_format(e),
3704            Expression::Install(e) => self.generate_install(e),
3705            Expression::IntervalOp(e) => self.generate_interval_op(e),
3706            Expression::IntervalSpan(e) => self.generate_interval_span(e),
3707            Expression::IntoClause(e) => self.generate_into_clause(e),
3708            Expression::Introducer(e) => self.generate_introducer(e),
3709            Expression::IsolatedLoadingProperty(e) => self.generate_isolated_loading_property(e),
3710            Expression::JSON(e) => self.generate_json(e),
3711            Expression::JSONArray(e) => self.generate_json_array(e),
3712            Expression::JSONArrayAgg(e) => self.generate_json_array_agg_struct(e),
3713            Expression::JSONArrayAppend(e) => self.generate_json_array_append(e),
3714            Expression::JSONArrayContains(e) => self.generate_json_array_contains(e),
3715            Expression::JSONArrayInsert(e) => self.generate_json_array_insert(e),
3716            Expression::JSONBExists(e) => self.generate_jsonb_exists(e),
3717            Expression::JSONBExtractScalar(e) => self.generate_jsonb_extract_scalar(e),
3718            Expression::JSONBObjectAgg(e) => self.generate_jsonb_object_agg(e),
3719            Expression::JSONObjectAgg(e) => self.generate_json_object_agg_struct(e),
3720            Expression::JSONColumnDef(e) => self.generate_json_column_def(e),
3721            Expression::JSONExists(e) => self.generate_json_exists(e),
3722            Expression::JSONCast(e) => self.generate_json_cast(e),
3723            Expression::JSONExtract(e) => self.generate_json_extract_path(e),
3724            Expression::JSONExtractArray(e) => self.generate_json_extract_array(e),
3725            Expression::JSONExtractQuote(e) => self.generate_json_extract_quote(e),
3726            Expression::JSONExtractScalar(e) => self.generate_json_extract_scalar(e),
3727            Expression::JSONFormat(e) => self.generate_json_format(e),
3728            Expression::JSONKeyValue(e) => self.generate_json_key_value(e),
3729            Expression::JSONKeys(e) => self.generate_json_keys(e),
3730            Expression::JSONKeysAtDepth(e) => self.generate_json_keys_at_depth(e),
3731            Expression::JSONPath(e) => self.generate_json_path_expr(e),
3732            Expression::JSONPathFilter(e) => self.generate_json_path_filter(e),
3733            Expression::JSONPathKey(e) => self.generate_json_path_key(e),
3734            Expression::JSONPathRecursive(e) => self.generate_json_path_recursive(e),
3735            Expression::JSONPathRoot(_) => self.generate_json_path_root(),
3736            Expression::JSONPathScript(e) => self.generate_json_path_script(e),
3737            Expression::JSONPathSelector(e) => self.generate_json_path_selector(e),
3738            Expression::JSONPathSlice(e) => self.generate_json_path_slice(e),
3739            Expression::JSONPathSubscript(e) => self.generate_json_path_subscript(e),
3740            Expression::JSONPathUnion(e) => self.generate_json_path_union(e),
3741            Expression::JSONRemove(e) => self.generate_json_remove(e),
3742            Expression::JSONSchema(e) => self.generate_json_schema(e),
3743            Expression::JSONSet(e) => self.generate_json_set(e),
3744            Expression::JSONStripNulls(e) => self.generate_json_strip_nulls(e),
3745            Expression::JSONTable(e) => self.generate_json_table(e),
3746            Expression::JSONType(e) => self.generate_json_type(e),
3747            Expression::JSONValue(e) => self.generate_json_value(e),
3748            Expression::JSONValueArray(e) => self.generate_json_value_array(e),
3749            Expression::JarowinklerSimilarity(e) => self.generate_jarowinkler_similarity(e),
3750            Expression::JoinHint(e) => self.generate_join_hint(e),
3751            Expression::JournalProperty(e) => self.generate_journal_property(e),
3752            Expression::LanguageProperty(e) => self.generate_language_property(e),
3753            Expression::Lateral(e) => self.generate_lateral(e),
3754            Expression::LikeProperty(e) => self.generate_like_property(e),
3755            Expression::Limit(e) => self.generate_limit(e),
3756            Expression::LimitOptions(e) => self.generate_limit_options(e),
3757            Expression::List(e) => self.generate_list(e),
3758            Expression::ToMap(e) => self.generate_tomap(e),
3759            Expression::Localtime(e) => self.generate_localtime(e),
3760            Expression::Localtimestamp(e) => self.generate_localtimestamp(e),
3761            Expression::LocationProperty(e) => self.generate_location_property(e),
3762            Expression::Lock(e) => self.generate_lock(e),
3763            Expression::LockProperty(e) => self.generate_lock_property(e),
3764            Expression::LockingProperty(e) => self.generate_locking_property(e),
3765            Expression::LockingStatement(e) => self.generate_locking_statement(e),
3766            Expression::LogProperty(e) => self.generate_log_property(e),
3767            Expression::MD5Digest(e) => self.generate_md5_digest(e),
3768            Expression::MLForecast(e) => self.generate_ml_forecast(e),
3769            Expression::MLTranslate(e) => self.generate_ml_translate(e),
3770            Expression::MakeInterval(e) => self.generate_make_interval(e),
3771            Expression::ManhattanDistance(e) => self.generate_manhattan_distance(e),
3772            Expression::Map(e) => self.generate_map(e),
3773            Expression::MapCat(e) => self.generate_map_cat(e),
3774            Expression::MapDelete(e) => self.generate_map_delete(e),
3775            Expression::MapInsert(e) => self.generate_map_insert(e),
3776            Expression::MapPick(e) => self.generate_map_pick(e),
3777            Expression::MaskingPolicyColumnConstraint(e) => {
3778                self.generate_masking_policy_column_constraint(e)
3779            }
3780            Expression::MatchAgainst(e) => self.generate_match_against(e),
3781            Expression::MatchRecognizeMeasure(e) => self.generate_match_recognize_measure(e),
3782            Expression::MaterializedProperty(e) => self.generate_materialized_property(e),
3783            Expression::Merge(e) => self.generate_merge(e),
3784            Expression::MergeBlockRatioProperty(e) => self.generate_merge_block_ratio_property(e),
3785            Expression::MergeTreeTTL(e) => self.generate_merge_tree_ttl(e),
3786            Expression::MergeTreeTTLAction(e) => self.generate_merge_tree_ttl_action(e),
3787            Expression::Minhash(e) => self.generate_minhash(e),
3788            Expression::ModelAttribute(e) => self.generate_model_attribute(e),
3789            Expression::Monthname(e) => self.generate_monthname(e),
3790            Expression::MultitableInserts(e) => self.generate_multitable_inserts(e),
3791            Expression::NextValueFor(e) => self.generate_next_value_for(e),
3792            Expression::Normal(e) => self.generate_normal(e),
3793            Expression::Normalize(e) => self.generate_normalize(e),
3794            Expression::NotNullColumnConstraint(e) => self.generate_not_null_column_constraint(e),
3795            Expression::Nullif(e) => self.generate_nullif(e),
3796            Expression::NumberToStr(e) => self.generate_number_to_str(e),
3797            Expression::ObjectAgg(e) => self.generate_object_agg(e),
3798            Expression::ObjectIdentifier(e) => self.generate_object_identifier(e),
3799            Expression::ObjectInsert(e) => self.generate_object_insert(e),
3800            Expression::Offset(e) => self.generate_offset(e),
3801            Expression::Qualify(e) => self.generate_qualify(e),
3802            Expression::OnCluster(e) => self.generate_on_cluster(e),
3803            Expression::OnCommitProperty(e) => self.generate_on_commit_property(e),
3804            Expression::OnCondition(e) => self.generate_on_condition(e),
3805            Expression::OnConflict(e) => self.generate_on_conflict(e),
3806            Expression::OnProperty(e) => self.generate_on_property(e),
3807            Expression::Opclass(e) => self.generate_opclass(e),
3808            Expression::OpenJSON(e) => self.generate_open_json(e),
3809            Expression::OpenJSONColumnDef(e) => self.generate_open_json_column_def(e),
3810            Expression::Operator(e) => self.generate_operator(e),
3811            Expression::OrderBy(e) => self.generate_order_by(e),
3812            Expression::OutputModelProperty(e) => self.generate_output_model_property(e),
3813            Expression::OverflowTruncateBehavior(e) => self.generate_overflow_truncate_behavior(e),
3814            Expression::ParameterizedAgg(e) => self.generate_parameterized_agg(e),
3815            Expression::ParseDatetime(e) => self.generate_parse_datetime(e),
3816            Expression::ParseIp(e) => self.generate_parse_ip(e),
3817            Expression::ParseJSON(e) => self.generate_parse_json(e),
3818            Expression::ParseTime(e) => self.generate_parse_time(e),
3819            Expression::ParseUrl(e) => self.generate_parse_url(e),
3820            Expression::Partition(e) => self.generate_partition_expr(e),
3821            Expression::PartitionBoundSpec(e) => self.generate_partition_bound_spec(e),
3822            Expression::PartitionByListProperty(e) => self.generate_partition_by_list_property(e),
3823            Expression::PartitionByRangeProperty(e) => self.generate_partition_by_range_property(e),
3824            Expression::PartitionByRangePropertyDynamic(e) => {
3825                self.generate_partition_by_range_property_dynamic(e)
3826            }
3827            Expression::PartitionByTruncate(e) => self.generate_partition_by_truncate(e),
3828            Expression::PartitionList(e) => self.generate_partition_list(e),
3829            Expression::PartitionRange(e) => self.generate_partition_range(e),
3830            Expression::PartitionByProperty(e) => self.generate_partition_by_property(e),
3831            Expression::PartitionedByBucket(e) => self.generate_partitioned_by_bucket(e),
3832            Expression::PartitionedByProperty(e) => self.generate_partitioned_by_property(e),
3833            Expression::PartitionedOfProperty(e) => self.generate_partitioned_of_property(e),
3834            Expression::PeriodForSystemTimeConstraint(e) => {
3835                self.generate_period_for_system_time_constraint(e)
3836            }
3837            Expression::PivotAlias(e) => self.generate_pivot_alias(e),
3838            Expression::PivotAny(e) => self.generate_pivot_any(e),
3839            Expression::Predict(e) => self.generate_predict(e),
3840            Expression::PreviousDay(e) => self.generate_previous_day(e),
3841            Expression::PrimaryKey(e) => self.generate_primary_key(e),
3842            Expression::PrimaryKeyColumnConstraint(e) => {
3843                self.generate_primary_key_column_constraint(e)
3844            }
3845            Expression::PathColumnConstraint(e) => self.generate_path_column_constraint(e),
3846            Expression::ProjectionDef(e) => self.generate_projection_def(e),
3847            Expression::OptionsProperty(e) => self.generate_options_property(e),
3848            Expression::Properties(e) => self.generate_properties(e),
3849            Expression::Property(e) => self.generate_property(e),
3850            Expression::PseudoType(e) => self.generate_pseudo_type(e),
3851            Expression::Put(e) => self.generate_put(e),
3852            Expression::Quantile(e) => self.generate_quantile(e),
3853            Expression::QueryBand(e) => self.generate_query_band(e),
3854            Expression::QueryOption(e) => self.generate_query_option(e),
3855            Expression::QueryTransform(e) => self.generate_query_transform(e),
3856            Expression::Randn(e) => self.generate_randn(e),
3857            Expression::Randstr(e) => self.generate_randstr(e),
3858            Expression::RangeBucket(e) => self.generate_range_bucket(e),
3859            Expression::RangeN(e) => self.generate_range_n(e),
3860            Expression::ReadCSV(e) => self.generate_read_csv(e),
3861            Expression::ReadParquet(e) => self.generate_read_parquet(e),
3862            Expression::RecursiveWithSearch(e) => self.generate_recursive_with_search(e),
3863            Expression::Reduce(e) => self.generate_reduce(e),
3864            Expression::Reference(e) => self.generate_reference(e),
3865            Expression::Refresh(e) => self.generate_refresh(e),
3866            Expression::RefreshTriggerProperty(e) => self.generate_refresh_trigger_property(e),
3867            Expression::RegexpCount(e) => self.generate_regexp_count(e),
3868            Expression::RegexpExtractAll(e) => self.generate_regexp_extract_all(e),
3869            Expression::RegexpFullMatch(e) => self.generate_regexp_full_match(e),
3870            Expression::RegexpILike(e) => self.generate_regexp_i_like(e),
3871            Expression::RegexpInstr(e) => self.generate_regexp_instr(e),
3872            Expression::RegexpSplit(e) => self.generate_regexp_split(e),
3873            Expression::RegrAvgx(e) => self.generate_regr_avgx(e),
3874            Expression::RegrAvgy(e) => self.generate_regr_avgy(e),
3875            Expression::RegrCount(e) => self.generate_regr_count(e),
3876            Expression::RegrIntercept(e) => self.generate_regr_intercept(e),
3877            Expression::RegrR2(e) => self.generate_regr_r2(e),
3878            Expression::RegrSlope(e) => self.generate_regr_slope(e),
3879            Expression::RegrSxx(e) => self.generate_regr_sxx(e),
3880            Expression::RegrSxy(e) => self.generate_regr_sxy(e),
3881            Expression::RegrSyy(e) => self.generate_regr_syy(e),
3882            Expression::RegrValx(e) => self.generate_regr_valx(e),
3883            Expression::RegrValy(e) => self.generate_regr_valy(e),
3884            Expression::RemoteWithConnectionModelProperty(e) => {
3885                self.generate_remote_with_connection_model_property(e)
3886            }
3887            Expression::RenameColumn(e) => self.generate_rename_column(e),
3888            Expression::ReplacePartition(e) => self.generate_replace_partition(e),
3889            Expression::Returning(e) => self.generate_returning(e),
3890            Expression::ReturnsProperty(e) => self.generate_returns_property(e),
3891            Expression::Rollback(e) => self.generate_rollback(e),
3892            Expression::Rollup(e) => self.generate_rollup(e),
3893            Expression::RowFormatDelimitedProperty(e) => {
3894                self.generate_row_format_delimited_property(e)
3895            }
3896            Expression::RowFormatProperty(e) => self.generate_row_format_property(e),
3897            Expression::RowFormatSerdeProperty(e) => self.generate_row_format_serde_property(e),
3898            Expression::SHA2(e) => self.generate_sha2(e),
3899            Expression::SHA2Digest(e) => self.generate_sha2_digest(e),
3900            Expression::SafeAdd(e) => self.generate_safe_add(e),
3901            Expression::SafeDivide(e) => self.generate_safe_divide(e),
3902            Expression::SafeMultiply(e) => self.generate_safe_multiply(e),
3903            Expression::SafeSubtract(e) => self.generate_safe_subtract(e),
3904            Expression::SampleProperty(e) => self.generate_sample_property(e),
3905            Expression::Schema(e) => self.generate_schema(e),
3906            Expression::SchemaCommentProperty(e) => self.generate_schema_comment_property(e),
3907            Expression::ScopeResolution(e) => self.generate_scope_resolution(e),
3908            Expression::Search(e) => self.generate_search(e),
3909            Expression::SearchIp(e) => self.generate_search_ip(e),
3910            Expression::SecurityProperty(e) => self.generate_security_property(e),
3911            Expression::SemanticView(e) => self.generate_semantic_view(e),
3912            Expression::SequenceProperties(e) => self.generate_sequence_properties(e),
3913            Expression::SerdeProperties(e) => self.generate_serde_properties(e),
3914            Expression::SessionParameter(e) => self.generate_session_parameter(e),
3915            Expression::Set(e) => self.generate_set(e),
3916            Expression::SetConfigProperty(e) => self.generate_set_config_property(e),
3917            Expression::SetItem(e) => self.generate_set_item(e),
3918            Expression::SetOperation(e) => self.generate_set_operation(e),
3919            Expression::SetProperty(e) => self.generate_set_property(e),
3920            Expression::SettingsProperty(e) => self.generate_settings_property(e),
3921            Expression::SharingProperty(e) => self.generate_sharing_property(e),
3922            Expression::Slice(e) => self.generate_slice(e),
3923            Expression::SortArray(e) => self.generate_sort_array(e),
3924            Expression::SortBy(e) => self.generate_sort_by(e),
3925            Expression::SortKeyProperty(e) => self.generate_sort_key_property(e),
3926            Expression::SplitPart(e) => self.generate_split_part(e),
3927            Expression::SqlReadWriteProperty(e) => self.generate_sql_read_write_property(e),
3928            Expression::SqlSecurityProperty(e) => self.generate_sql_security_property(e),
3929            Expression::StDistance(e) => self.generate_st_distance(e),
3930            Expression::StPoint(e) => self.generate_st_point(e),
3931            Expression::StabilityProperty(e) => self.generate_stability_property(e),
3932            Expression::StandardHash(e) => self.generate_standard_hash(e),
3933            Expression::StorageHandlerProperty(e) => self.generate_storage_handler_property(e),
3934            Expression::StrPosition(e) => self.generate_str_position(e),
3935            Expression::StrToDate(e) => self.generate_str_to_date(e),
3936            Expression::DateStrToDate(f) => self.generate_simple_func("DATE_STR_TO_DATE", &f.this),
3937            Expression::DateToDateStr(f) => self.generate_simple_func("DATE_TO_DATE_STR", &f.this),
3938            Expression::StrToMap(e) => self.generate_str_to_map(e),
3939            Expression::StrToTime(e) => self.generate_str_to_time(e),
3940            Expression::StrToUnix(e) => self.generate_str_to_unix(e),
3941            Expression::StringToArray(e) => self.generate_string_to_array(e),
3942            Expression::Struct(e) => self.generate_struct(e),
3943            Expression::Stuff(e) => self.generate_stuff(e),
3944            Expression::SubstringIndex(e) => self.generate_substring_index(e),
3945            Expression::Summarize(e) => self.generate_summarize(e),
3946            Expression::Systimestamp(e) => self.generate_systimestamp(e),
3947            Expression::TableAlias(e) => self.generate_table_alias(e),
3948            Expression::TableFromRows(e) => self.generate_table_from_rows(e),
3949            Expression::RowsFrom(e) => self.generate_rows_from(e),
3950            Expression::TableSample(e) => self.generate_table_sample(e),
3951            Expression::Tag(e) => self.generate_tag(e),
3952            Expression::Tags(e) => self.generate_tags(e),
3953            Expression::TemporaryProperty(e) => self.generate_temporary_property(e),
3954            Expression::Time(e) => self.generate_time_func(e),
3955            Expression::TimeAdd(e) => self.generate_time_add(e),
3956            Expression::TimeDiff(e) => self.generate_time_diff(e),
3957            Expression::TimeFromParts(e) => self.generate_time_from_parts(e),
3958            Expression::TimeSlice(e) => self.generate_time_slice(e),
3959            Expression::TimeStrToDate(e) => self.generate_time_str_to_date(e),
3960            Expression::TimeStrToTime(e) => self.generate_time_str_to_time(e),
3961            Expression::TimeSub(e) => self.generate_time_sub(e),
3962            Expression::TimeToStr(e) => self.generate_time_to_str(e),
3963            Expression::TimeToUnix(e) => self.generate_time_to_unix(e),
3964            Expression::TimeTrunc(e) => self.generate_time_trunc(e),
3965            Expression::TimeUnit(e) => self.generate_time_unit(e),
3966            Expression::Timestamp(e) => self.generate_timestamp_func(e),
3967            Expression::TimestampAdd(e) => self.generate_timestamp_add(e),
3968            Expression::TimestampDiff(e) => self.generate_timestamp_diff(e),
3969            Expression::TimestampFromParts(e) => self.generate_timestamp_from_parts(e),
3970            Expression::TimestampSub(e) => self.generate_timestamp_sub(e),
3971            Expression::TimestampTzFromParts(e) => self.generate_timestamp_tz_from_parts(e),
3972            Expression::ToBinary(e) => self.generate_to_binary(e),
3973            Expression::ToBoolean(e) => self.generate_to_boolean(e),
3974            Expression::ToChar(e) => self.generate_to_char(e),
3975            Expression::ToDecfloat(e) => self.generate_to_decfloat(e),
3976            Expression::ToDouble(e) => self.generate_to_double(e),
3977            Expression::ToFile(e) => self.generate_to_file(e),
3978            Expression::ToNumber(e) => self.generate_to_number(e),
3979            Expression::ToTableProperty(e) => self.generate_to_table_property(e),
3980            Expression::Transaction(e) => self.generate_transaction(e),
3981            Expression::Transform(e) => self.generate_transform(e),
3982            Expression::TransformModelProperty(e) => self.generate_transform_model_property(e),
3983            Expression::TransientProperty(e) => self.generate_transient_property(e),
3984            Expression::Translate(e) => self.generate_translate(e),
3985            Expression::TranslateCharacters(e) => self.generate_translate_characters(e),
3986            Expression::TruncateTable(e) => self.generate_truncate_table(e),
3987            Expression::TryBase64DecodeBinary(e) => self.generate_try_base64_decode_binary(e),
3988            Expression::TryBase64DecodeString(e) => self.generate_try_base64_decode_string(e),
3989            Expression::TryToDecfloat(e) => self.generate_try_to_decfloat(e),
3990            Expression::TsOrDsAdd(e) => self.generate_ts_or_ds_add(e),
3991            Expression::TsOrDsDiff(e) => self.generate_ts_or_ds_diff(e),
3992            Expression::TsOrDsToDate(e) => self.generate_ts_or_ds_to_date(e),
3993            Expression::TsOrDsToTime(e) => self.generate_ts_or_ds_to_time(e),
3994            Expression::Unhex(e) => self.generate_unhex(e),
3995            Expression::UnicodeString(e) => self.generate_unicode_string(e),
3996            Expression::Uniform(e) => self.generate_uniform(e),
3997            Expression::UniqueColumnConstraint(e) => self.generate_unique_column_constraint(e),
3998            Expression::UniqueKeyProperty(e) => self.generate_unique_key_property(e),
3999            Expression::RollupProperty(e) => self.generate_rollup_property(e),
4000            Expression::UnixToStr(e) => self.generate_unix_to_str(e),
4001            Expression::UnixToTime(e) => self.generate_unix_to_time(e),
4002            Expression::UnpivotColumns(e) => self.generate_unpivot_columns(e),
4003            Expression::UserDefinedFunction(e) => self.generate_user_defined_function(e),
4004            Expression::UsingTemplateProperty(e) => self.generate_using_template_property(e),
4005            Expression::UtcTime(e) => self.generate_utc_time(e),
4006            Expression::UtcTimestamp(e) => self.generate_utc_timestamp(e),
4007            Expression::Uuid(e) => self.generate_uuid(e),
4008            Expression::Var(v) => {
4009                if matches!(self.config.dialect, Some(DialectType::MySQL))
4010                    && v.this.len() > 2
4011                    && (v.this.starts_with("0x") || v.this.starts_with("0X"))
4012                    && !v.this[2..].chars().all(|c| c.is_ascii_hexdigit())
4013                {
4014                    return self.generate_identifier(&Identifier {
4015                        name: v.this.clone(),
4016                        quoted: true,
4017                        trailing_comments: Vec::new(),
4018                        span: None,
4019                    });
4020                }
4021                self.write(&v.this);
4022                Ok(())
4023            }
4024            Expression::Variadic(e) => {
4025                self.write_keyword("VARIADIC");
4026                self.write_space();
4027                self.generate_expression(&e.this)?;
4028                Ok(())
4029            }
4030            Expression::VarMap(e) => self.generate_var_map(e),
4031            Expression::VectorSearch(e) => self.generate_vector_search(e),
4032            Expression::Version(e) => self.generate_version(e),
4033            Expression::ViewAttributeProperty(e) => self.generate_view_attribute_property(e),
4034            Expression::VolatileProperty(e) => self.generate_volatile_property(e),
4035            Expression::WatermarkColumnConstraint(e) => {
4036                self.generate_watermark_column_constraint(e)
4037            }
4038            Expression::Week(e) => self.generate_week(e),
4039            Expression::When(e) => self.generate_when(e),
4040            Expression::Whens(e) => self.generate_whens(e),
4041            Expression::Where(e) => self.generate_where(e),
4042            Expression::WidthBucket(e) => self.generate_width_bucket(e),
4043            Expression::Window(e) => self.generate_window(e),
4044            Expression::WindowSpec(e) => self.generate_window_spec(e),
4045            Expression::WithDataProperty(e) => self.generate_with_data_property(e),
4046            Expression::WithFill(e) => self.generate_with_fill(e),
4047            Expression::WithJournalTableProperty(e) => self.generate_with_journal_table_property(e),
4048            Expression::WithOperator(e) => self.generate_with_operator(e),
4049            Expression::WithProcedureOptions(e) => self.generate_with_procedure_options(e),
4050            Expression::WithSchemaBindingProperty(e) => {
4051                self.generate_with_schema_binding_property(e)
4052            }
4053            Expression::WithSystemVersioningProperty(e) => {
4054                self.generate_with_system_versioning_property(e)
4055            }
4056            Expression::WithTableHint(e) => self.generate_with_table_hint(e),
4057            Expression::XMLElement(e) => self.generate_xml_element(e),
4058            Expression::XMLGet(e) => self.generate_xml_get(e),
4059            Expression::XMLKeyValueOption(e) => self.generate_xml_key_value_option(e),
4060            Expression::XMLTable(e) => self.generate_xml_table(e),
4061            Expression::Xor(e) => self.generate_xor(e),
4062            Expression::Zipf(e) => self.generate_zipf(e),
4063            _ => self.write_unsupported_comment("unsupported expression"),
4064        }
4065    }
4066
4067    fn should_handle_tsql_distinct_null_ordering(&self, select: &Select) -> bool {
4068        matches!(
4069            self.config.dialect,
4070            Some(DialectType::TSQL) | Some(DialectType::Fabric)
4071        ) && !self.config.null_ordering_supported
4072            && select.distinct
4073            && select.order_by.as_ref().is_some_and(|order_by| {
4074                order_by
4075                    .expressions
4076                    .iter()
4077                    .any(Self::ordered_requires_tsql_null_ordering_emulation)
4078            })
4079    }
4080
4081    fn ordered_requires_tsql_null_ordering_emulation(ordered: &Ordered) -> bool {
4082        let Some(nulls_first) = ordered.nulls_first else {
4083            return false;
4084        };
4085
4086        let random_ordering = matches!(ordered.this, Expression::Rand(_) | Expression::Random(_));
4087        let target_default_nulls_first = !ordered.desc;
4088
4089        nulls_first != target_default_nulls_first && !random_ordering
4090    }
4091
4092    fn projection_output_identifier(expression: &Expression) -> Option<Identifier> {
4093        match expression {
4094            Expression::Alias(alias) if !alias.alias.name.is_empty() => Some(alias.alias.clone()),
4095            Expression::Column(column) => Some(column.name.clone()),
4096            Expression::Identifier(identifier) => Some(identifier.clone()),
4097            _ => None,
4098        }
4099    }
4100
4101    fn identifier_names_match(left: &Identifier, right: &Identifier) -> bool {
4102        if left.quoted || right.quoted {
4103            left.name == right.name
4104        } else {
4105            left.name.eq_ignore_ascii_case(&right.name)
4106        }
4107    }
4108
4109    fn expression_matches_identifier(expression: &Expression, identifier: &Identifier) -> bool {
4110        match expression {
4111            Expression::Column(column) if column.table.is_none() => {
4112                Self::identifier_names_match(&column.name, identifier)
4113            }
4114            Expression::Identifier(other) => Self::identifier_names_match(other, identifier),
4115            _ => false,
4116        }
4117    }
4118
4119    fn column_expression(identifier: Identifier) -> Expression {
4120        Expression::Column(Box::new(Column {
4121            name: identifier,
4122            table: None,
4123            join_mark: false,
4124            trailing_comments: Vec::new(),
4125            span: None,
4126            inferred_type: None,
4127        }))
4128    }
4129
4130    fn resolve_distinct_order_projection(
4131        projections: &[Expression],
4132        order_expression: &Expression,
4133    ) -> Option<Expression> {
4134        for projection in projections {
4135            if let Expression::Alias(alias) = projection {
4136                if Self::expression_matches_identifier(order_expression, &alias.alias)
4137                    || &alias.this == order_expression
4138                {
4139                    return Some(alias.this.clone());
4140                }
4141            } else if projection == order_expression {
4142                return Some(projection.clone());
4143            } else if let Some(identifier) = Self::projection_output_identifier(projection) {
4144                if Self::expression_matches_identifier(order_expression, &identifier) {
4145                    return Some(projection.clone());
4146                }
4147            }
4148        }
4149
4150        None
4151    }
4152
4153    fn fresh_polyglot_alias(base: &str, index: usize, used_names: &mut Vec<String>) -> String {
4154        let mut suffix = 0;
4155        loop {
4156            let candidate = if suffix == 0 {
4157                format!("{base}_{index}")
4158            } else {
4159                format!("{base}_{index}_{suffix}")
4160            };
4161
4162            if !used_names
4163                .iter()
4164                .any(|name| name.eq_ignore_ascii_case(&candidate))
4165            {
4166                used_names.push(candidate.clone());
4167                return candidate;
4168            }
4169
4170            suffix += 1;
4171        }
4172    }
4173
4174    fn tsql_null_ordering_case(expression: Expression) -> Expression {
4175        Expression::Case(Box::new(Case {
4176            operand: None,
4177            whens: vec![(
4178                Expression::IsNull(Box::new(IsNull {
4179                    this: expression,
4180                    not: false,
4181                    postfix_form: false,
4182                })),
4183                Expression::number(1),
4184            )],
4185            else_: Some(Expression::number(0)),
4186            comments: Vec::new(),
4187            inferred_type: None,
4188        }))
4189    }
4190
4191    fn try_build_tsql_distinct_null_ordering_wrapper(&self, select: &Select) -> Option<Select> {
4192        let order_by = select.order_by.as_ref()?;
4193
4194        if order_by.siblings
4195            || select.distinct_on.is_some()
4196            || select.distribute_by.is_some()
4197            || select.cluster_by.is_some()
4198            || select.sort_by.is_some()
4199            || select.limit.is_some()
4200            || select.offset.is_some()
4201            || select.limit_by.is_some()
4202            || select.fetch.is_some()
4203            || select.top.is_some()
4204            || select.settings.is_some()
4205            || select.format.is_some()
4206            || select.kind.is_some()
4207            || select.hint.is_some()
4208            || select.into.is_some()
4209            || !select.locks.is_empty()
4210            || !select.for_xml.is_empty()
4211            || !select.for_json.is_empty()
4212            || !select.operation_modifiers.is_empty()
4213            || select.option.is_some()
4214            || select.exclude.is_some()
4215        {
4216            return None;
4217        }
4218
4219        let projection_identifiers: Vec<_> = select
4220            .expressions
4221            .iter()
4222            .map(Self::projection_output_identifier)
4223            .collect::<Option<_>>()?;
4224        let mut used_names: Vec<_> = projection_identifiers
4225            .iter()
4226            .map(|identifier| identifier.name.clone())
4227            .collect();
4228
4229        let mut inner = select.clone();
4230        inner.order_by = None;
4231
4232        let outer_with = inner.with.take();
4233        let outer_leading_comments = std::mem::take(&mut inner.leading_comments);
4234        let outer_post_select_comments = std::mem::take(&mut inner.post_select_comments);
4235
4236        let mut outer_order_expressions = Vec::with_capacity(order_by.expressions.len() * 2);
4237        for (index, ordered) in order_by.expressions.iter().enumerate() {
4238            if ordered.with_fill.is_some() {
4239                return None;
4240            }
4241
4242            let sort_expression =
4243                Self::resolve_distinct_order_projection(&select.expressions, &ordered.this)?;
4244
4245            if Self::ordered_requires_tsql_null_ordering_emulation(ordered) {
4246                let null_alias =
4247                    Self::fresh_polyglot_alias("_polyglot_order_null", index, &mut used_names);
4248                inner.expressions.push(
4249                    Self::tsql_null_ordering_case(sort_expression.clone())
4250                        .alias(null_alias.clone()),
4251                );
4252                outer_order_expressions.push(Ordered {
4253                    this: Expression::column(null_alias),
4254                    desc: ordered.nulls_first == Some(true),
4255                    nulls_first: None,
4256                    explicit_asc: false,
4257                    with_fill: None,
4258                });
4259            }
4260
4261            let key_alias =
4262                Self::fresh_polyglot_alias("_polyglot_order_key", index, &mut used_names);
4263            inner
4264                .expressions
4265                .push(sort_expression.alias(key_alias.clone()));
4266            outer_order_expressions.push(Ordered {
4267                this: Expression::column(key_alias),
4268                desc: ordered.desc,
4269                nulls_first: None,
4270                explicit_asc: ordered.explicit_asc,
4271                with_fill: None,
4272            });
4273        }
4274
4275        let subquery = Subquery {
4276            this: Expression::Select(Box::new(inner)),
4277            alias: Some(Identifier::new("_polyglot_distinct_order")),
4278            column_aliases: Vec::new(),
4279            alias_explicit_as: true,
4280            alias_keyword: None,
4281            order_by: None,
4282            limit: None,
4283            offset: None,
4284            distribute_by: None,
4285            sort_by: None,
4286            cluster_by: None,
4287            lateral: false,
4288            modifiers_inside: false,
4289            trailing_comments: Vec::new(),
4290            inferred_type: None,
4291        };
4292
4293        let mut outer = Select::new();
4294        outer.with = outer_with;
4295        outer.leading_comments = outer_leading_comments;
4296        outer.post_select_comments = outer_post_select_comments;
4297        outer.expressions = projection_identifiers
4298            .into_iter()
4299            .map(Self::column_expression)
4300            .collect();
4301        outer.from = Some(From {
4302            expressions: vec![Expression::Subquery(Box::new(subquery))],
4303        });
4304        outer.order_by = Some(OrderBy {
4305            expressions: outer_order_expressions,
4306            siblings: false,
4307            comments: order_by.comments.clone(),
4308        });
4309
4310        Some(outer)
4311    }
4312
4313    fn generate_select(&mut self, select: &Select) -> Result<()> {
4314        use crate::dialects::DialectType;
4315
4316        if self.should_handle_tsql_distinct_null_ordering(select) {
4317            if let Some(wrapped_select) = self.try_build_tsql_distinct_null_ordering_wrapper(select)
4318            {
4319                return self.generate_select(&wrapped_select);
4320            }
4321
4322            self.unsupported(
4323                "SELECT DISTINCT with emulated NULL ordering is not supported for TSQL/Fabric",
4324            )?;
4325        }
4326
4327        // Redshift-style EXCLUDE: for dialects other than Redshift, wrap in a derived table
4328        // e.g., SELECT *, col4 EXCLUDE (col2, col3) FROM t
4329        //   → SELECT * EXCLUDE (col2, col3) FROM (SELECT *, col4 FROM t)
4330        if let Some(exclude) = &select.exclude {
4331            if !exclude.is_empty() && !matches!(self.config.dialect, Some(DialectType::Redshift)) {
4332                // Build the inner select (same as original but without exclude)
4333                let mut inner_select = select.clone();
4334                inner_select.exclude = None;
4335                let inner_expr = Expression::Select(Box::new(inner_select));
4336
4337                // Build the subquery
4338                let subquery = crate::expressions::Subquery {
4339                    this: inner_expr,
4340                    alias: None,
4341                    column_aliases: Vec::new(),
4342                    alias_explicit_as: false,
4343                    alias_keyword: None,
4344                    order_by: None,
4345                    limit: None,
4346                    offset: None,
4347                    distribute_by: None,
4348                    sort_by: None,
4349                    cluster_by: None,
4350                    lateral: false,
4351                    modifiers_inside: false,
4352                    trailing_comments: Vec::new(),
4353                    inferred_type: None,
4354                };
4355
4356                // Build the outer select: SELECT * EXCLUDE (cols) FROM (inner)
4357                let star = Expression::Star(crate::expressions::Star {
4358                    table: None,
4359                    except: Some(
4360                        exclude
4361                            .iter()
4362                            .map(|e| match e {
4363                                Expression::Column(col) => col.name.clone(),
4364                                Expression::Identifier(id) => id.clone(),
4365                                _ => crate::expressions::Identifier::new("unknown".to_string()),
4366                            })
4367                            .collect(),
4368                    ),
4369                    replace: None,
4370                    rename: None,
4371                    trailing_comments: Vec::new(),
4372                    span: None,
4373                });
4374
4375                let outer_select = Select {
4376                    expressions: vec![star],
4377                    from: Some(crate::expressions::From {
4378                        expressions: vec![Expression::Subquery(Box::new(subquery))],
4379                    }),
4380                    ..Select::new()
4381                };
4382
4383                return self.generate_select(&outer_select);
4384            }
4385        }
4386
4387        // Output leading comments before SELECT
4388        for comment in &select.leading_comments {
4389            self.write_formatted_comment(comment);
4390            self.write(" ");
4391        }
4392
4393        // WITH clause
4394        if let Some(with) = &select.with {
4395            self.generate_with(with)?;
4396            if self.config.pretty {
4397                self.write_newline();
4398                self.write_indent();
4399            } else {
4400                self.write_space();
4401            }
4402        }
4403
4404        // Output post-SELECT comments (comments that appeared after SELECT keyword)
4405        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
4406        for comment in &select.post_select_comments {
4407            self.write_formatted_comment(comment);
4408            self.write(" ");
4409        }
4410
4411        self.write_keyword("SELECT");
4412
4413        // Generate query hint if present /*+ ... */
4414        if let Some(hint) = &select.hint {
4415            self.generate_hint(hint)?;
4416        }
4417
4418        // For SQL Server, convert LIMIT to TOP (structural transformation)
4419        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
4420        // TOP clause (SQL Server style - before DISTINCT)
4421        let use_top_from_limit = matches!(
4422            self.config.dialect,
4423            Some(DialectType::TSQL) | Some(DialectType::Fabric)
4424        ) && select.top.is_none()
4425            && select.limit.is_some()
4426            && select.offset.is_none(); // Don't use TOP when there's OFFSET
4427
4428        // For TOP-supporting dialects: DISTINCT before TOP
4429        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
4430        let is_top_dialect = matches!(
4431            self.config.dialect,
4432            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
4433        );
4434        let keep_top_verbatim = !is_top_dialect
4435            && select.limit.is_none()
4436            && select
4437                .top
4438                .as_ref()
4439                .map_or(false, |top| top.percent || top.with_ties);
4440
4441        if select.distinct && (is_top_dialect || select.top.is_some()) {
4442            self.write_space();
4443            self.write_keyword("DISTINCT");
4444        }
4445
4446        if is_top_dialect || keep_top_verbatim {
4447            if let Some(top) = &select.top {
4448                self.write_space();
4449                self.write_keyword("TOP");
4450                if top.parenthesized {
4451                    if matches!(&top.this, Expression::Subquery(_) | Expression::Paren(_)) {
4452                        self.write_space();
4453                        self.generate_expression(&top.this)?;
4454                    } else {
4455                        self.write(" (");
4456                        self.generate_expression(&top.this)?;
4457                        self.write(")");
4458                    }
4459                } else {
4460                    self.write_space();
4461                    self.generate_expression(&top.this)?;
4462                }
4463                if top.percent {
4464                    self.write_space();
4465                    self.write_keyword("PERCENT");
4466                }
4467                if top.with_ties {
4468                    self.write_space();
4469                    self.write_keyword("WITH TIES");
4470                }
4471            } else if use_top_from_limit {
4472                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
4473                if let Some(limit) = &select.limit {
4474                    self.write_space();
4475                    self.write_keyword("TOP");
4476                    // Use parentheses for complex expressions, but not for simple literals
4477                    let is_simple_literal = matches!(&limit.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)));
4478                    if is_simple_literal {
4479                        self.write_space();
4480                        self.generate_expression(&limit.this)?;
4481                    } else {
4482                        self.write(" (");
4483                        self.generate_expression(&limit.this)?;
4484                        self.write(")");
4485                    }
4486                }
4487            }
4488        }
4489
4490        if select.distinct && !is_top_dialect && select.top.is_none() {
4491            self.write_space();
4492            self.write_keyword("DISTINCT");
4493        }
4494
4495        // DISTINCT ON clause (PostgreSQL)
4496        if let Some(distinct_on) = &select.distinct_on {
4497            self.write_space();
4498            self.write_keyword("ON");
4499            self.write(" (");
4500            for (i, expr) in distinct_on.iter().enumerate() {
4501                if i > 0 {
4502                    self.write(", ");
4503                }
4504                self.generate_expression(expr)?;
4505            }
4506            self.write(")");
4507        }
4508
4509        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
4510        for modifier in &select.operation_modifiers {
4511            self.write_space();
4512            self.write_keyword(modifier);
4513        }
4514
4515        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
4516        if let Some(kind) = &select.kind {
4517            self.write_space();
4518            self.write_keyword("AS");
4519            self.write_space();
4520            self.write_keyword(kind);
4521        }
4522
4523        // Expressions (only if there are any)
4524        if !select.expressions.is_empty() {
4525            if self.config.pretty {
4526                self.write_newline();
4527                self.indent_level += 1;
4528            } else {
4529                self.write_space();
4530            }
4531        }
4532
4533        for (i, expr) in select.expressions.iter().enumerate() {
4534            if i > 0 {
4535                self.write(",");
4536                if self.config.pretty {
4537                    self.write_newline();
4538                } else {
4539                    self.write_space();
4540                }
4541            }
4542            if self.config.pretty {
4543                self.write_indent();
4544            }
4545            self.generate_expression(expr)?;
4546        }
4547
4548        if self.config.pretty && !select.expressions.is_empty() {
4549            self.indent_level -= 1;
4550        }
4551
4552        // Redshift-style EXCLUDE clause at the end of the projection list
4553        // For Redshift dialect: append EXCLUDE (col1, col2) after the expressions
4554        // For other dialects (DuckDB, Snowflake): this is handled by wrapping in a derived table
4555        // (done after the full select is generated below)
4556        if let Some(exclude) = &select.exclude {
4557            if !exclude.is_empty() && matches!(self.config.dialect, Some(DialectType::Redshift)) {
4558                self.write_space();
4559                self.write_keyword("EXCLUDE");
4560                self.write(" (");
4561                for (i, col) in exclude.iter().enumerate() {
4562                    if i > 0 {
4563                        self.write(", ");
4564                    }
4565                    self.generate_expression(col)?;
4566                }
4567                self.write(")");
4568            }
4569        }
4570
4571        // INTO clause (SELECT ... INTO table_name)
4572        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4573        if let Some(into) = &select.into {
4574            if self.config.pretty {
4575                self.write_newline();
4576                self.write_indent();
4577            } else {
4578                self.write_space();
4579            }
4580            if into.bulk_collect {
4581                self.write_keyword("BULK COLLECT INTO");
4582            } else {
4583                self.write_keyword("INTO");
4584            }
4585            if into.temporary {
4586                self.write_space();
4587                self.write_keyword("TEMPORARY");
4588            }
4589            if into.unlogged {
4590                self.write_space();
4591                self.write_keyword("UNLOGGED");
4592            }
4593            self.write_space();
4594            // If we have multiple expressions, output them comma-separated
4595            if !into.expressions.is_empty() {
4596                for (i, expr) in into.expressions.iter().enumerate() {
4597                    if i > 0 {
4598                        self.write(", ");
4599                    }
4600                    self.generate_expression(expr)?;
4601                }
4602            } else {
4603                self.generate_expression(&into.this)?;
4604            }
4605        }
4606
4607        // FROM clause
4608        if let Some(from) = &select.from {
4609            if self.config.pretty {
4610                self.write_newline();
4611                self.write_indent();
4612            } else {
4613                self.write_space();
4614            }
4615            self.write_keyword("FROM");
4616            self.write_space();
4617
4618            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4619            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4620            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4621            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4622            let has_tablesample = from
4623                .expressions
4624                .iter()
4625                .any(|e| matches!(e, Expression::TableSample(_)));
4626            let is_cross_join_dialect = matches!(
4627                self.config.dialect,
4628                Some(DialectType::BigQuery)
4629                    | Some(DialectType::Hive)
4630                    | Some(DialectType::Spark)
4631                    | Some(DialectType::Databricks)
4632                    | Some(DialectType::SQLite)
4633                    | Some(DialectType::ClickHouse)
4634            );
4635            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4636            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4637            let source_is_same_as_target = self.config.source_dialect.is_some()
4638                && self.config.source_dialect == self.config.dialect;
4639            let source_is_cross_join_dialect = matches!(
4640                self.config.source_dialect,
4641                Some(DialectType::BigQuery)
4642                    | Some(DialectType::Hive)
4643                    | Some(DialectType::Spark)
4644                    | Some(DialectType::Databricks)
4645                    | Some(DialectType::SQLite)
4646                    | Some(DialectType::ClickHouse)
4647            );
4648            let use_cross_join = !has_tablesample
4649                && is_cross_join_dialect
4650                && (source_is_same_as_target
4651                    || source_is_cross_join_dialect
4652                    || self.config.source_dialect.is_none());
4653
4654            // Snowflake wraps standalone VALUES in FROM clause with parentheses
4655            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
4656
4657            for (i, expr) in from.expressions.iter().enumerate() {
4658                if i > 0 {
4659                    if use_cross_join {
4660                        self.write(" CROSS JOIN ");
4661                    } else {
4662                        self.write(", ");
4663                    }
4664                }
4665                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
4666                    self.write("(");
4667                    self.generate_expression(expr)?;
4668                    self.write(")");
4669                } else {
4670                    self.generate_expression(expr)?;
4671                }
4672                // Output leading comments that were on the table name before FROM
4673                // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
4674                let leading = Self::extract_table_leading_comments(expr);
4675                for comment in &leading {
4676                    self.write_space();
4677                    self.write_formatted_comment(comment);
4678                }
4679            }
4680        }
4681
4682        // JOINs - handle nested join structure for pretty printing
4683        // Deferred-condition joins "own" the non-deferred joins that follow them
4684        // until the next deferred join or end of list
4685        if self.config.pretty {
4686            self.generate_joins_with_nesting(&select.joins)?;
4687        } else {
4688            for join in &select.joins {
4689                self.generate_join(join)?;
4690            }
4691            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4692            for join in select.joins.iter().rev() {
4693                if join.deferred_condition {
4694                    self.generate_join_condition(join)?;
4695                }
4696            }
4697        }
4698
4699        // LATERAL VIEW clauses (Hive/Spark)
4700        for (lv_idx, lateral_view) in select.lateral_views.iter().enumerate() {
4701            self.generate_lateral_view(lateral_view, lv_idx)?;
4702        }
4703
4704        // PREWHERE (ClickHouse)
4705        if let Some(prewhere) = &select.prewhere {
4706            self.write_clause_condition("PREWHERE", prewhere)?;
4707        }
4708
4709        // WHERE
4710        if let Some(where_clause) = &select.where_clause {
4711            self.write_clause_condition("WHERE", &where_clause.this)?;
4712        }
4713
4714        // CONNECT BY (Oracle hierarchical queries)
4715        if let Some(connect) = &select.connect {
4716            self.generate_connect(connect)?;
4717        }
4718
4719        // GROUP BY
4720        if let Some(group_by) = &select.group_by {
4721            if self.config.pretty {
4722                // Output leading comments on their own lines before GROUP BY
4723                for comment in &group_by.comments {
4724                    self.write_newline();
4725                    self.write_indent();
4726                    self.write_formatted_comment(comment);
4727                }
4728                self.write_newline();
4729                self.write_indent();
4730            } else {
4731                self.write_space();
4732                // In non-pretty mode, output comments inline
4733                for comment in &group_by.comments {
4734                    self.write_formatted_comment(comment);
4735                    self.write_space();
4736                }
4737            }
4738            let clickhouse_bare_modifiers =
4739                matches!(self.config.dialect, Some(DialectType::ClickHouse))
4740                    && group_by.all.is_none()
4741                    && (group_by.totals || !group_by.expressions.is_empty())
4742                    && group_by.expressions.iter().all(|expr| match expr {
4743                        Expression::Cube(c) => c.expressions.is_empty(),
4744                        Expression::Rollup(r) => r.expressions.is_empty(),
4745                        _ => false,
4746                    });
4747
4748            if clickhouse_bare_modifiers {
4749                let trailing_cube = group_by
4750                    .expressions
4751                    .iter()
4752                    .any(|expr| matches!(expr, Expression::Cube(c) if c.expressions.is_empty()));
4753                let trailing_rollup = group_by
4754                    .expressions
4755                    .iter()
4756                    .any(|expr| matches!(expr, Expression::Rollup(r) if r.expressions.is_empty()));
4757
4758                if trailing_cube {
4759                    self.write_keyword("WITH CUBE");
4760                } else if trailing_rollup {
4761                    self.write_keyword("WITH ROLLUP");
4762                }
4763
4764                if group_by.totals {
4765                    if trailing_cube || trailing_rollup {
4766                        self.write_space();
4767                    }
4768                    self.write_keyword("WITH TOTALS");
4769                }
4770            } else {
4771                self.write_keyword("GROUP BY");
4772                // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
4773                match group_by.all {
4774                    Some(true) => {
4775                        self.write_space();
4776                        self.write_keyword("ALL");
4777                    }
4778                    Some(false) => {
4779                        self.write_space();
4780                        self.write_keyword("DISTINCT");
4781                    }
4782                    None => {}
4783                }
4784                if !group_by.expressions.is_empty() {
4785                    // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
4786                    // These are represented as Cube/Rollup expressions with empty expressions at the end
4787                    let mut trailing_cube = false;
4788                    let mut trailing_rollup = false;
4789                    let mut plain_expressions: Vec<&Expression> = Vec::new();
4790                    let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
4791                    let mut cube_expressions: Vec<&Expression> = Vec::new();
4792                    let mut rollup_expressions: Vec<&Expression> = Vec::new();
4793
4794                    for expr in &group_by.expressions {
4795                        match expr {
4796                            Expression::Cube(c) if c.expressions.is_empty() => {
4797                                trailing_cube = true;
4798                            }
4799                            Expression::Rollup(r) if r.expressions.is_empty() => {
4800                                trailing_rollup = true;
4801                            }
4802                            Expression::Function(f) if f.name == "CUBE" => {
4803                                cube_expressions.push(expr);
4804                            }
4805                            Expression::Function(f) if f.name == "ROLLUP" => {
4806                                rollup_expressions.push(expr);
4807                            }
4808                            Expression::Function(f) if f.name == "GROUPING SETS" => {
4809                                grouping_sets_expressions.push(expr);
4810                            }
4811                            _ => {
4812                                plain_expressions.push(expr);
4813                            }
4814                        }
4815                    }
4816
4817                    // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
4818                    let mut regular_expressions: Vec<&Expression> = Vec::new();
4819                    regular_expressions.extend(plain_expressions);
4820                    regular_expressions.extend(grouping_sets_expressions);
4821                    regular_expressions.extend(cube_expressions);
4822                    regular_expressions.extend(rollup_expressions);
4823
4824                    if self.config.pretty {
4825                        self.write_newline();
4826                        self.indent_level += 1;
4827                        self.write_indent();
4828                    } else {
4829                        self.write_space();
4830                    }
4831
4832                    for (i, expr) in regular_expressions.iter().enumerate() {
4833                        if i > 0 {
4834                            if self.config.pretty {
4835                                self.write(",");
4836                                self.write_newline();
4837                                self.write_indent();
4838                            } else {
4839                                self.write(", ");
4840                            }
4841                        }
4842                        self.generate_expression(expr)?;
4843                    }
4844
4845                    if self.config.pretty {
4846                        self.indent_level -= 1;
4847                    }
4848
4849                    // Output trailing WITH CUBE or WITH ROLLUP
4850                    if trailing_cube {
4851                        self.write_space();
4852                        self.write_keyword("WITH CUBE");
4853                    } else if trailing_rollup {
4854                        self.write_space();
4855                        self.write_keyword("WITH ROLLUP");
4856                    }
4857                }
4858
4859                // ClickHouse: WITH TOTALS
4860                if group_by.totals {
4861                    self.write_space();
4862                    self.write_keyword("WITH TOTALS");
4863                }
4864            }
4865        }
4866
4867        // HAVING
4868        if let Some(having) = &select.having {
4869            if self.config.pretty {
4870                // Output leading comments on their own lines before HAVING
4871                for comment in &having.comments {
4872                    self.write_newline();
4873                    self.write_indent();
4874                    self.write_formatted_comment(comment);
4875                }
4876            } else {
4877                for comment in &having.comments {
4878                    self.write_space();
4879                    self.write_formatted_comment(comment);
4880                }
4881            }
4882            self.write_clause_condition("HAVING", &having.this)?;
4883        }
4884
4885        // QUALIFY and WINDOW clause ordering depends on input SQL
4886        if select.qualify_after_window {
4887            // WINDOW before QUALIFY (DuckDB style)
4888            if let Some(windows) = &select.windows {
4889                self.write_window_clause(windows)?;
4890            }
4891            if let Some(qualify) = &select.qualify {
4892                self.write_clause_condition("QUALIFY", &qualify.this)?;
4893            }
4894        } else {
4895            // QUALIFY before WINDOW (Snowflake/BigQuery default)
4896            if let Some(qualify) = &select.qualify {
4897                self.write_clause_condition("QUALIFY", &qualify.this)?;
4898            }
4899            if let Some(windows) = &select.windows {
4900                self.write_window_clause(windows)?;
4901            }
4902        }
4903
4904        // DISTRIBUTE BY (Hive/Spark)
4905        if let Some(distribute_by) = &select.distribute_by {
4906            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
4907        }
4908
4909        // CLUSTER BY (Hive/Spark)
4910        if let Some(cluster_by) = &select.cluster_by {
4911            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
4912        }
4913
4914        // SORT BY (Hive/Spark - comes before ORDER BY)
4915        if let Some(sort_by) = &select.sort_by {
4916            self.write_order_clause("SORT BY", &sort_by.expressions)?;
4917        }
4918
4919        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
4920        if let Some(order_by) = &select.order_by {
4921            if self.config.pretty {
4922                // Output leading comments on their own lines before ORDER BY
4923                for comment in &order_by.comments {
4924                    self.write_newline();
4925                    self.write_indent();
4926                    self.write_formatted_comment(comment);
4927                }
4928            } else {
4929                for comment in &order_by.comments {
4930                    self.write_space();
4931                    self.write_formatted_comment(comment);
4932                }
4933            }
4934            let keyword = if order_by.siblings {
4935                "ORDER SIBLINGS BY"
4936            } else {
4937                "ORDER BY"
4938            };
4939            self.write_order_clause(keyword, &order_by.expressions)?;
4940        }
4941
4942        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
4943        if select.order_by.is_none()
4944            && select.fetch.is_some()
4945            && matches!(
4946                self.config.dialect,
4947                Some(DialectType::TSQL) | Some(DialectType::Fabric)
4948            )
4949        {
4950            if self.config.pretty {
4951                self.write_newline();
4952                self.write_indent();
4953            } else {
4954                self.write_space();
4955            }
4956            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
4957        }
4958
4959        // LIMIT and OFFSET
4960        // PostgreSQL and others use: LIMIT count OFFSET offset
4961        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
4962        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
4963        let is_presto_like = matches!(
4964            self.config.dialect,
4965            Some(DialectType::Presto) | Some(DialectType::Trino)
4966        );
4967
4968        if is_presto_like && select.offset.is_some() {
4969            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
4970            if let Some(offset) = &select.offset {
4971                if self.config.pretty {
4972                    self.write_newline();
4973                    self.write_indent();
4974                } else {
4975                    self.write_space();
4976                }
4977                self.write_keyword("OFFSET");
4978                self.write_space();
4979                self.write_limit_expr(&offset.this)?;
4980                if offset.rows == Some(true) {
4981                    self.write_space();
4982                    self.write_keyword("ROWS");
4983                }
4984            }
4985            if let Some(limit) = &select.limit {
4986                if self.config.pretty {
4987                    self.write_newline();
4988                    self.write_indent();
4989                } else {
4990                    self.write_space();
4991                }
4992                self.write_keyword("LIMIT");
4993                self.write_space();
4994                self.write_limit_expr(&limit.this)?;
4995                if limit.percent {
4996                    self.write_space();
4997                    self.write_keyword("PERCENT");
4998                }
4999                // Emit any comments that were captured from before the LIMIT keyword
5000                for comment in &limit.comments {
5001                    self.write(" ");
5002                    self.write_formatted_comment(comment);
5003                }
5004            }
5005        } else {
5006            // Check if FETCH will be converted to LIMIT (used for ordering)
5007            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
5008                !fetch.percent
5009                    && !fetch.with_ties
5010                    && fetch.count.is_some()
5011                    && matches!(
5012                        self.config.dialect,
5013                        Some(DialectType::Spark)
5014                            | Some(DialectType::Hive)
5015                            | Some(DialectType::DuckDB)
5016                            | Some(DialectType::SQLite)
5017                            | Some(DialectType::MySQL)
5018                            | Some(DialectType::BigQuery)
5019                            | Some(DialectType::Databricks)
5020                            | Some(DialectType::StarRocks)
5021                            | Some(DialectType::Doris)
5022                            | Some(DialectType::Athena)
5023                            | Some(DialectType::ClickHouse)
5024                            | Some(DialectType::Redshift)
5025                    )
5026            });
5027
5028            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
5029            if let Some(limit) = &select.limit {
5030                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
5031                if !matches!(
5032                    self.config.dialect,
5033                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
5034                ) {
5035                    if self.config.pretty {
5036                        self.write_newline();
5037                        self.write_indent();
5038                    } else {
5039                        self.write_space();
5040                    }
5041                    self.write_keyword("LIMIT");
5042                    self.write_space();
5043                    self.write_limit_expr(&limit.this)?;
5044                    if limit.percent {
5045                        self.write_space();
5046                        self.write_keyword("PERCENT");
5047                    }
5048                    // Emit any comments that were captured from before the LIMIT keyword
5049                    for comment in &limit.comments {
5050                        self.write(" ");
5051                        self.write_formatted_comment(comment);
5052                    }
5053                }
5054            }
5055
5056            // Convert TOP to LIMIT for non-TOP dialects
5057            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
5058                if let Some(top) = &select.top {
5059                    if !top.percent && !top.with_ties {
5060                        if self.config.pretty {
5061                            self.write_newline();
5062                            self.write_indent();
5063                        } else {
5064                            self.write_space();
5065                        }
5066                        self.write_keyword("LIMIT");
5067                        self.write_space();
5068                        self.generate_expression(&top.this)?;
5069                    }
5070                }
5071            }
5072
5073            // If FETCH will be converted to LIMIT and there's also OFFSET,
5074            // emit LIMIT from FETCH BEFORE the OFFSET
5075            if fetch_as_limit && select.offset.is_some() {
5076                if let Some(fetch) = &select.fetch {
5077                    if self.config.pretty {
5078                        self.write_newline();
5079                        self.write_indent();
5080                    } else {
5081                        self.write_space();
5082                    }
5083                    self.write_keyword("LIMIT");
5084                    self.write_space();
5085                    self.generate_expression(fetch.count.as_ref().unwrap())?;
5086                }
5087            }
5088
5089            // OFFSET
5090            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
5091            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
5092            if let Some(offset) = &select.offset {
5093                if self.config.pretty {
5094                    self.write_newline();
5095                    self.write_indent();
5096                } else {
5097                    self.write_space();
5098                }
5099                if matches!(
5100                    self.config.dialect,
5101                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
5102                ) {
5103                    // SQL Server 2012+ OFFSET ... FETCH syntax
5104                    self.write_keyword("OFFSET");
5105                    self.write_space();
5106                    self.write_limit_expr(&offset.this)?;
5107                    self.write_space();
5108                    self.write_keyword("ROWS");
5109                    // If there was a LIMIT, use FETCH NEXT ... ROWS ONLY
5110                    if let Some(limit) = &select.limit {
5111                        self.write_space();
5112                        self.write_keyword("FETCH NEXT");
5113                        self.write_space();
5114                        self.write_limit_expr(&limit.this)?;
5115                        self.write_space();
5116                        self.write_keyword("ROWS ONLY");
5117                    }
5118                } else {
5119                    self.write_keyword("OFFSET");
5120                    self.write_space();
5121                    self.write_limit_expr(&offset.this)?;
5122                    // Output ROWS keyword if it was in the original SQL
5123                    if offset.rows == Some(true) {
5124                        self.write_space();
5125                        self.write_keyword("ROWS");
5126                    }
5127                }
5128            }
5129        }
5130
5131        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
5132        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5133            if let Some(limit_by) = &select.limit_by {
5134                if !limit_by.is_empty() {
5135                    self.write_space();
5136                    self.write_keyword("BY");
5137                    self.write_space();
5138                    for (i, expr) in limit_by.iter().enumerate() {
5139                        if i > 0 {
5140                            self.write(", ");
5141                        }
5142                        self.generate_expression(expr)?;
5143                    }
5144                }
5145            }
5146        }
5147
5148        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
5149        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5150            if let Some(settings) = &select.settings {
5151                if self.config.pretty {
5152                    self.write_newline();
5153                    self.write_indent();
5154                } else {
5155                    self.write_space();
5156                }
5157                self.write_keyword("SETTINGS");
5158                self.write_space();
5159                for (i, expr) in settings.iter().enumerate() {
5160                    if i > 0 {
5161                        self.write(", ");
5162                    }
5163                    self.generate_expression(expr)?;
5164                }
5165            }
5166
5167            if let Some(format_expr) = &select.format {
5168                if self.config.pretty {
5169                    self.write_newline();
5170                    self.write_indent();
5171                } else {
5172                    self.write_space();
5173                }
5174                self.write_keyword("FORMAT");
5175                self.write_space();
5176                self.generate_expression(format_expr)?;
5177            }
5178        }
5179
5180        // FETCH FIRST/NEXT
5181        if let Some(fetch) = &select.fetch {
5182            // Check if we already emitted LIMIT from FETCH before OFFSET
5183            let fetch_already_as_limit = select.offset.is_some()
5184                && !fetch.percent
5185                && !fetch.with_ties
5186                && fetch.count.is_some()
5187                && matches!(
5188                    self.config.dialect,
5189                    Some(DialectType::Spark)
5190                        | Some(DialectType::Hive)
5191                        | Some(DialectType::DuckDB)
5192                        | Some(DialectType::SQLite)
5193                        | Some(DialectType::MySQL)
5194                        | Some(DialectType::BigQuery)
5195                        | Some(DialectType::Databricks)
5196                        | Some(DialectType::StarRocks)
5197                        | Some(DialectType::Doris)
5198                        | Some(DialectType::Athena)
5199                        | Some(DialectType::ClickHouse)
5200                        | Some(DialectType::Redshift)
5201                );
5202
5203            if fetch_already_as_limit {
5204                // Already emitted as LIMIT before OFFSET, skip
5205            } else {
5206                if self.config.pretty {
5207                    self.write_newline();
5208                    self.write_indent();
5209                } else {
5210                    self.write_space();
5211                }
5212
5213                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
5214                let use_limit = !fetch.percent
5215                    && !fetch.with_ties
5216                    && fetch.count.is_some()
5217                    && matches!(
5218                        self.config.dialect,
5219                        Some(DialectType::Spark)
5220                            | Some(DialectType::Hive)
5221                            | Some(DialectType::DuckDB)
5222                            | Some(DialectType::SQLite)
5223                            | Some(DialectType::MySQL)
5224                            | Some(DialectType::BigQuery)
5225                            | Some(DialectType::Databricks)
5226                            | Some(DialectType::StarRocks)
5227                            | Some(DialectType::Doris)
5228                            | Some(DialectType::Athena)
5229                            | Some(DialectType::ClickHouse)
5230                            | Some(DialectType::Redshift)
5231                    );
5232
5233                if use_limit {
5234                    self.write_keyword("LIMIT");
5235                    self.write_space();
5236                    self.generate_expression(fetch.count.as_ref().unwrap())?;
5237                } else {
5238                    self.write_keyword("FETCH");
5239                    self.write_space();
5240                    self.write_keyword(&fetch.direction);
5241                    if let Some(ref count) = fetch.count {
5242                        self.write_space();
5243                        self.generate_expression(count)?;
5244                    }
5245                    if fetch.percent {
5246                        self.write_space();
5247                        self.write_keyword("PERCENT");
5248                    }
5249                    if fetch.rows {
5250                        self.write_space();
5251                        self.write_keyword("ROWS");
5252                    }
5253                    if fetch.with_ties {
5254                        self.write_space();
5255                        self.write_keyword("WITH TIES");
5256                    } else {
5257                        self.write_space();
5258                        self.write_keyword("ONLY");
5259                    }
5260                }
5261            } // close fetch_already_as_limit else
5262        }
5263
5264        // SAMPLE / TABLESAMPLE
5265        if let Some(sample) = &select.sample {
5266            use crate::dialects::DialectType;
5267            if self.config.pretty {
5268                self.write_newline();
5269            } else {
5270                self.write_space();
5271            }
5272
5273            if sample.is_using_sample {
5274                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
5275                self.write_keyword("USING SAMPLE");
5276                self.generate_sample_body(sample)?;
5277            } else {
5278                self.write_keyword("TABLESAMPLE");
5279
5280                // Snowflake defaults to BERNOULLI when no explicit method is given
5281                let snowflake_bernoulli =
5282                    matches!(self.config.dialect, Some(DialectType::Snowflake))
5283                        && !sample.explicit_method;
5284                if snowflake_bernoulli {
5285                    self.write_space();
5286                    self.write_keyword("BERNOULLI");
5287                }
5288
5289                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
5290                if matches!(sample.method, SampleMethod::Bucket) {
5291                    self.write_space();
5292                    self.write("(");
5293                    self.write_keyword("BUCKET");
5294                    self.write_space();
5295                    if let Some(ref num) = sample.bucket_numerator {
5296                        self.generate_expression(num)?;
5297                    }
5298                    self.write_space();
5299                    self.write_keyword("OUT OF");
5300                    self.write_space();
5301                    if let Some(ref denom) = sample.bucket_denominator {
5302                        self.generate_expression(denom)?;
5303                    }
5304                    if let Some(ref field) = sample.bucket_field {
5305                        self.write_space();
5306                        self.write_keyword("ON");
5307                        self.write_space();
5308                        self.generate_expression(field)?;
5309                    }
5310                    self.write(")");
5311                } else if sample.unit_after_size {
5312                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
5313                    if sample.explicit_method && sample.method_before_size {
5314                        self.write_space();
5315                        match sample.method {
5316                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5317                            SampleMethod::System => self.write_keyword("SYSTEM"),
5318                            SampleMethod::Block => self.write_keyword("BLOCK"),
5319                            SampleMethod::Row => self.write_keyword("ROW"),
5320                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5321                            _ => {}
5322                        }
5323                    }
5324                    self.write(" (");
5325                    self.generate_expression(&sample.size)?;
5326                    self.write_space();
5327                    match sample.method {
5328                        SampleMethod::Percent => self.write_keyword("PERCENT"),
5329                        SampleMethod::Row => self.write_keyword("ROWS"),
5330                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
5331                        _ => {
5332                            self.write_keyword("PERCENT");
5333                        }
5334                    }
5335                    self.write(")");
5336                } else {
5337                    // Syntax: TABLESAMPLE METHOD (size)
5338                    self.write_space();
5339                    match sample.method {
5340                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5341                        SampleMethod::System => self.write_keyword("SYSTEM"),
5342                        SampleMethod::Block => self.write_keyword("BLOCK"),
5343                        SampleMethod::Row => self.write_keyword("ROW"),
5344                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
5345                        SampleMethod::Bucket => {}
5346                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5347                    }
5348                    self.write(" (");
5349                    self.generate_expression(&sample.size)?;
5350                    if matches!(sample.method, SampleMethod::Percent) {
5351                        self.write_space();
5352                        self.write_keyword("PERCENT");
5353                    }
5354                    self.write(")");
5355                }
5356            }
5357
5358            if let Some(seed) = &sample.seed {
5359                self.write_space();
5360                // Databricks/Spark use REPEATABLE, not SEED
5361                let use_seed = sample.use_seed_keyword
5362                    && !matches!(
5363                        self.config.dialect,
5364                        Some(crate::dialects::DialectType::Databricks)
5365                            | Some(crate::dialects::DialectType::Spark)
5366                    );
5367                if use_seed {
5368                    self.write_keyword("SEED");
5369                } else {
5370                    self.write_keyword("REPEATABLE");
5371                }
5372                self.write(" (");
5373                self.generate_expression(seed)?;
5374                self.write(")");
5375            }
5376        }
5377
5378        // FOR UPDATE/SHARE locks
5379        // Skip locking clauses for dialects that don't support them
5380        if self.config.locking_reads_supported {
5381            for lock in &select.locks {
5382                if self.config.pretty {
5383                    self.write_newline();
5384                    self.write_indent();
5385                } else {
5386                    self.write_space();
5387                }
5388                self.generate_lock(lock)?;
5389            }
5390        }
5391
5392        // FOR XML clause (T-SQL)
5393        if !select.for_xml.is_empty() {
5394            if self.config.pretty {
5395                self.write_newline();
5396                self.write_indent();
5397            } else {
5398                self.write_space();
5399            }
5400            self.write_keyword("FOR XML");
5401            for (i, opt) in select.for_xml.iter().enumerate() {
5402                if self.config.pretty {
5403                    if i > 0 {
5404                        self.write(",");
5405                    }
5406                    self.write_newline();
5407                    self.write_indent();
5408                    self.write("  "); // extra indent for options
5409                } else {
5410                    if i > 0 {
5411                        self.write(",");
5412                    }
5413                    self.write_space();
5414                }
5415                self.generate_for_xml_option(opt)?;
5416            }
5417        }
5418
5419        // FOR JSON clause (T-SQL)
5420        if !select.for_json.is_empty()
5421            && matches!(
5422                self.config.dialect,
5423                None | Some(DialectType::TSQL) | Some(DialectType::Fabric)
5424            )
5425        {
5426            if self.config.pretty {
5427                self.write_newline();
5428                self.write_indent();
5429            } else {
5430                self.write_space();
5431            }
5432            self.write_keyword("FOR JSON");
5433            for (i, opt) in select.for_json.iter().enumerate() {
5434                if self.config.pretty {
5435                    if i > 0 {
5436                        self.write(",");
5437                    }
5438                    self.write_newline();
5439                    self.write_indent();
5440                    self.write("  "); // extra indent for options
5441                } else {
5442                    if i > 0 {
5443                        self.write(",");
5444                    }
5445                    self.write_space();
5446                }
5447                self.generate_for_xml_option(opt)?;
5448            }
5449        }
5450
5451        // TSQL: OPTION clause
5452        if let Some(ref option) = select.option {
5453            if matches!(
5454                self.config.dialect,
5455                Some(crate::dialects::DialectType::TSQL)
5456                    | Some(crate::dialects::DialectType::Fabric)
5457            ) {
5458                self.write_space();
5459                self.write(option);
5460            }
5461        }
5462
5463        Ok(())
5464    }
5465
5466    /// Generate a single FOR XML option
5467    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
5468        match opt {
5469            Expression::QueryOption(qo) => {
5470                // Extract the option name from Var
5471                if let Expression::Var(var) = &*qo.this {
5472                    self.write(&var.this);
5473                } else {
5474                    self.generate_expression(&qo.this)?;
5475                }
5476                // If there's an expression (like PATH('element')), output it in parens
5477                if let Some(expr) = &qo.expression {
5478                    self.write("(");
5479                    self.generate_expression(expr)?;
5480                    self.write(")");
5481                }
5482            }
5483            _ => {
5484                self.generate_expression(opt)?;
5485            }
5486        }
5487        Ok(())
5488    }
5489
5490    fn generate_with(&mut self, with: &With) -> Result<()> {
5491        use crate::dialects::DialectType;
5492
5493        // Output leading comments before WITH
5494        for comment in &with.leading_comments {
5495            self.write_formatted_comment(comment);
5496            self.write(" ");
5497        }
5498        self.write_keyword("WITH");
5499        if with.recursive && self.config.cte_recursive_keyword_required {
5500            self.write_space();
5501            self.write_keyword("RECURSIVE");
5502        }
5503        self.write_space();
5504
5505        // BigQuery doesn't support column aliases in CTE definitions
5506        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
5507
5508        for (i, cte) in with.ctes.iter().enumerate() {
5509            if i > 0 {
5510                self.write(",");
5511                if self.config.pretty {
5512                    self.write_space();
5513                } else {
5514                    self.write(" ");
5515                }
5516            }
5517            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
5518                self.generate_expression(&cte.this)?;
5519                self.write_space();
5520                self.write_keyword("AS");
5521                self.write_space();
5522                self.generate_identifier(&cte.alias)?;
5523                continue;
5524            }
5525            self.generate_identifier(&cte.alias)?;
5526            // Output CTE comments after alias name, before AS
5527            for comment in &cte.comments {
5528                self.write_space();
5529                self.write_formatted_comment(comment);
5530            }
5531            if !cte.columns.is_empty() && !skip_cte_columns {
5532                self.write("(");
5533                for (j, col) in cte.columns.iter().enumerate() {
5534                    if j > 0 {
5535                        self.write(", ");
5536                    }
5537                    self.generate_identifier(col)?;
5538                }
5539                self.write(")");
5540            }
5541            // USING KEY (columns) for DuckDB recursive CTEs
5542            if !cte.key_expressions.is_empty() {
5543                self.write_space();
5544                self.write_keyword("USING KEY");
5545                self.write(" (");
5546                for (i, key) in cte.key_expressions.iter().enumerate() {
5547                    if i > 0 {
5548                        self.write(", ");
5549                    }
5550                    self.generate_identifier(key)?;
5551                }
5552                self.write(")");
5553            }
5554            self.write_space();
5555            self.write_keyword("AS");
5556            // MATERIALIZED / NOT MATERIALIZED
5557            if let Some(materialized) = cte.materialized {
5558                self.write_space();
5559                if materialized {
5560                    self.write_keyword("MATERIALIZED");
5561                } else {
5562                    self.write_keyword("NOT MATERIALIZED");
5563                }
5564            }
5565            self.write(" (");
5566            if self.config.pretty {
5567                self.write_newline();
5568                self.indent_level += 1;
5569                self.write_indent();
5570            }
5571            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
5572            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
5573            let wrap_values_in_select = matches!(
5574                self.config.dialect,
5575                Some(DialectType::Spark) | Some(DialectType::Databricks)
5576            ) && matches!(&cte.this, Expression::Values(_));
5577
5578            if wrap_values_in_select {
5579                self.write_keyword("SELECT");
5580                self.write(" * ");
5581                self.write_keyword("FROM");
5582                self.write_space();
5583            }
5584            self.generate_expression(&cte.this)?;
5585            if self.config.pretty {
5586                self.write_newline();
5587                self.indent_level -= 1;
5588                self.write_indent();
5589            }
5590            self.write(")");
5591        }
5592
5593        // Generate SEARCH/CYCLE clause if present
5594        if let Some(search) = &with.search {
5595            self.write_space();
5596            self.generate_expression(search)?;
5597        }
5598
5599        Ok(())
5600    }
5601
5602    /// Generate joins with proper nesting structure for pretty printing.
5603    /// Deferred-condition joins "own" the non-deferred joins that follow them
5604    /// within the same nesting_group.
5605    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
5606        let mut i = 0;
5607        while i < joins.len() {
5608            if joins[i].deferred_condition {
5609                let parent_group = joins[i].nesting_group;
5610
5611                // This join owns the following non-deferred joins in the same nesting_group
5612                // First output the join keyword and table (without condition)
5613                self.generate_join_without_condition(&joins[i])?;
5614
5615                // Find the range of child joins: same nesting_group and not deferred
5616                let child_start = i + 1;
5617                let mut child_end = child_start;
5618                while child_end < joins.len()
5619                    && !joins[child_end].deferred_condition
5620                    && joins[child_end].nesting_group == parent_group
5621                {
5622                    child_end += 1;
5623                }
5624
5625                // Output child joins with extra indentation
5626                if child_start < child_end {
5627                    self.indent_level += 1;
5628                    for j in child_start..child_end {
5629                        self.generate_join(&joins[j])?;
5630                    }
5631                    self.indent_level -= 1;
5632                }
5633
5634                // Output the deferred condition at the parent level
5635                self.generate_join_condition(&joins[i])?;
5636
5637                i = child_end;
5638            } else {
5639                // Regular join (no nesting)
5640                self.generate_join(&joins[i])?;
5641                i += 1;
5642            }
5643        }
5644        Ok(())
5645    }
5646
5647    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5648    /// Used for deferred-condition joins where the condition is output after child joins.
5649    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5650        // Save and temporarily clear the condition to prevent generate_join from outputting it
5651        // We achieve this by creating a modified copy
5652        let mut join_copy = join.clone();
5653        join_copy.on = None;
5654        join_copy.using = Vec::new();
5655        join_copy.deferred_condition = false;
5656        self.generate_join(&join_copy)
5657    }
5658
5659    fn generate_join(&mut self, join: &Join) -> Result<()> {
5660        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5661        if join.kind == JoinKind::Implicit {
5662            self.write(",");
5663            if self.config.pretty {
5664                self.write_newline();
5665                self.write_indent();
5666            } else {
5667                self.write_space();
5668            }
5669            self.generate_expression(&join.this)?;
5670            return Ok(());
5671        }
5672
5673        if self.config.pretty {
5674            self.write_newline();
5675            self.write_indent();
5676        } else {
5677            self.write_space();
5678        }
5679
5680        // Helper: format hint suffix (e.g., " LOOP" or "")
5681        // Only include join hints for dialects that support them
5682        let hint_str = if self.config.join_hints {
5683            join.join_hint
5684                .as_ref()
5685                .map(|h| format!(" {}", h))
5686                .unwrap_or_default()
5687        } else {
5688            String::new()
5689        };
5690
5691        let clickhouse_join_keyword =
5692            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5693                if let Some(hint) = &join.join_hint {
5694                    let mut global = false;
5695                    let mut strictness: Option<&'static str> = None;
5696                    for part in hint.split_whitespace() {
5697                        if part.eq_ignore_ascii_case("GLOBAL") {
5698                            global = true;
5699                        } else if part.eq_ignore_ascii_case("ALL") {
5700                            strictness = Some("ALL");
5701                        } else if part.eq_ignore_ascii_case("ANY") {
5702                            strictness = Some("ANY");
5703                        } else if part.eq_ignore_ascii_case("ASOF") {
5704                            strictness = Some("ASOF");
5705                        } else if part.eq_ignore_ascii_case("SEMI") {
5706                            strictness = Some("SEMI");
5707                        } else if part.eq_ignore_ascii_case("ANTI") {
5708                            strictness = Some("ANTI");
5709                        }
5710                    }
5711
5712                    if global || strictness.is_some() {
5713                        let join_type = match join.kind {
5714                            JoinKind::Left => {
5715                                if join.use_outer_keyword {
5716                                    "LEFT OUTER"
5717                                } else if join.use_inner_keyword {
5718                                    "LEFT INNER"
5719                                } else {
5720                                    "LEFT"
5721                                }
5722                            }
5723                            JoinKind::Right => {
5724                                if join.use_outer_keyword {
5725                                    "RIGHT OUTER"
5726                                } else if join.use_inner_keyword {
5727                                    "RIGHT INNER"
5728                                } else {
5729                                    "RIGHT"
5730                                }
5731                            }
5732                            JoinKind::Full => {
5733                                if join.use_outer_keyword {
5734                                    "FULL OUTER"
5735                                } else {
5736                                    "FULL"
5737                                }
5738                            }
5739                            JoinKind::Inner => {
5740                                if join.use_inner_keyword {
5741                                    "INNER"
5742                                } else {
5743                                    ""
5744                                }
5745                            }
5746                            _ => "",
5747                        };
5748
5749                        let mut parts = Vec::new();
5750                        if global {
5751                            parts.push("GLOBAL");
5752                        }
5753                        if !join_type.is_empty() {
5754                            parts.push(join_type);
5755                        }
5756                        if let Some(strict) = strictness {
5757                            parts.push(strict);
5758                        }
5759                        parts.push("JOIN");
5760                        Some(parts.join(" "))
5761                    } else {
5762                        None
5763                    }
5764                } else {
5765                    None
5766                }
5767            } else {
5768                None
5769            };
5770
5771        // Output any comments associated with this join
5772        // In pretty mode, comments go on their own line before the join keyword
5773        // In non-pretty mode, comments go inline before the join keyword
5774        if !join.comments.is_empty() {
5775            if self.config.pretty {
5776                // In pretty mode, go back before the newline+indent we just wrote
5777                // and output comments on their own lines
5778                // We need to output comments BEFORE the join keyword on separate lines
5779                // Trim the trailing newline+indent we already wrote
5780                let trimmed = self.output.trim_end().len();
5781                self.output.truncate(trimmed);
5782                for comment in &join.comments {
5783                    self.write_newline();
5784                    self.write_indent();
5785                    self.write_formatted_comment(comment);
5786                }
5787                self.write_newline();
5788                self.write_indent();
5789            } else {
5790                for comment in &join.comments {
5791                    self.write_formatted_comment(comment);
5792                    self.write_space();
5793                }
5794            }
5795        }
5796
5797        let directed_str = if join.directed { " DIRECTED" } else { "" };
5798
5799        if let Some(keyword) = clickhouse_join_keyword {
5800            self.write_keyword(&keyword);
5801        } else {
5802            match join.kind {
5803                JoinKind::Inner => {
5804                    if join.use_inner_keyword {
5805                        if hint_str.is_empty() && directed_str.is_empty() {
5806                            self.write_keyword("INNER JOIN");
5807                        } else {
5808                            self.write_keyword("INNER");
5809                            if !hint_str.is_empty() {
5810                                self.write_keyword(&hint_str);
5811                            }
5812                            if !directed_str.is_empty() {
5813                                self.write_keyword(directed_str);
5814                            }
5815                            self.write_keyword(" JOIN");
5816                        }
5817                    } else {
5818                        if !hint_str.is_empty() {
5819                            self.write_keyword(hint_str.trim());
5820                            self.write_keyword(" ");
5821                        }
5822                        if !directed_str.is_empty() {
5823                            self.write_keyword("DIRECTED ");
5824                        }
5825                        self.write_keyword("JOIN");
5826                    }
5827                }
5828                JoinKind::Left => {
5829                    if join.use_outer_keyword {
5830                        if hint_str.is_empty() && directed_str.is_empty() {
5831                            self.write_keyword("LEFT OUTER JOIN");
5832                        } else {
5833                            self.write_keyword("LEFT OUTER");
5834                            if !hint_str.is_empty() {
5835                                self.write_keyword(&hint_str);
5836                            }
5837                            if !directed_str.is_empty() {
5838                                self.write_keyword(directed_str);
5839                            }
5840                            self.write_keyword(" JOIN");
5841                        }
5842                    } else if join.use_inner_keyword {
5843                        if hint_str.is_empty() && directed_str.is_empty() {
5844                            self.write_keyword("LEFT INNER JOIN");
5845                        } else {
5846                            self.write_keyword("LEFT INNER");
5847                            if !hint_str.is_empty() {
5848                                self.write_keyword(&hint_str);
5849                            }
5850                            if !directed_str.is_empty() {
5851                                self.write_keyword(directed_str);
5852                            }
5853                            self.write_keyword(" JOIN");
5854                        }
5855                    } else {
5856                        if hint_str.is_empty() && directed_str.is_empty() {
5857                            self.write_keyword("LEFT JOIN");
5858                        } else {
5859                            self.write_keyword("LEFT");
5860                            if !hint_str.is_empty() {
5861                                self.write_keyword(&hint_str);
5862                            }
5863                            if !directed_str.is_empty() {
5864                                self.write_keyword(directed_str);
5865                            }
5866                            self.write_keyword(" JOIN");
5867                        }
5868                    }
5869                }
5870                JoinKind::Right => {
5871                    if join.use_outer_keyword {
5872                        if hint_str.is_empty() && directed_str.is_empty() {
5873                            self.write_keyword("RIGHT OUTER JOIN");
5874                        } else {
5875                            self.write_keyword("RIGHT OUTER");
5876                            if !hint_str.is_empty() {
5877                                self.write_keyword(&hint_str);
5878                            }
5879                            if !directed_str.is_empty() {
5880                                self.write_keyword(directed_str);
5881                            }
5882                            self.write_keyword(" JOIN");
5883                        }
5884                    } else if join.use_inner_keyword {
5885                        if hint_str.is_empty() && directed_str.is_empty() {
5886                            self.write_keyword("RIGHT INNER JOIN");
5887                        } else {
5888                            self.write_keyword("RIGHT INNER");
5889                            if !hint_str.is_empty() {
5890                                self.write_keyword(&hint_str);
5891                            }
5892                            if !directed_str.is_empty() {
5893                                self.write_keyword(directed_str);
5894                            }
5895                            self.write_keyword(" JOIN");
5896                        }
5897                    } else {
5898                        if hint_str.is_empty() && directed_str.is_empty() {
5899                            self.write_keyword("RIGHT JOIN");
5900                        } else {
5901                            self.write_keyword("RIGHT");
5902                            if !hint_str.is_empty() {
5903                                self.write_keyword(&hint_str);
5904                            }
5905                            if !directed_str.is_empty() {
5906                                self.write_keyword(directed_str);
5907                            }
5908                            self.write_keyword(" JOIN");
5909                        }
5910                    }
5911                }
5912                JoinKind::Full => {
5913                    if join.use_outer_keyword {
5914                        if hint_str.is_empty() && directed_str.is_empty() {
5915                            self.write_keyword("FULL OUTER JOIN");
5916                        } else {
5917                            self.write_keyword("FULL OUTER");
5918                            if !hint_str.is_empty() {
5919                                self.write_keyword(&hint_str);
5920                            }
5921                            if !directed_str.is_empty() {
5922                                self.write_keyword(directed_str);
5923                            }
5924                            self.write_keyword(" JOIN");
5925                        }
5926                    } else {
5927                        if hint_str.is_empty() && directed_str.is_empty() {
5928                            self.write_keyword("FULL JOIN");
5929                        } else {
5930                            self.write_keyword("FULL");
5931                            if !hint_str.is_empty() {
5932                                self.write_keyword(&hint_str);
5933                            }
5934                            if !directed_str.is_empty() {
5935                                self.write_keyword(directed_str);
5936                            }
5937                            self.write_keyword(" JOIN");
5938                        }
5939                    }
5940                }
5941                JoinKind::Outer => {
5942                    if directed_str.is_empty() {
5943                        self.write_keyword("OUTER JOIN");
5944                    } else {
5945                        self.write_keyword("OUTER");
5946                        self.write_keyword(directed_str);
5947                        self.write_keyword(" JOIN");
5948                    }
5949                }
5950                JoinKind::Cross => {
5951                    if directed_str.is_empty() {
5952                        self.write_keyword("CROSS JOIN");
5953                    } else {
5954                        self.write_keyword("CROSS");
5955                        self.write_keyword(directed_str);
5956                        self.write_keyword(" JOIN");
5957                    }
5958                }
5959                JoinKind::Natural => {
5960                    if join.use_inner_keyword {
5961                        if directed_str.is_empty() {
5962                            self.write_keyword("NATURAL INNER JOIN");
5963                        } else {
5964                            self.write_keyword("NATURAL INNER");
5965                            self.write_keyword(directed_str);
5966                            self.write_keyword(" JOIN");
5967                        }
5968                    } else {
5969                        if directed_str.is_empty() {
5970                            self.write_keyword("NATURAL JOIN");
5971                        } else {
5972                            self.write_keyword("NATURAL");
5973                            self.write_keyword(directed_str);
5974                            self.write_keyword(" JOIN");
5975                        }
5976                    }
5977                }
5978                JoinKind::NaturalLeft => {
5979                    if join.use_outer_keyword {
5980                        if directed_str.is_empty() {
5981                            self.write_keyword("NATURAL LEFT OUTER JOIN");
5982                        } else {
5983                            self.write_keyword("NATURAL LEFT OUTER");
5984                            self.write_keyword(directed_str);
5985                            self.write_keyword(" JOIN");
5986                        }
5987                    } else {
5988                        if directed_str.is_empty() {
5989                            self.write_keyword("NATURAL LEFT JOIN");
5990                        } else {
5991                            self.write_keyword("NATURAL LEFT");
5992                            self.write_keyword(directed_str);
5993                            self.write_keyword(" JOIN");
5994                        }
5995                    }
5996                }
5997                JoinKind::NaturalRight => {
5998                    if join.use_outer_keyword {
5999                        if directed_str.is_empty() {
6000                            self.write_keyword("NATURAL RIGHT OUTER JOIN");
6001                        } else {
6002                            self.write_keyword("NATURAL RIGHT OUTER");
6003                            self.write_keyword(directed_str);
6004                            self.write_keyword(" JOIN");
6005                        }
6006                    } else {
6007                        if directed_str.is_empty() {
6008                            self.write_keyword("NATURAL RIGHT JOIN");
6009                        } else {
6010                            self.write_keyword("NATURAL RIGHT");
6011                            self.write_keyword(directed_str);
6012                            self.write_keyword(" JOIN");
6013                        }
6014                    }
6015                }
6016                JoinKind::NaturalFull => {
6017                    if join.use_outer_keyword {
6018                        if directed_str.is_empty() {
6019                            self.write_keyword("NATURAL FULL OUTER JOIN");
6020                        } else {
6021                            self.write_keyword("NATURAL FULL OUTER");
6022                            self.write_keyword(directed_str);
6023                            self.write_keyword(" JOIN");
6024                        }
6025                    } else {
6026                        if directed_str.is_empty() {
6027                            self.write_keyword("NATURAL FULL JOIN");
6028                        } else {
6029                            self.write_keyword("NATURAL FULL");
6030                            self.write_keyword(directed_str);
6031                            self.write_keyword(" JOIN");
6032                        }
6033                    }
6034                }
6035                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
6036                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
6037                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
6038                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
6039                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
6040                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
6041                JoinKind::CrossApply => {
6042                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL-like dialects
6043                    if matches!(
6044                        self.config.dialect,
6045                        Some(DialectType::TSQL) | Some(DialectType::Fabric) | None
6046                    ) {
6047                        self.write_keyword("CROSS APPLY");
6048                    } else {
6049                        self.write_keyword("INNER JOIN LATERAL");
6050                    }
6051                }
6052                JoinKind::OuterApply => {
6053                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL-like dialects
6054                    if matches!(
6055                        self.config.dialect,
6056                        Some(DialectType::TSQL) | Some(DialectType::Fabric) | None
6057                    ) {
6058                        self.write_keyword("OUTER APPLY");
6059                    } else {
6060                        self.write_keyword("LEFT JOIN LATERAL");
6061                    }
6062                }
6063                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
6064                JoinKind::AsOfLeft => {
6065                    if join.use_outer_keyword {
6066                        self.write_keyword("ASOF LEFT OUTER JOIN");
6067                    } else {
6068                        self.write_keyword("ASOF LEFT JOIN");
6069                    }
6070                }
6071                JoinKind::AsOfRight => {
6072                    if join.use_outer_keyword {
6073                        self.write_keyword("ASOF RIGHT OUTER JOIN");
6074                    } else {
6075                        self.write_keyword("ASOF RIGHT JOIN");
6076                    }
6077                }
6078                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
6079                JoinKind::LeftLateral => {
6080                    if join.use_outer_keyword {
6081                        self.write_keyword("LEFT OUTER LATERAL JOIN");
6082                    } else {
6083                        self.write_keyword("LEFT LATERAL JOIN");
6084                    }
6085                }
6086                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
6087                JoinKind::Implicit => {
6088                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
6089                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
6090                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
6091                    use crate::dialects::DialectType;
6092                    let is_cj_dialect = matches!(
6093                        self.config.dialect,
6094                        Some(DialectType::BigQuery)
6095                            | Some(DialectType::Hive)
6096                            | Some(DialectType::Spark)
6097                            | Some(DialectType::Databricks)
6098                    );
6099                    let source_is_same = self.config.source_dialect.is_some()
6100                        && self.config.source_dialect == self.config.dialect;
6101                    let source_is_cj = matches!(
6102                        self.config.source_dialect,
6103                        Some(DialectType::BigQuery)
6104                            | Some(DialectType::Hive)
6105                            | Some(DialectType::Spark)
6106                            | Some(DialectType::Databricks)
6107                    );
6108                    if is_cj_dialect
6109                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
6110                    {
6111                        self.write_keyword("CROSS JOIN");
6112                    } else {
6113                        // Implicit join uses comma: FROM a, b
6114                        // We already wrote a space before the match, so replace with comma
6115                        // by removing trailing space and writing ", "
6116                        self.output.truncate(self.output.trim_end().len());
6117                        self.write(",");
6118                    }
6119                }
6120                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
6121                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
6122                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
6123                JoinKind::Positional => self.write_keyword("POSITIONAL JOIN"),
6124            }
6125        }
6126
6127        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
6128        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
6129            match &join.this {
6130                Expression::Tuple(t) if t.expressions.is_empty() => {}
6131                Expression::Tuple(t) => {
6132                    self.write_space();
6133                    for (i, item) in t.expressions.iter().enumerate() {
6134                        if i > 0 {
6135                            self.write(", ");
6136                        }
6137                        self.generate_expression(item)?;
6138                    }
6139                }
6140                other => {
6141                    self.write_space();
6142                    self.generate_expression(other)?;
6143                }
6144            }
6145        } else {
6146            self.write_space();
6147            self.generate_expression(&join.this)?;
6148        }
6149
6150        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
6151        if !join.deferred_condition {
6152            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
6153            if let Some(match_cond) = &join.match_condition {
6154                self.write_space();
6155                self.write_keyword("MATCH_CONDITION");
6156                self.write(" (");
6157                self.generate_expression(match_cond)?;
6158                self.write(")");
6159            }
6160
6161            if let Some(on) = &join.on {
6162                if self.config.pretty {
6163                    self.write_newline();
6164                    self.indent_level += 1;
6165                    self.write_indent();
6166                    self.write_keyword("ON");
6167                    self.write_space();
6168                    self.generate_join_on_condition(on)?;
6169                    self.indent_level -= 1;
6170                } else {
6171                    self.write_space();
6172                    self.write_keyword("ON");
6173                    self.write_space();
6174                    self.generate_expression(on)?;
6175                }
6176            }
6177
6178            if !join.using.is_empty() {
6179                if self.config.pretty {
6180                    self.write_newline();
6181                    self.indent_level += 1;
6182                    self.write_indent();
6183                    self.write_keyword("USING");
6184                    self.write(" (");
6185                    for (i, col) in join.using.iter().enumerate() {
6186                        if i > 0 {
6187                            self.write(", ");
6188                        }
6189                        self.generate_identifier(col)?;
6190                    }
6191                    self.write(")");
6192                    self.indent_level -= 1;
6193                } else {
6194                    self.write_space();
6195                    self.write_keyword("USING");
6196                    self.write(" (");
6197                    for (i, col) in join.using.iter().enumerate() {
6198                        if i > 0 {
6199                            self.write(", ");
6200                        }
6201                        self.generate_identifier(col)?;
6202                    }
6203                    self.write(")");
6204                }
6205            }
6206        }
6207
6208        // Generate PIVOT/UNPIVOT expressions that follow this join
6209        for pivot in &join.pivots {
6210            self.write_space();
6211            self.generate_expression(pivot)?;
6212        }
6213
6214        Ok(())
6215    }
6216
6217    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
6218    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
6219        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
6220        if let Some(match_cond) = &join.match_condition {
6221            self.write_space();
6222            self.write_keyword("MATCH_CONDITION");
6223            self.write(" (");
6224            self.generate_expression(match_cond)?;
6225            self.write(")");
6226        }
6227
6228        if let Some(on) = &join.on {
6229            if self.config.pretty {
6230                self.write_newline();
6231                self.indent_level += 1;
6232                self.write_indent();
6233                self.write_keyword("ON");
6234                self.write_space();
6235                // In pretty mode, split AND conditions onto separate lines
6236                self.generate_join_on_condition(on)?;
6237                self.indent_level -= 1;
6238            } else {
6239                self.write_space();
6240                self.write_keyword("ON");
6241                self.write_space();
6242                self.generate_expression(on)?;
6243            }
6244        }
6245
6246        if !join.using.is_empty() {
6247            if self.config.pretty {
6248                self.write_newline();
6249                self.indent_level += 1;
6250                self.write_indent();
6251                self.write_keyword("USING");
6252                self.write(" (");
6253                for (i, col) in join.using.iter().enumerate() {
6254                    if i > 0 {
6255                        self.write(", ");
6256                    }
6257                    self.generate_identifier(col)?;
6258                }
6259                self.write(")");
6260                self.indent_level -= 1;
6261            } else {
6262                self.write_space();
6263                self.write_keyword("USING");
6264                self.write(" (");
6265                for (i, col) in join.using.iter().enumerate() {
6266                    if i > 0 {
6267                        self.write(", ");
6268                    }
6269                    self.generate_identifier(col)?;
6270                }
6271                self.write(")");
6272            }
6273        }
6274
6275        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
6276        for pivot in &join.pivots {
6277            self.write_space();
6278            self.generate_expression(pivot)?;
6279        }
6280
6281        Ok(())
6282    }
6283
6284    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
6285    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
6286        if let Expression::And(and_op) = expr {
6287            if let Some(conditions) = self.flatten_connector_terms(and_op, ConnectorOperator::And) {
6288                self.generate_expression(conditions[0])?;
6289                for condition in conditions.iter().skip(1) {
6290                    self.write_newline();
6291                    self.write_indent();
6292                    self.write_keyword("AND");
6293                    self.write_space();
6294                    self.generate_expression(condition)?;
6295                }
6296                return Ok(());
6297            }
6298        }
6299
6300        self.generate_expression(expr)
6301    }
6302
6303    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
6304        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
6305        self.write("(");
6306        self.generate_expression(&jt.left)?;
6307
6308        // Generate all joins
6309        for join in &jt.joins {
6310            self.generate_join(join)?;
6311        }
6312
6313        // Generate LATERAL VIEW clauses (Hive/Spark)
6314        for (lv_idx, lv) in jt.lateral_views.iter().enumerate() {
6315            self.generate_lateral_view(lv, lv_idx)?;
6316        }
6317
6318        self.write(")");
6319
6320        // Alias
6321        if let Some(alias) = &jt.alias {
6322            self.write_space();
6323            self.write_keyword("AS");
6324            self.write_space();
6325            self.generate_identifier(alias)?;
6326        }
6327
6328        Ok(())
6329    }
6330
6331    fn generate_lateral_view(&mut self, lv: &LateralView, lv_index: usize) -> Result<()> {
6332        use crate::dialects::DialectType;
6333
6334        if self.config.pretty {
6335            self.write_newline();
6336            self.write_indent();
6337        } else {
6338            self.write_space();
6339        }
6340
6341        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
6342        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
6343        let use_lateral_join = matches!(
6344            self.config.dialect,
6345            Some(DialectType::PostgreSQL)
6346                | Some(DialectType::DuckDB)
6347                | Some(DialectType::Snowflake)
6348                | Some(DialectType::TSQL)
6349                | Some(DialectType::Presto)
6350                | Some(DialectType::Trino)
6351                | Some(DialectType::Athena)
6352        );
6353
6354        // Check if target dialect should use UNNEST instead of EXPLODE
6355        let use_unnest = matches!(
6356            self.config.dialect,
6357            Some(DialectType::DuckDB)
6358                | Some(DialectType::Presto)
6359                | Some(DialectType::Trino)
6360                | Some(DialectType::Athena)
6361        );
6362
6363        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
6364        let (is_posexplode, is_inline, func_args) = match &lv.this {
6365            Expression::Explode(uf) => {
6366                // Expression::Explode is the dedicated EXPLODE expression type
6367                (false, false, vec![uf.this.clone()])
6368            }
6369            Expression::Unnest(uf) => {
6370                let mut args = vec![uf.this.clone()];
6371                args.extend(uf.expressions.clone());
6372                (false, false, args)
6373            }
6374            Expression::Function(func) => {
6375                if func.name.eq_ignore_ascii_case("POSEXPLODE")
6376                    || func.name.eq_ignore_ascii_case("POSEXPLODE_OUTER")
6377                {
6378                    (true, false, func.args.clone())
6379                } else if func.name.eq_ignore_ascii_case("INLINE") {
6380                    (false, true, func.args.clone())
6381                } else if func.name.eq_ignore_ascii_case("EXPLODE")
6382                    || func.name.eq_ignore_ascii_case("EXPLODE_OUTER")
6383                {
6384                    (false, false, func.args.clone())
6385                } else {
6386                    (false, false, vec![])
6387                }
6388            }
6389            _ => (false, false, vec![]),
6390        };
6391
6392        if use_lateral_join {
6393            // Convert to CROSS JOIN for PostgreSQL-like dialects
6394            if lv.outer {
6395                self.write_keyword("LEFT JOIN LATERAL");
6396            } else {
6397                self.write_keyword("CROSS JOIN");
6398            }
6399            self.write_space();
6400
6401            if use_unnest && !func_args.is_empty() {
6402                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
6403                // For DuckDB, also convert ARRAY(y) -> [y]
6404                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6405                    // DuckDB: ARRAY(y) -> [y]
6406                    func_args
6407                        .iter()
6408                        .map(|a| {
6409                            if let Expression::Function(ref f) = a {
6410                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() == 1 {
6411                                    return Expression::ArrayFunc(Box::new(
6412                                        crate::expressions::ArrayConstructor {
6413                                            expressions: f.args.clone(),
6414                                            bracket_notation: true,
6415                                            use_list_keyword: false,
6416                                        },
6417                                    ));
6418                                }
6419                            }
6420                            a.clone()
6421                        })
6422                        .collect::<Vec<_>>()
6423                } else if matches!(
6424                    self.config.dialect,
6425                    Some(DialectType::Presto)
6426                        | Some(DialectType::Trino)
6427                        | Some(DialectType::Athena)
6428                ) {
6429                    // Presto: ARRAY(y) -> ARRAY[y]
6430                    func_args
6431                        .iter()
6432                        .map(|a| {
6433                            if let Expression::Function(ref f) = a {
6434                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() >= 1 {
6435                                    return Expression::ArrayFunc(Box::new(
6436                                        crate::expressions::ArrayConstructor {
6437                                            expressions: f.args.clone(),
6438                                            bracket_notation: true,
6439                                            use_list_keyword: false,
6440                                        },
6441                                    ));
6442                                }
6443                            }
6444                            a.clone()
6445                        })
6446                        .collect::<Vec<_>>()
6447                } else {
6448                    func_args
6449                };
6450
6451                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
6452                if is_posexplode {
6453                    self.write_keyword("LATERAL");
6454                    self.write(" (");
6455                    self.write_keyword("SELECT");
6456                    self.write_space();
6457
6458                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
6459                    // column_aliases[0] is the position column, rest are data columns
6460                    let pos_alias = if !lv.column_aliases.is_empty() {
6461                        lv.column_aliases[0].clone()
6462                    } else {
6463                        Identifier::new("pos")
6464                    };
6465                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
6466                        lv.column_aliases[1..].to_vec()
6467                    } else {
6468                        vec![Identifier::new("col")]
6469                    };
6470
6471                    // pos - 1 AS pos
6472                    self.generate_identifier(&pos_alias)?;
6473                    self.write(" - 1");
6474                    self.write_space();
6475                    self.write_keyword("AS");
6476                    self.write_space();
6477                    self.generate_identifier(&pos_alias)?;
6478
6479                    // , col [, key, value ...]
6480                    for data_col in &data_aliases {
6481                        self.write(", ");
6482                        self.generate_identifier(data_col)?;
6483                    }
6484
6485                    self.write_space();
6486                    self.write_keyword("FROM");
6487                    self.write_space();
6488                    self.write_keyword("UNNEST");
6489                    self.write("(");
6490                    for (i, arg) in unnest_args.iter().enumerate() {
6491                        if i > 0 {
6492                            self.write(", ");
6493                        }
6494                        self.generate_expression(arg)?;
6495                    }
6496                    self.write(")");
6497                    self.write_space();
6498                    self.write_keyword("WITH ORDINALITY");
6499                    self.write_space();
6500                    self.write_keyword("AS");
6501                    self.write_space();
6502
6503                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
6504                    let table_alias_ident = lv
6505                        .table_alias
6506                        .clone()
6507                        .unwrap_or_else(|| Identifier::new("t"));
6508                    self.generate_identifier(&table_alias_ident)?;
6509                    self.write("(");
6510                    for (i, data_col) in data_aliases.iter().enumerate() {
6511                        if i > 0 {
6512                            self.write(", ");
6513                        }
6514                        self.generate_identifier(data_col)?;
6515                    }
6516                    self.write(", ");
6517                    self.generate_identifier(&pos_alias)?;
6518                    self.write("))");
6519                } else if is_inline && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6520                    // INLINE -> LATERAL (SELECT UNNEST(arg, max_depth => 2)) AS alias
6521                    self.write_keyword("LATERAL");
6522                    self.write(" (");
6523                    self.write_keyword("SELECT");
6524                    self.write_space();
6525                    self.write_keyword("UNNEST");
6526                    self.write("(");
6527                    for (i, arg) in unnest_args.iter().enumerate() {
6528                        if i > 0 {
6529                            self.write(", ");
6530                        }
6531                        self.generate_expression(arg)?;
6532                    }
6533                    self.write(", ");
6534                    self.write_keyword("max_depth");
6535                    self.write(" => 2))");
6536
6537                    // Add table and column aliases
6538                    if let Some(alias) = &lv.table_alias {
6539                        self.write_space();
6540                        self.write_keyword("AS");
6541                        self.write_space();
6542                        self.generate_identifier(alias)?;
6543                        if !lv.column_aliases.is_empty() {
6544                            self.write("(");
6545                            for (i, col) in lv.column_aliases.iter().enumerate() {
6546                                if i > 0 {
6547                                    self.write(", ");
6548                                }
6549                                self.generate_identifier(col)?;
6550                            }
6551                            self.write(")");
6552                        }
6553                    } else if !lv.column_aliases.is_empty() {
6554                        // Auto-generate alias like _u_N
6555                        self.write_space();
6556                        self.write_keyword("AS");
6557                        self.write_space();
6558                        self.write(&format!("_u_{}", lv_index));
6559                        self.write("(");
6560                        for (i, col) in lv.column_aliases.iter().enumerate() {
6561                            if i > 0 {
6562                                self.write(", ");
6563                            }
6564                            self.generate_identifier(col)?;
6565                        }
6566                        self.write(")");
6567                    }
6568                } else {
6569                    self.write_keyword("UNNEST");
6570                    self.write("(");
6571                    for (i, arg) in unnest_args.iter().enumerate() {
6572                        if i > 0 {
6573                            self.write(", ");
6574                        }
6575                        self.generate_expression(arg)?;
6576                    }
6577                    self.write(")");
6578
6579                    // Add table and column aliases for non-POSEXPLODE
6580                    if let Some(alias) = &lv.table_alias {
6581                        self.write_space();
6582                        self.write_keyword("AS");
6583                        self.write_space();
6584                        self.generate_identifier(alias)?;
6585                        if !lv.column_aliases.is_empty() {
6586                            self.write("(");
6587                            for (i, col) in lv.column_aliases.iter().enumerate() {
6588                                if i > 0 {
6589                                    self.write(", ");
6590                                }
6591                                self.generate_identifier(col)?;
6592                            }
6593                            self.write(")");
6594                        }
6595                    } else if !lv.column_aliases.is_empty() {
6596                        self.write_space();
6597                        self.write_keyword("AS");
6598                        self.write(" t(");
6599                        for (i, col) in lv.column_aliases.iter().enumerate() {
6600                            if i > 0 {
6601                                self.write(", ");
6602                            }
6603                            self.generate_identifier(col)?;
6604                        }
6605                        self.write(")");
6606                    }
6607                }
6608            } else {
6609                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
6610                if !lv.outer {
6611                    self.write_keyword("LATERAL");
6612                    self.write_space();
6613                }
6614                self.generate_expression(&lv.this)?;
6615
6616                // Add table and column aliases
6617                if let Some(alias) = &lv.table_alias {
6618                    self.write_space();
6619                    self.write_keyword("AS");
6620                    self.write_space();
6621                    self.generate_identifier(alias)?;
6622                    if !lv.column_aliases.is_empty() {
6623                        self.write("(");
6624                        for (i, col) in lv.column_aliases.iter().enumerate() {
6625                            if i > 0 {
6626                                self.write(", ");
6627                            }
6628                            self.generate_identifier(col)?;
6629                        }
6630                        self.write(")");
6631                    }
6632                } else if !lv.column_aliases.is_empty() {
6633                    self.write_space();
6634                    self.write_keyword("AS");
6635                    self.write(" t(");
6636                    for (i, col) in lv.column_aliases.iter().enumerate() {
6637                        if i > 0 {
6638                            self.write(", ");
6639                        }
6640                        self.generate_identifier(col)?;
6641                    }
6642                    self.write(")");
6643                }
6644            }
6645
6646            // For LEFT JOIN LATERAL, need ON TRUE
6647            if lv.outer {
6648                self.write_space();
6649                self.write_keyword("ON TRUE");
6650            }
6651        } else {
6652            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
6653            self.write_keyword("LATERAL VIEW");
6654            if lv.outer {
6655                self.write_space();
6656                self.write_keyword("OUTER");
6657            }
6658            if self.config.pretty {
6659                self.write_newline();
6660                self.write_indent();
6661            } else {
6662                self.write_space();
6663            }
6664            self.generate_expression(&lv.this)?;
6665
6666            // Table alias
6667            if let Some(alias) = &lv.table_alias {
6668                self.write_space();
6669                self.generate_identifier(alias)?;
6670            }
6671
6672            // Column aliases
6673            if !lv.column_aliases.is_empty() {
6674                self.write_space();
6675                self.write_keyword("AS");
6676                self.write_space();
6677                for (i, col) in lv.column_aliases.iter().enumerate() {
6678                    if i > 0 {
6679                        self.write(", ");
6680                    }
6681                    self.generate_identifier(col)?;
6682                }
6683            }
6684        }
6685
6686        Ok(())
6687    }
6688
6689    fn should_wrap_set_operation_modifiers(
6690        &self,
6691        order_by: &Option<OrderBy>,
6692        limit: &Option<Box<Expression>>,
6693        offset: &Option<Box<Expression>>,
6694    ) -> bool {
6695        let has_row_limit = limit.is_some() || offset.is_some();
6696        let has_emulated_null_ordering = order_by.as_ref().map_or(false, |order_by| {
6697            order_by
6698                .expressions
6699                .iter()
6700                .any(|ordered| ordered.nulls_first.is_some())
6701        });
6702
6703        (has_row_limit || has_emulated_null_ordering)
6704            && matches!(
6705                self.config.dialect,
6706                Some(DialectType::TSQL) | Some(DialectType::Fabric)
6707            )
6708    }
6709
6710    fn generate_tsql_wrapped_set_operation(
6711        &mut self,
6712        inner: Expression,
6713        with: Option<With>,
6714        order_by: Option<OrderBy>,
6715        limit: Option<Box<Expression>>,
6716        offset: Option<Box<Expression>>,
6717    ) -> Result<()> {
6718        let subquery = Subquery {
6719            this: inner,
6720            alias: Some(Identifier::new("_l_0".to_string())),
6721            column_aliases: Vec::new(),
6722            alias_explicit_as: true,
6723            alias_keyword: None,
6724            order_by: None,
6725            limit: None,
6726            offset: None,
6727            lateral: false,
6728            modifiers_inside: false,
6729            trailing_comments: Vec::new(),
6730            distribute_by: None,
6731            sort_by: None,
6732            cluster_by: None,
6733            inferred_type: None,
6734        };
6735
6736        let mut outer_select = Select {
6737            expressions: vec![Expression::Star(Star {
6738                table: None,
6739                except: None,
6740                replace: None,
6741                rename: None,
6742                trailing_comments: Vec::new(),
6743                span: None,
6744            })],
6745            from: Some(From {
6746                expressions: vec![Expression::Subquery(Box::new(subquery))],
6747            }),
6748            with,
6749            order_by,
6750            limit: limit.map(|limit| Limit {
6751                this: *limit,
6752                percent: false,
6753                comments: Vec::new(),
6754            }),
6755            offset: offset.map(|offset| Offset {
6756                this: *offset,
6757                rows: Some(true),
6758            }),
6759            ..Select::new()
6760        };
6761
6762        if outer_select.offset.is_some() && outer_select.order_by.is_none() {
6763            outer_select.order_by = Some(Self::dummy_tsql_order_by());
6764        }
6765
6766        self.generate_select(&outer_select)
6767    }
6768
6769    fn dummy_tsql_order_by() -> OrderBy {
6770        let null_select = Expression::Select(Box::new(Select {
6771            expressions: vec![Expression::Null(Null)],
6772            ..Select::new()
6773        }));
6774
6775        OrderBy {
6776            expressions: vec![Ordered {
6777                this: Expression::Subquery(Box::new(Subquery {
6778                    this: null_select,
6779                    alias: None,
6780                    column_aliases: Vec::new(),
6781                    alias_explicit_as: false,
6782                    alias_keyword: None,
6783                    order_by: None,
6784                    limit: None,
6785                    offset: None,
6786                    lateral: false,
6787                    modifiers_inside: false,
6788                    trailing_comments: Vec::new(),
6789                    distribute_by: None,
6790                    sort_by: None,
6791                    cluster_by: None,
6792                    inferred_type: None,
6793                })),
6794                desc: false,
6795                nulls_first: None,
6796                explicit_asc: false,
6797                with_fill: None,
6798            }],
6799            siblings: false,
6800            comments: Vec::new(),
6801        }
6802    }
6803
6804    fn generate_union(&mut self, outermost: &Union) -> Result<()> {
6805        if self.should_wrap_set_operation_modifiers(
6806            &outermost.order_by,
6807            &outermost.limit,
6808            &outermost.offset,
6809        ) {
6810            let mut inner = outermost.clone();
6811            let with = inner.with.take();
6812            let order_by = inner.order_by.take();
6813            let limit = inner.limit.take();
6814            let offset = inner.offset.take();
6815
6816            return self.generate_tsql_wrapped_set_operation(
6817                Expression::Union(Box::new(inner)),
6818                with,
6819                order_by,
6820                limit,
6821                offset,
6822            );
6823        }
6824
6825        // Collect the left-recursive chain of Union nodes iteratively.
6826        // This avoids stack overflow for deeply nested chains like
6827        // SELECT 1 UNION ALL SELECT 2 UNION ALL ... UNION ALL SELECT N
6828        // where the parser builds: Union(Union(Union(A, B), C), D)
6829        let mut chain: Vec<&Union> = vec![outermost];
6830        let mut leftmost: &Expression = &outermost.left;
6831        while let Expression::Union(inner) = leftmost {
6832            chain.push(inner);
6833            leftmost = &inner.left;
6834        }
6835        // chain[0] = outermost, chain[last] = innermost
6836        // leftmost = innermost.left (a non-Union expression, typically Select)
6837
6838        // WITH clause (only on outermost)
6839        if let Some(with) = &outermost.with {
6840            self.generate_with(with)?;
6841            self.write_space();
6842        }
6843
6844        // Generate the base (leftmost) expression
6845        self.generate_expression(leftmost)?;
6846
6847        // Generate each union step from innermost to outermost
6848        for union in chain.iter().rev() {
6849            self.generate_union_step(union)?;
6850        }
6851        Ok(())
6852    }
6853
6854    /// Generate a single UNION step: keyword, right expression, and trailing modifiers.
6855    fn generate_union_step(&mut self, union: &Union) -> Result<()> {
6856        if self.config.pretty {
6857            self.write_newline();
6858            self.write_indent();
6859        } else {
6860            self.write_space();
6861        }
6862
6863        // BigQuery set operation modifiers: [side] [kind] UNION
6864        if let Some(side) = &union.side {
6865            self.write_keyword(side);
6866            self.write_space();
6867        }
6868        if let Some(kind) = &union.kind {
6869            self.write_keyword(kind);
6870            self.write_space();
6871        }
6872
6873        self.write_keyword("UNION");
6874        if union.all {
6875            self.write_space();
6876            self.write_keyword("ALL");
6877        } else if union.distinct {
6878            self.write_space();
6879            self.write_keyword("DISTINCT");
6880        }
6881
6882        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6883        // DuckDB: BY NAME
6884        if union.corresponding || union.by_name {
6885            self.write_space();
6886            self.write_keyword("BY NAME");
6887        }
6888        if !union.on_columns.is_empty() {
6889            self.write_space();
6890            self.write_keyword("ON");
6891            self.write(" (");
6892            for (i, col) in union.on_columns.iter().enumerate() {
6893                if i > 0 {
6894                    self.write(", ");
6895                }
6896                self.generate_expression(col)?;
6897            }
6898            self.write(")");
6899        }
6900
6901        if self.config.pretty {
6902            self.write_newline();
6903            self.write_indent();
6904        } else {
6905            self.write_space();
6906        }
6907        self.generate_expression(&union.right)?;
6908        // ORDER BY, LIMIT, OFFSET for the set operation
6909        if let Some(order_by) = &union.order_by {
6910            if self.config.pretty {
6911                self.write_newline();
6912            } else {
6913                self.write_space();
6914            }
6915            self.write_keyword("ORDER BY");
6916            self.write_space();
6917            for (i, ordered) in order_by.expressions.iter().enumerate() {
6918                if i > 0 {
6919                    self.write(", ");
6920                }
6921                self.generate_ordered(ordered)?;
6922            }
6923        }
6924        if let Some(limit) = &union.limit {
6925            if self.config.pretty {
6926                self.write_newline();
6927            } else {
6928                self.write_space();
6929            }
6930            self.write_keyword("LIMIT");
6931            self.write_space();
6932            self.generate_expression(limit)?;
6933        }
6934        if let Some(offset) = &union.offset {
6935            if self.config.pretty {
6936                self.write_newline();
6937            } else {
6938                self.write_space();
6939            }
6940            self.write_keyword("OFFSET");
6941            self.write_space();
6942            self.generate_expression(offset)?;
6943        }
6944        // DISTRIBUTE BY (Hive/Spark)
6945        if let Some(distribute_by) = &union.distribute_by {
6946            self.write_space();
6947            self.write_keyword("DISTRIBUTE BY");
6948            self.write_space();
6949            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6950                if i > 0 {
6951                    self.write(", ");
6952                }
6953                self.generate_expression(expr)?;
6954            }
6955        }
6956        // SORT BY (Hive/Spark)
6957        if let Some(sort_by) = &union.sort_by {
6958            self.write_space();
6959            self.write_keyword("SORT BY");
6960            self.write_space();
6961            for (i, ord) in sort_by.expressions.iter().enumerate() {
6962                if i > 0 {
6963                    self.write(", ");
6964                }
6965                self.generate_ordered(ord)?;
6966            }
6967        }
6968        // CLUSTER BY (Hive/Spark)
6969        if let Some(cluster_by) = &union.cluster_by {
6970            self.write_space();
6971            self.write_keyword("CLUSTER BY");
6972            self.write_space();
6973            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6974                if i > 0 {
6975                    self.write(", ");
6976                }
6977                self.generate_ordered(ord)?;
6978            }
6979        }
6980        Ok(())
6981    }
6982
6983    fn generate_intersect(&mut self, outermost: &Intersect) -> Result<()> {
6984        if self.should_wrap_set_operation_modifiers(
6985            &outermost.order_by,
6986            &outermost.limit,
6987            &outermost.offset,
6988        ) {
6989            let mut inner = outermost.clone();
6990            let with = inner.with.take();
6991            let order_by = inner.order_by.take();
6992            let limit = inner.limit.take();
6993            let offset = inner.offset.take();
6994
6995            return self.generate_tsql_wrapped_set_operation(
6996                Expression::Intersect(Box::new(inner)),
6997                with,
6998                order_by,
6999                limit,
7000                offset,
7001            );
7002        }
7003
7004        // Collect the left-recursive chain iteratively to avoid stack overflow
7005        let mut chain: Vec<&Intersect> = vec![outermost];
7006        let mut leftmost: &Expression = &outermost.left;
7007        while let Expression::Intersect(inner) = leftmost {
7008            chain.push(inner);
7009            leftmost = &inner.left;
7010        }
7011
7012        if let Some(with) = &outermost.with {
7013            self.generate_with(with)?;
7014            self.write_space();
7015        }
7016
7017        self.generate_expression(leftmost)?;
7018
7019        for intersect in chain.iter().rev() {
7020            self.generate_intersect_step(intersect)?;
7021        }
7022        Ok(())
7023    }
7024
7025    /// Generate a single INTERSECT step: keyword, right expression, and trailing modifiers.
7026    fn generate_intersect_step(&mut self, intersect: &Intersect) -> Result<()> {
7027        if self.config.pretty {
7028            self.write_newline();
7029            self.write_indent();
7030        } else {
7031            self.write_space();
7032        }
7033
7034        // BigQuery set operation modifiers: [side] [kind] INTERSECT
7035        if let Some(side) = &intersect.side {
7036            self.write_keyword(side);
7037            self.write_space();
7038        }
7039        if let Some(kind) = &intersect.kind {
7040            self.write_keyword(kind);
7041            self.write_space();
7042        }
7043
7044        self.write_keyword("INTERSECT");
7045        if intersect.all {
7046            if !self.config.except_intersect_support_all_clause {
7047                self.unsupported("INTERSECT ALL is not supported")?;
7048            }
7049            self.write_space();
7050            self.write_keyword("ALL");
7051        } else if intersect.distinct {
7052            self.write_space();
7053            self.write_keyword("DISTINCT");
7054        }
7055
7056        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
7057        // DuckDB: BY NAME
7058        if intersect.corresponding || intersect.by_name {
7059            self.write_space();
7060            self.write_keyword("BY NAME");
7061        }
7062        if !intersect.on_columns.is_empty() {
7063            self.write_space();
7064            self.write_keyword("ON");
7065            self.write(" (");
7066            for (i, col) in intersect.on_columns.iter().enumerate() {
7067                if i > 0 {
7068                    self.write(", ");
7069                }
7070                self.generate_expression(col)?;
7071            }
7072            self.write(")");
7073        }
7074
7075        if self.config.pretty {
7076            self.write_newline();
7077            self.write_indent();
7078        } else {
7079            self.write_space();
7080        }
7081        self.generate_expression(&intersect.right)?;
7082        // ORDER BY, LIMIT, OFFSET for the set operation
7083        if let Some(order_by) = &intersect.order_by {
7084            if self.config.pretty {
7085                self.write_newline();
7086            } else {
7087                self.write_space();
7088            }
7089            self.write_keyword("ORDER BY");
7090            self.write_space();
7091            for (i, ordered) in order_by.expressions.iter().enumerate() {
7092                if i > 0 {
7093                    self.write(", ");
7094                }
7095                self.generate_ordered(ordered)?;
7096            }
7097        }
7098        if let Some(limit) = &intersect.limit {
7099            if self.config.pretty {
7100                self.write_newline();
7101            } else {
7102                self.write_space();
7103            }
7104            self.write_keyword("LIMIT");
7105            self.write_space();
7106            self.generate_expression(limit)?;
7107        }
7108        if let Some(offset) = &intersect.offset {
7109            if self.config.pretty {
7110                self.write_newline();
7111            } else {
7112                self.write_space();
7113            }
7114            self.write_keyword("OFFSET");
7115            self.write_space();
7116            self.generate_expression(offset)?;
7117        }
7118        // DISTRIBUTE BY (Hive/Spark)
7119        if let Some(distribute_by) = &intersect.distribute_by {
7120            self.write_space();
7121            self.write_keyword("DISTRIBUTE BY");
7122            self.write_space();
7123            for (i, expr) in distribute_by.expressions.iter().enumerate() {
7124                if i > 0 {
7125                    self.write(", ");
7126                }
7127                self.generate_expression(expr)?;
7128            }
7129        }
7130        // SORT BY (Hive/Spark)
7131        if let Some(sort_by) = &intersect.sort_by {
7132            self.write_space();
7133            self.write_keyword("SORT BY");
7134            self.write_space();
7135            for (i, ord) in sort_by.expressions.iter().enumerate() {
7136                if i > 0 {
7137                    self.write(", ");
7138                }
7139                self.generate_ordered(ord)?;
7140            }
7141        }
7142        // CLUSTER BY (Hive/Spark)
7143        if let Some(cluster_by) = &intersect.cluster_by {
7144            self.write_space();
7145            self.write_keyword("CLUSTER BY");
7146            self.write_space();
7147            for (i, ord) in cluster_by.expressions.iter().enumerate() {
7148                if i > 0 {
7149                    self.write(", ");
7150                }
7151                self.generate_ordered(ord)?;
7152            }
7153        }
7154        Ok(())
7155    }
7156
7157    fn generate_except(&mut self, outermost: &Except) -> Result<()> {
7158        if self.should_wrap_set_operation_modifiers(
7159            &outermost.order_by,
7160            &outermost.limit,
7161            &outermost.offset,
7162        ) {
7163            let mut inner = outermost.clone();
7164            let with = inner.with.take();
7165            let order_by = inner.order_by.take();
7166            let limit = inner.limit.take();
7167            let offset = inner.offset.take();
7168
7169            return self.generate_tsql_wrapped_set_operation(
7170                Expression::Except(Box::new(inner)),
7171                with,
7172                order_by,
7173                limit,
7174                offset,
7175            );
7176        }
7177
7178        // Collect the left-recursive chain iteratively to avoid stack overflow
7179        let mut chain: Vec<&Except> = vec![outermost];
7180        let mut leftmost: &Expression = &outermost.left;
7181        while let Expression::Except(inner) = leftmost {
7182            chain.push(inner);
7183            leftmost = &inner.left;
7184        }
7185
7186        if let Some(with) = &outermost.with {
7187            self.generate_with(with)?;
7188            self.write_space();
7189        }
7190
7191        self.generate_expression(leftmost)?;
7192
7193        for except in chain.iter().rev() {
7194            self.generate_except_step(except)?;
7195        }
7196        Ok(())
7197    }
7198
7199    /// Generate a single EXCEPT step: keyword, right expression, and trailing modifiers.
7200    fn generate_except_step(&mut self, except: &Except) -> Result<()> {
7201        use crate::dialects::DialectType;
7202
7203        if self.config.pretty {
7204            self.write_newline();
7205            self.write_indent();
7206        } else {
7207            self.write_space();
7208        }
7209
7210        // BigQuery set operation modifiers: [side] [kind] EXCEPT
7211        if let Some(side) = &except.side {
7212            self.write_keyword(side);
7213            self.write_space();
7214        }
7215        if let Some(kind) = &except.kind {
7216            self.write_keyword(kind);
7217            self.write_space();
7218        }
7219
7220        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
7221        match self.config.dialect {
7222            Some(DialectType::Oracle) if !except.all => {
7223                self.write_keyword("MINUS");
7224            }
7225            Some(DialectType::ClickHouse) => {
7226                self.write_keyword("EXCEPT");
7227                let preserve_all = self.config.source_dialect.is_none()
7228                    || matches!(self.config.source_dialect, Some(DialectType::ClickHouse));
7229                if except.all && preserve_all {
7230                    self.write_space();
7231                    self.write_keyword("ALL");
7232                }
7233                if except.distinct {
7234                    self.write_space();
7235                    self.write_keyword("DISTINCT");
7236                }
7237            }
7238            Some(DialectType::BigQuery) => {
7239                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
7240                self.write_keyword("EXCEPT");
7241                if except.all {
7242                    self.write_space();
7243                    self.write_keyword("ALL");
7244                } else {
7245                    self.write_space();
7246                    self.write_keyword("DISTINCT");
7247                }
7248            }
7249            _ => {
7250                self.write_keyword("EXCEPT");
7251                if except.all {
7252                    if !self.config.except_intersect_support_all_clause {
7253                        self.unsupported("EXCEPT ALL is not supported")?;
7254                    }
7255                    self.write_space();
7256                    self.write_keyword("ALL");
7257                } else if except.distinct {
7258                    self.write_space();
7259                    self.write_keyword("DISTINCT");
7260                }
7261            }
7262        }
7263
7264        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
7265        // DuckDB: BY NAME
7266        if except.corresponding || except.by_name {
7267            self.write_space();
7268            self.write_keyword("BY NAME");
7269        }
7270        if !except.on_columns.is_empty() {
7271            self.write_space();
7272            self.write_keyword("ON");
7273            self.write(" (");
7274            for (i, col) in except.on_columns.iter().enumerate() {
7275                if i > 0 {
7276                    self.write(", ");
7277                }
7278                self.generate_expression(col)?;
7279            }
7280            self.write(")");
7281        }
7282
7283        if self.config.pretty {
7284            self.write_newline();
7285            self.write_indent();
7286        } else {
7287            self.write_space();
7288        }
7289        self.generate_expression(&except.right)?;
7290        // ORDER BY, LIMIT, OFFSET for the set operation
7291        if let Some(order_by) = &except.order_by {
7292            if self.config.pretty {
7293                self.write_newline();
7294            } else {
7295                self.write_space();
7296            }
7297            self.write_keyword("ORDER BY");
7298            self.write_space();
7299            for (i, ordered) in order_by.expressions.iter().enumerate() {
7300                if i > 0 {
7301                    self.write(", ");
7302                }
7303                self.generate_ordered(ordered)?;
7304            }
7305        }
7306        if let Some(limit) = &except.limit {
7307            if self.config.pretty {
7308                self.write_newline();
7309            } else {
7310                self.write_space();
7311            }
7312            self.write_keyword("LIMIT");
7313            self.write_space();
7314            self.generate_expression(limit)?;
7315        }
7316        if let Some(offset) = &except.offset {
7317            if self.config.pretty {
7318                self.write_newline();
7319            } else {
7320                self.write_space();
7321            }
7322            self.write_keyword("OFFSET");
7323            self.write_space();
7324            self.generate_expression(offset)?;
7325        }
7326        // DISTRIBUTE BY (Hive/Spark)
7327        if let Some(distribute_by) = &except.distribute_by {
7328            self.write_space();
7329            self.write_keyword("DISTRIBUTE BY");
7330            self.write_space();
7331            for (i, expr) in distribute_by.expressions.iter().enumerate() {
7332                if i > 0 {
7333                    self.write(", ");
7334                }
7335                self.generate_expression(expr)?;
7336            }
7337        }
7338        // SORT BY (Hive/Spark)
7339        if let Some(sort_by) = &except.sort_by {
7340            self.write_space();
7341            self.write_keyword("SORT BY");
7342            self.write_space();
7343            for (i, ord) in sort_by.expressions.iter().enumerate() {
7344                if i > 0 {
7345                    self.write(", ");
7346                }
7347                self.generate_ordered(ord)?;
7348            }
7349        }
7350        // CLUSTER BY (Hive/Spark)
7351        if let Some(cluster_by) = &except.cluster_by {
7352            self.write_space();
7353            self.write_keyword("CLUSTER BY");
7354            self.write_space();
7355            for (i, ord) in cluster_by.expressions.iter().enumerate() {
7356                if i > 0 {
7357                    self.write(", ");
7358                }
7359                self.generate_ordered(ord)?;
7360            }
7361        }
7362        Ok(())
7363    }
7364
7365    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
7366        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
7367        let prepend_query_cte = if insert.with.is_none() {
7368            use crate::dialects::DialectType;
7369            let should_prepend = matches!(
7370                self.config.dialect,
7371                Some(DialectType::TSQL)
7372                    | Some(DialectType::Fabric)
7373                    | Some(DialectType::Spark)
7374                    | Some(DialectType::Databricks)
7375                    | Some(DialectType::Hive)
7376            );
7377            if should_prepend {
7378                if let Some(Expression::Select(select)) = &insert.query {
7379                    select.with.clone()
7380                } else {
7381                    None
7382                }
7383            } else {
7384                None
7385            }
7386        } else {
7387            None
7388        };
7389
7390        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
7391        if let Some(with) = &insert.with {
7392            self.generate_with(with)?;
7393            self.write_space();
7394        } else if let Some(with) = &prepend_query_cte {
7395            self.generate_with(with)?;
7396            self.write_space();
7397        }
7398
7399        // Output leading comments before INSERT
7400        for comment in &insert.leading_comments {
7401            self.write_formatted_comment(comment);
7402            self.write(" ");
7403        }
7404
7405        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
7406        if let Some(dir) = &insert.directory {
7407            self.write_keyword("INSERT OVERWRITE");
7408            if dir.local {
7409                self.write_space();
7410                self.write_keyword("LOCAL");
7411            }
7412            self.write_space();
7413            self.write_keyword("DIRECTORY");
7414            self.write_space();
7415            self.write("'");
7416            self.write(&dir.path);
7417            self.write("'");
7418
7419            // ROW FORMAT clause
7420            if let Some(row_format) = &dir.row_format {
7421                self.write_space();
7422                self.write_keyword("ROW FORMAT");
7423                if row_format.delimited {
7424                    self.write_space();
7425                    self.write_keyword("DELIMITED");
7426                }
7427                if let Some(val) = &row_format.fields_terminated_by {
7428                    self.write_space();
7429                    self.write_keyword("FIELDS TERMINATED BY");
7430                    self.write_space();
7431                    self.generate_string_literal(val)?;
7432                }
7433                if let Some(val) = &row_format.collection_items_terminated_by {
7434                    self.write_space();
7435                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
7436                    self.write_space();
7437                    self.write("'");
7438                    self.write(val);
7439                    self.write("'");
7440                }
7441                if let Some(val) = &row_format.map_keys_terminated_by {
7442                    self.write_space();
7443                    self.write_keyword("MAP KEYS TERMINATED BY");
7444                    self.write_space();
7445                    self.write("'");
7446                    self.write(val);
7447                    self.write("'");
7448                }
7449                if let Some(val) = &row_format.lines_terminated_by {
7450                    self.write_space();
7451                    self.write_keyword("LINES TERMINATED BY");
7452                    self.write_space();
7453                    self.write("'");
7454                    self.write(val);
7455                    self.write("'");
7456                }
7457                if let Some(val) = &row_format.null_defined_as {
7458                    self.write_space();
7459                    self.write_keyword("NULL DEFINED AS");
7460                    self.write_space();
7461                    self.write("'");
7462                    self.write(val);
7463                    self.write("'");
7464                }
7465            }
7466
7467            // STORED AS clause
7468            if let Some(format) = &dir.stored_as {
7469                self.write_space();
7470                self.write_keyword("STORED AS");
7471                self.write_space();
7472                self.write_keyword(format);
7473            }
7474
7475            // Query (SELECT statement)
7476            if let Some(query) = &insert.query {
7477                self.write_space();
7478                self.generate_expression(query)?;
7479            }
7480
7481            return Ok(());
7482        }
7483
7484        if insert.is_replace {
7485            // MySQL/SQLite REPLACE INTO statement
7486            self.write_keyword("REPLACE INTO");
7487        } else if insert.overwrite {
7488            // Use dialect-specific INSERT OVERWRITE format
7489            self.write_keyword("INSERT");
7490            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
7491            if let Some(ref hint) = insert.hint {
7492                self.generate_hint(hint)?;
7493            }
7494            self.write(&self.config.insert_overwrite.to_ascii_uppercase());
7495        } else if let Some(ref action) = insert.conflict_action {
7496            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
7497            self.write_keyword("INSERT OR");
7498            self.write_space();
7499            self.write_keyword(action);
7500            self.write_space();
7501            self.write_keyword("INTO");
7502        } else if insert.ignore {
7503            // MySQL INSERT IGNORE syntax
7504            self.write_keyword("INSERT IGNORE INTO");
7505        } else {
7506            self.write_keyword("INSERT");
7507            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
7508            if let Some(ref hint) = insert.hint {
7509                self.generate_hint(hint)?;
7510            }
7511            self.write_space();
7512            self.write_keyword("INTO");
7513        }
7514        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
7515        if let Some(ref func) = insert.function_target {
7516            self.write_space();
7517            self.write_keyword("FUNCTION");
7518            self.write_space();
7519            self.generate_expression(func)?;
7520        } else {
7521            self.write_space();
7522            self.generate_table(&insert.table)?;
7523        }
7524
7525        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
7526        if let Some(ref alias) = insert.alias {
7527            self.write_space();
7528            if insert.alias_explicit_as {
7529                self.write_keyword("AS");
7530                self.write_space();
7531            }
7532            self.generate_identifier(alias)?;
7533        }
7534
7535        // IF EXISTS clause (Hive)
7536        if insert.if_exists {
7537            self.write_space();
7538            self.write_keyword("IF EXISTS");
7539        }
7540
7541        // REPLACE WHERE clause (Databricks)
7542        if let Some(ref replace_where) = insert.replace_where {
7543            if self.config.pretty {
7544                self.write_newline();
7545                self.write_indent();
7546            } else {
7547                self.write_space();
7548            }
7549            self.write_keyword("REPLACE WHERE");
7550            self.write_space();
7551            self.generate_expression(replace_where)?;
7552        }
7553
7554        // Generate PARTITION clause if present
7555        if !insert.partition.is_empty() {
7556            self.write_space();
7557            self.write_keyword("PARTITION");
7558            self.write("(");
7559            for (i, (col, val)) in insert.partition.iter().enumerate() {
7560                if i > 0 {
7561                    self.write(", ");
7562                }
7563                self.generate_identifier(col)?;
7564                if let Some(v) = val {
7565                    self.write(" = ");
7566                    self.generate_expression(v)?;
7567                }
7568            }
7569            self.write(")");
7570        }
7571
7572        // ClickHouse: PARTITION BY expr
7573        if let Some(ref partition_by) = insert.partition_by {
7574            self.write_space();
7575            self.write_keyword("PARTITION BY");
7576            self.write_space();
7577            self.generate_expression(partition_by)?;
7578        }
7579
7580        // ClickHouse: SETTINGS key = val, ...
7581        if !insert.settings.is_empty() {
7582            self.write_space();
7583            self.write_keyword("SETTINGS");
7584            self.write_space();
7585            for (i, setting) in insert.settings.iter().enumerate() {
7586                if i > 0 {
7587                    self.write(", ");
7588                }
7589                self.generate_expression(setting)?;
7590            }
7591        }
7592
7593        if !insert.columns.is_empty() {
7594            if insert.alias.is_some() && insert.alias_explicit_as {
7595                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
7596                self.write("(");
7597            } else {
7598                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
7599                self.write(" (");
7600            }
7601            for (i, col) in insert.columns.iter().enumerate() {
7602                if i > 0 {
7603                    self.write(", ");
7604                }
7605                self.generate_identifier(col)?;
7606            }
7607            self.write(")");
7608        }
7609
7610        // OUTPUT clause (TSQL)
7611        if let Some(ref output) = insert.output {
7612            self.generate_output_clause(output)?;
7613        }
7614
7615        // BY NAME modifier (DuckDB)
7616        if insert.by_name {
7617            self.write_space();
7618            self.write_keyword("BY NAME");
7619        }
7620
7621        if insert.default_values {
7622            self.write_space();
7623            self.write_keyword("DEFAULT VALUES");
7624        } else if let Some(query) = &insert.query {
7625            if self.config.pretty {
7626                self.write_newline();
7627            } else {
7628                self.write_space();
7629            }
7630            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
7631            if prepend_query_cte.is_some() {
7632                if let Expression::Select(select) = query {
7633                    let mut select_no_with = select.clone();
7634                    select_no_with.with = None;
7635                    self.generate_select(&select_no_with)?;
7636                } else {
7637                    self.generate_expression(query)?;
7638                }
7639            } else {
7640                self.generate_expression(query)?;
7641            }
7642        } else if !insert.values.is_empty() {
7643            if self.config.pretty {
7644                // Pretty printing: VALUES on new line, each tuple indented
7645                self.write_newline();
7646                self.write_keyword("VALUES");
7647                self.write_newline();
7648                self.indent_level += 1;
7649                for (i, row) in insert.values.iter().enumerate() {
7650                    if i > 0 {
7651                        self.write(",");
7652                        self.write_newline();
7653                    }
7654                    self.write_indent();
7655                    self.write("(");
7656                    for (j, val) in row.iter().enumerate() {
7657                        if j > 0 {
7658                            self.write(", ");
7659                        }
7660                        self.generate_expression(val)?;
7661                    }
7662                    self.write(")");
7663                }
7664                self.indent_level -= 1;
7665            } else {
7666                // Non-pretty: single line
7667                self.write_space();
7668                self.write_keyword("VALUES");
7669                for (i, row) in insert.values.iter().enumerate() {
7670                    if i > 0 {
7671                        self.write(",");
7672                    }
7673                    self.write(" (");
7674                    for (j, val) in row.iter().enumerate() {
7675                        if j > 0 {
7676                            self.write(", ");
7677                        }
7678                        self.generate_expression(val)?;
7679                    }
7680                    self.write(")");
7681                }
7682            }
7683        }
7684
7685        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
7686        if let Some(ref source) = insert.source {
7687            self.write_space();
7688            self.write_keyword("TABLE");
7689            self.write_space();
7690            self.generate_expression(source)?;
7691        }
7692
7693        // Source alias (MySQL: VALUES (...) AS new_data)
7694        if let Some(alias) = &insert.source_alias {
7695            self.write_space();
7696            self.write_keyword("AS");
7697            self.write_space();
7698            self.generate_identifier(alias)?;
7699        }
7700
7701        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
7702        if let Some(on_conflict) = &insert.on_conflict {
7703            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
7704                self.write_space();
7705                self.generate_expression(on_conflict)?;
7706            }
7707        }
7708
7709        // RETURNING clause
7710        if !insert.returning.is_empty() {
7711            self.write_space();
7712            self.write_keyword("RETURNING");
7713            self.write_space();
7714            for (i, expr) in insert.returning.iter().enumerate() {
7715                if i > 0 {
7716                    self.write(", ");
7717                }
7718                self.generate_expression(expr)?;
7719            }
7720        }
7721
7722        Ok(())
7723    }
7724
7725    fn generate_update(&mut self, update: &Update) -> Result<()> {
7726        // Output leading comments before UPDATE
7727        for comment in &update.leading_comments {
7728            self.write_formatted_comment(comment);
7729            self.write(" ");
7730        }
7731
7732        // WITH clause (CTEs)
7733        if let Some(ref with) = update.with {
7734            self.generate_with(with)?;
7735            self.write_space();
7736        }
7737
7738        self.write_keyword("UPDATE");
7739        if let Some(hint) = &update.hint {
7740            self.generate_hint(hint)?;
7741        }
7742        self.write_space();
7743        self.generate_table(&update.table)?;
7744
7745        let mysql_like_update_from = matches!(
7746            self.config.dialect,
7747            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
7748        ) && update.from_clause.is_some();
7749
7750        let mut set_pairs = update.set.clone();
7751
7752        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
7753        let mut pre_set_joins = update.table_joins.clone();
7754        if mysql_like_update_from {
7755            let target_name = update
7756                .table
7757                .alias
7758                .as_ref()
7759                .map(|a| a.name.clone())
7760                .unwrap_or_else(|| update.table.name.name.clone());
7761
7762            for (col, _) in &mut set_pairs {
7763                if !col.name.contains('.') {
7764                    col.name = format!("{}.{}", target_name, col.name);
7765                }
7766            }
7767
7768            if let Some(from_clause) = &update.from_clause {
7769                for table_expr in &from_clause.expressions {
7770                    pre_set_joins.push(crate::expressions::Join {
7771                        this: table_expr.clone(),
7772                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7773                            value: true,
7774                        })),
7775                        using: Vec::new(),
7776                        kind: crate::expressions::JoinKind::Inner,
7777                        use_inner_keyword: false,
7778                        use_outer_keyword: false,
7779                        deferred_condition: false,
7780                        join_hint: None,
7781                        match_condition: None,
7782                        pivots: Vec::new(),
7783                        comments: Vec::new(),
7784                        nesting_group: 0,
7785                        directed: false,
7786                    });
7787                }
7788            }
7789            for join in &update.from_joins {
7790                let mut join = join.clone();
7791                if join.on.is_none() && join.using.is_empty() {
7792                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7793                        value: true,
7794                    }));
7795                }
7796                pre_set_joins.push(join);
7797            }
7798        }
7799
7800        // Extra tables for multi-table UPDATE (MySQL syntax)
7801        for extra_table in &update.extra_tables {
7802            self.write(", ");
7803            self.generate_table(extra_table)?;
7804        }
7805
7806        // JOINs attached to the table list (MySQL multi-table syntax)
7807        for join in &pre_set_joins {
7808            // generate_join already adds a leading space
7809            self.generate_join(join)?;
7810        }
7811
7812        // Teradata: FROM clause comes before SET
7813        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
7814        if teradata_from_before_set && !mysql_like_update_from {
7815            if let Some(ref from_clause) = update.from_clause {
7816                self.write_space();
7817                self.write_keyword("FROM");
7818                self.write_space();
7819                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7820                    if i > 0 {
7821                        self.write(", ");
7822                    }
7823                    self.generate_expression(table_expr)?;
7824                }
7825            }
7826            for join in &update.from_joins {
7827                self.generate_join(join)?;
7828            }
7829        }
7830
7831        self.write_space();
7832        self.write_keyword("SET");
7833        self.write_space();
7834
7835        for (i, (col, val)) in set_pairs.iter().enumerate() {
7836            if i > 0 {
7837                self.write(", ");
7838            }
7839            self.generate_identifier(col)?;
7840            self.write(" = ");
7841            self.generate_expression(val)?;
7842        }
7843
7844        // OUTPUT clause (TSQL)
7845        if let Some(ref output) = update.output {
7846            self.generate_output_clause(output)?;
7847        }
7848
7849        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
7850        if !mysql_like_update_from && !teradata_from_before_set {
7851            if let Some(ref from_clause) = update.from_clause {
7852                self.write_space();
7853                self.write_keyword("FROM");
7854                self.write_space();
7855                // Generate each table in the FROM clause
7856                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7857                    if i > 0 {
7858                        self.write(", ");
7859                    }
7860                    self.generate_expression(table_expr)?;
7861                }
7862            }
7863        }
7864
7865        if !mysql_like_update_from && !teradata_from_before_set {
7866            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
7867            for join in &update.from_joins {
7868                self.generate_join(join)?;
7869            }
7870        }
7871
7872        if let Some(where_clause) = &update.where_clause {
7873            self.write_space();
7874            self.write_keyword("WHERE");
7875            self.write_space();
7876            self.generate_expression(&where_clause.this)?;
7877        }
7878
7879        // RETURNING clause
7880        if !update.returning.is_empty() {
7881            self.write_space();
7882            self.write_keyword("RETURNING");
7883            self.write_space();
7884            for (i, expr) in update.returning.iter().enumerate() {
7885                if i > 0 {
7886                    self.write(", ");
7887                }
7888                self.generate_expression(expr)?;
7889            }
7890        }
7891
7892        // ORDER BY clause (MySQL)
7893        if let Some(ref order_by) = update.order_by {
7894            self.write_space();
7895            self.generate_order_by(order_by)?;
7896        }
7897
7898        // LIMIT clause (MySQL)
7899        if let Some(ref limit) = update.limit {
7900            self.write_space();
7901            self.write_keyword("LIMIT");
7902            self.write_space();
7903            self.generate_expression(limit)?;
7904        }
7905
7906        Ok(())
7907    }
7908
7909    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
7910        // Output WITH clause if present
7911        if let Some(with) = &delete.with {
7912            self.generate_with(with)?;
7913            self.write_space();
7914        }
7915
7916        // Output leading comments before DELETE
7917        for comment in &delete.leading_comments {
7918            self.write_formatted_comment(comment);
7919            self.write(" ");
7920        }
7921
7922        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
7923        if !delete.tables.is_empty() && !delete.tables_from_using {
7924            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
7925            self.write_keyword("DELETE");
7926            if let Some(hint) = &delete.hint {
7927                self.generate_hint(hint)?;
7928            }
7929            self.write_space();
7930            for (i, tbl) in delete.tables.iter().enumerate() {
7931                if i > 0 {
7932                    self.write(", ");
7933                }
7934                self.generate_table(tbl)?;
7935            }
7936            // TSQL: OUTPUT clause between target table and FROM
7937            if let Some(ref output) = delete.output {
7938                self.generate_output_clause(output)?;
7939            }
7940            self.write_space();
7941            self.write_keyword("FROM");
7942            self.write_space();
7943            self.generate_table(&delete.table)?;
7944        } else if !delete.tables.is_empty() && delete.tables_from_using {
7945            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
7946            self.write_keyword("DELETE");
7947            if let Some(hint) = &delete.hint {
7948                self.generate_hint(hint)?;
7949            }
7950            self.write_space();
7951            self.write_keyword("FROM");
7952            self.write_space();
7953            for (i, tbl) in delete.tables.iter().enumerate() {
7954                if i > 0 {
7955                    self.write(", ");
7956                }
7957                self.generate_table(tbl)?;
7958            }
7959        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
7960            // BigQuery-style DELETE without FROM keyword
7961            self.write_keyword("DELETE");
7962            if let Some(hint) = &delete.hint {
7963                self.generate_hint(hint)?;
7964            }
7965            self.write_space();
7966            self.generate_table(&delete.table)?;
7967        } else {
7968            self.write_keyword("DELETE");
7969            if let Some(hint) = &delete.hint {
7970                self.generate_hint(hint)?;
7971            }
7972            self.write_space();
7973            self.write_keyword("FROM");
7974            self.write_space();
7975            self.generate_table(&delete.table)?;
7976        }
7977
7978        // ClickHouse: ON CLUSTER clause
7979        if let Some(ref on_cluster) = delete.on_cluster {
7980            self.write_space();
7981            self.generate_on_cluster(on_cluster)?;
7982        }
7983
7984        // FORCE INDEX hint (MySQL)
7985        if let Some(ref idx) = delete.force_index {
7986            self.write_space();
7987            self.write_keyword("FORCE INDEX");
7988            self.write(" (");
7989            self.write(idx);
7990            self.write(")");
7991        }
7992
7993        // Optional alias
7994        if let Some(ref alias) = delete.alias {
7995            self.write_space();
7996            if delete.alias_explicit_as
7997                || matches!(self.config.dialect, Some(DialectType::BigQuery))
7998            {
7999                self.write_keyword("AS");
8000                self.write_space();
8001            }
8002            self.generate_identifier(alias)?;
8003        }
8004
8005        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
8006        if !delete.tables_from_using {
8007            for join in &delete.joins {
8008                self.generate_join(join)?;
8009            }
8010        }
8011
8012        // USING clause (PostgreSQL/DuckDB/MySQL)
8013        if !delete.using.is_empty() {
8014            self.write_space();
8015            self.write_keyword("USING");
8016            for (i, table) in delete.using.iter().enumerate() {
8017                if i > 0 {
8018                    self.write(",");
8019                }
8020                self.write_space();
8021                // Check if the table has subquery hints (DuckDB USING with subquery)
8022                if !table.hints.is_empty() && table.name.is_empty() {
8023                    // Subquery in USING: (VALUES ...) AS alias(cols)
8024                    self.generate_expression(&table.hints[0])?;
8025                    if let Some(ref alias) = table.alias {
8026                        self.write_space();
8027                        if table.alias_explicit_as {
8028                            self.write_keyword("AS");
8029                            self.write_space();
8030                        }
8031                        self.generate_identifier(alias)?;
8032                        if !table.column_aliases.is_empty() {
8033                            self.write("(");
8034                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
8035                                if j > 0 {
8036                                    self.write(", ");
8037                                }
8038                                self.generate_identifier(col_alias)?;
8039                            }
8040                            self.write(")");
8041                        }
8042                    }
8043                } else {
8044                    self.generate_table(table)?;
8045                }
8046            }
8047        }
8048
8049        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
8050        if delete.tables_from_using {
8051            for join in &delete.joins {
8052                self.generate_join(join)?;
8053            }
8054        }
8055
8056        // OUTPUT clause (TSQL) - only if not already emitted in the early position
8057        let output_already_emitted =
8058            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
8059        if !output_already_emitted {
8060            if let Some(ref output) = delete.output {
8061                self.generate_output_clause(output)?;
8062            }
8063        }
8064
8065        if let Some(where_clause) = &delete.where_clause {
8066            self.write_space();
8067            self.write_keyword("WHERE");
8068            self.write_space();
8069            self.generate_expression(&where_clause.this)?;
8070        }
8071
8072        // ORDER BY clause (MySQL)
8073        if let Some(ref order_by) = delete.order_by {
8074            self.write_space();
8075            self.generate_order_by(order_by)?;
8076        }
8077
8078        // LIMIT clause (MySQL)
8079        if let Some(ref limit) = delete.limit {
8080            self.write_space();
8081            self.write_keyword("LIMIT");
8082            self.write_space();
8083            self.generate_expression(limit)?;
8084        }
8085
8086        // RETURNING clause (PostgreSQL)
8087        if !delete.returning.is_empty() {
8088            self.write_space();
8089            self.write_keyword("RETURNING");
8090            self.write_space();
8091            for (i, expr) in delete.returning.iter().enumerate() {
8092                if i > 0 {
8093                    self.write(", ");
8094                }
8095                self.generate_expression(expr)?;
8096            }
8097        }
8098
8099        Ok(())
8100    }
8101
8102    // ==================== DDL Generation ====================
8103
8104    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
8105        // Athena: Determine if this is Hive-style DDL or Trino-style DML
8106        // CREATE TABLE AS SELECT uses Trino (double quotes)
8107        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
8108        let saved_athena_hive_context = self.athena_hive_context;
8109        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
8110        if matches!(
8111            self.config.dialect,
8112            Some(crate::dialects::DialectType::Athena)
8113        ) {
8114            // Use Hive context if:
8115            // 1. It's an EXTERNAL table, OR
8116            // 2. There's no AS SELECT clause
8117            let is_external = ct
8118                .table_modifier
8119                .as_ref()
8120                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
8121                .unwrap_or(false);
8122            let has_as_select = ct.as_select.is_some();
8123            self.athena_hive_context = is_external || !has_as_select;
8124        }
8125
8126        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
8127        if matches!(
8128            self.config.dialect,
8129            Some(crate::dialects::DialectType::TSQL)
8130        ) {
8131            if let Some(ref query) = ct.as_select {
8132                // Output WITH CTE clause if present
8133                if let Some(with_cte) = &ct.with_cte {
8134                    self.generate_with(with_cte)?;
8135                    self.write_space();
8136                }
8137
8138                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
8139                self.write_keyword("SELECT");
8140                self.write(" * ");
8141                self.write_keyword("INTO");
8142                self.write_space();
8143
8144                // If temporary, prefix with # for TSQL temp table
8145                if ct.temporary {
8146                    self.write("#");
8147                }
8148                self.generate_table(&ct.name)?;
8149
8150                self.write_space();
8151                self.write_keyword("FROM");
8152                self.write(" (");
8153                // For TSQL, add aliases to select columns to preserve column names
8154                let aliased_query = Self::add_column_aliases_to_query(query.clone());
8155                self.generate_expression(&aliased_query)?;
8156                self.write(") ");
8157                self.write_keyword("AS");
8158                self.write(" temp");
8159                return Ok(());
8160            }
8161        }
8162
8163        // Output WITH CTE clause if present
8164        if let Some(with_cte) = &ct.with_cte {
8165            self.generate_with(with_cte)?;
8166            self.write_space();
8167        }
8168
8169        // Output leading comments before CREATE
8170        for comment in &ct.leading_comments {
8171            self.write_formatted_comment(comment);
8172            self.write(" ");
8173        }
8174        self.write_keyword("CREATE");
8175
8176        if ct.or_replace {
8177            self.write_space();
8178            self.write_keyword("OR REPLACE");
8179        }
8180
8181        if ct.temporary {
8182            self.write_space();
8183            // Oracle uses GLOBAL TEMPORARY TABLE syntax
8184            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
8185                self.write_keyword("GLOBAL TEMPORARY");
8186            } else {
8187                self.write_keyword("TEMPORARY");
8188            }
8189        }
8190
8191        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
8192        let is_dictionary = ct
8193            .table_modifier
8194            .as_ref()
8195            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
8196            .unwrap_or(false);
8197        if let Some(ref modifier) = ct.table_modifier {
8198            // TRANSIENT is Snowflake-specific - skip for other dialects
8199            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
8200                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
8201            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
8202            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
8203                || modifier.eq_ignore_ascii_case("SET")
8204                || modifier.eq_ignore_ascii_case("MULTISET")
8205                || modifier.to_ascii_uppercase().contains("VOLATILE")
8206                || modifier.to_ascii_uppercase().starts_with("SET ")
8207                || modifier.to_ascii_uppercase().starts_with("MULTISET ");
8208            let skip_teradata =
8209                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
8210            if !skip_transient && !skip_teradata {
8211                self.write_space();
8212                self.write_keyword(modifier);
8213            }
8214        }
8215
8216        if !is_dictionary {
8217            self.write_space();
8218            self.write_keyword("TABLE");
8219        }
8220
8221        if ct.if_not_exists {
8222            self.write_space();
8223            self.write_keyword("IF NOT EXISTS");
8224        }
8225
8226        self.write_space();
8227        self.generate_table(&ct.name)?;
8228
8229        // ClickHouse: UUID 'xxx' clause after table name
8230        if let Some(ref uuid) = ct.uuid {
8231            self.write_space();
8232            self.write_keyword("UUID");
8233            self.write(" '");
8234            self.write(uuid);
8235            self.write("'");
8236        }
8237
8238        // ClickHouse: ON CLUSTER clause
8239        if let Some(ref on_cluster) = ct.on_cluster {
8240            self.write_space();
8241            self.generate_on_cluster(on_cluster)?;
8242        }
8243
8244        // Teradata: options after table name before column list (comma-separated)
8245        if matches!(
8246            self.config.dialect,
8247            Some(crate::dialects::DialectType::Teradata)
8248        ) && !ct.teradata_post_name_options.is_empty()
8249        {
8250            for opt in &ct.teradata_post_name_options {
8251                self.write(", ");
8252                self.write(opt);
8253            }
8254        }
8255
8256        // Snowflake: COPY GRANTS clause
8257        if ct.copy_grants {
8258            self.write_space();
8259            self.write_keyword("COPY GRANTS");
8260        }
8261
8262        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
8263        if let Some(ref using_template) = ct.using_template {
8264            self.write_space();
8265            self.write_keyword("USING TEMPLATE");
8266            self.write_space();
8267            self.generate_expression(using_template)?;
8268            return Ok(());
8269        }
8270
8271        // ClickHouse uses CREATE TABLE target AS source [ENGINE ...] for table-structure copies.
8272        // When explicit columns or constraints are present, the source must be emitted
8273        // after the parenthesized schema: CREATE TABLE target (cols) AS source.
8274        if is_clickhouse {
8275            if let Some(ref clone_source) = ct.clone_source {
8276                if ct.columns.is_empty() && ct.constraints.is_empty() {
8277                    self.write_space();
8278                    self.write_keyword("AS");
8279                    self.write_space();
8280                    self.generate_table(clone_source)?;
8281                }
8282            }
8283        }
8284
8285        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
8286        if !is_clickhouse {
8287            if let Some(ref clone_source) = ct.clone_source {
8288                self.write_space();
8289                if ct.is_copy && self.config.supports_table_copy {
8290                    // BigQuery uses COPY
8291                    self.write_keyword("COPY");
8292                } else if ct.shallow_clone {
8293                    self.write_keyword("SHALLOW CLONE");
8294                } else if ct.deep_clone {
8295                    self.write_keyword("DEEP CLONE");
8296                } else {
8297                    self.write_keyword("CLONE");
8298                }
8299                self.write_space();
8300                self.generate_table(clone_source)?;
8301                // Generate AT/BEFORE time travel clause (stored as Raw expression)
8302                if let Some(ref at_clause) = ct.clone_at_clause {
8303                    self.write_space();
8304                    self.generate_expression(at_clause)?;
8305                }
8306                return Ok(());
8307            }
8308        }
8309
8310        // Handle PARTITION OF property
8311        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
8312        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
8313        if let Some(ref partition_of) = ct.partition_of {
8314            self.write_space();
8315
8316            // Extract the PartitionedOfProperty parts to generate them separately
8317            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
8318                // Output: PARTITION OF <table>
8319                self.write_keyword("PARTITION OF");
8320                self.write_space();
8321                self.generate_expression(&pop.this)?;
8322
8323                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
8324                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
8325                    self.write(" (");
8326                    let mut first = true;
8327                    for col in &ct.columns {
8328                        if !first {
8329                            self.write(", ");
8330                        }
8331                        first = false;
8332                        self.generate_column_def(col)?;
8333                    }
8334                    for constraint in &ct.constraints {
8335                        if !first {
8336                            self.write(", ");
8337                        }
8338                        first = false;
8339                        self.generate_table_constraint(constraint)?;
8340                    }
8341                    self.write(")");
8342                }
8343
8344                // Output partition bound spec: FOR VALUES ... or DEFAULT
8345                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
8346                    self.write_space();
8347                    self.write_keyword("FOR VALUES");
8348                    self.write_space();
8349                    self.generate_expression(&pop.expression)?;
8350                } else {
8351                    self.write_space();
8352                    self.write_keyword("DEFAULT");
8353                }
8354            } else {
8355                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
8356                self.generate_expression(partition_of)?;
8357
8358                // Output columns/constraints if present
8359                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
8360                    self.write(" (");
8361                    let mut first = true;
8362                    for col in &ct.columns {
8363                        if !first {
8364                            self.write(", ");
8365                        }
8366                        first = false;
8367                        self.generate_column_def(col)?;
8368                    }
8369                    for constraint in &ct.constraints {
8370                        if !first {
8371                            self.write(", ");
8372                        }
8373                        first = false;
8374                        self.generate_table_constraint(constraint)?;
8375                    }
8376                    self.write(")");
8377                }
8378            }
8379
8380            // Output table properties (e.g., PARTITION BY RANGE(population))
8381            for prop in &ct.properties {
8382                self.write_space();
8383                self.generate_expression(prop)?;
8384            }
8385
8386            return Ok(());
8387        }
8388
8389        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
8390        // This matches Python sqlglot's behavior for SQLite dialect
8391        self.sqlite_inline_pk_columns.clear();
8392        if matches!(
8393            self.config.dialect,
8394            Some(crate::dialects::DialectType::SQLite)
8395        ) {
8396            for constraint in &ct.constraints {
8397                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8398                    // Only inline if: single column, no constraint name, and column exists in table
8399                    if columns.len() == 1 && name.is_none() {
8400                        let pk_col_name = columns[0].name.to_ascii_lowercase();
8401                        // Check if this column exists in the table
8402                        if ct
8403                            .columns
8404                            .iter()
8405                            .any(|c| c.name.name.to_ascii_lowercase() == pk_col_name)
8406                        {
8407                            self.sqlite_inline_pk_columns.insert(pk_col_name);
8408                        }
8409                    }
8410                }
8411            }
8412        }
8413
8414        // Output columns if present (even for CTAS with columns)
8415        if !ct.columns.is_empty() {
8416            if self.config.pretty {
8417                // Pretty print: each column on new line
8418                self.write(" (");
8419                self.write_newline();
8420                self.indent_level += 1;
8421                for (i, col) in ct.columns.iter().enumerate() {
8422                    if i > 0 {
8423                        self.write(",");
8424                        self.write_newline();
8425                    }
8426                    self.write_indent();
8427                    self.generate_column_def(col)?;
8428                }
8429                // Table constraints (skip inlined PRIMARY KEY for SQLite)
8430                for constraint in &ct.constraints {
8431                    // Skip single-column PRIMARY KEY that was inlined for SQLite
8432                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8433                        if columns.len() == 1
8434                            && name.is_none()
8435                            && self
8436                                .sqlite_inline_pk_columns
8437                                .contains(&columns[0].name.to_ascii_lowercase())
8438                        {
8439                            continue;
8440                        }
8441                    }
8442                    self.write(",");
8443                    self.write_newline();
8444                    self.write_indent();
8445                    self.generate_table_constraint(constraint)?;
8446                }
8447                self.indent_level -= 1;
8448                self.write_newline();
8449                self.write(")");
8450            } else {
8451                self.write(" (");
8452                for (i, col) in ct.columns.iter().enumerate() {
8453                    if i > 0 {
8454                        self.write(", ");
8455                    }
8456                    self.generate_column_def(col)?;
8457                }
8458                // Table constraints (skip inlined PRIMARY KEY for SQLite)
8459                let mut first_constraint = true;
8460                for constraint in &ct.constraints {
8461                    // Skip single-column PRIMARY KEY that was inlined for SQLite
8462                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8463                        if columns.len() == 1
8464                            && name.is_none()
8465                            && self
8466                                .sqlite_inline_pk_columns
8467                                .contains(&columns[0].name.to_ascii_lowercase())
8468                        {
8469                            continue;
8470                        }
8471                    }
8472                    if first_constraint {
8473                        self.write(", ");
8474                        first_constraint = false;
8475                    } else {
8476                        self.write(", ");
8477                    }
8478                    self.generate_table_constraint(constraint)?;
8479                }
8480                self.write(")");
8481            }
8482        } else if !ct.constraints.is_empty() {
8483            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
8484            let has_like_only = ct
8485                .constraints
8486                .iter()
8487                .all(|c| matches!(c, TableConstraint::Like { .. }));
8488            let has_tags_only = ct
8489                .constraints
8490                .iter()
8491                .all(|c| matches!(c, TableConstraint::Tags(_)));
8492            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
8493            // Most dialects: CREATE TABLE A LIKE B (no parens)
8494            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
8495            let is_pg_like = matches!(
8496                self.config.dialect,
8497                Some(crate::dialects::DialectType::PostgreSQL)
8498                    | Some(crate::dialects::DialectType::CockroachDB)
8499                    | Some(crate::dialects::DialectType::Materialize)
8500                    | Some(crate::dialects::DialectType::RisingWave)
8501                    | Some(crate::dialects::DialectType::Redshift)
8502                    | Some(crate::dialects::DialectType::Presto)
8503                    | Some(crate::dialects::DialectType::Trino)
8504                    | Some(crate::dialects::DialectType::Athena)
8505            );
8506            let use_parens = if has_like_only {
8507                is_pg_like
8508            } else {
8509                !has_tags_only
8510            };
8511            if self.config.pretty && use_parens {
8512                self.write(" (");
8513                self.write_newline();
8514                self.indent_level += 1;
8515                for (i, constraint) in ct.constraints.iter().enumerate() {
8516                    if i > 0 {
8517                        self.write(",");
8518                        self.write_newline();
8519                    }
8520                    self.write_indent();
8521                    self.generate_table_constraint(constraint)?;
8522                }
8523                self.indent_level -= 1;
8524                self.write_newline();
8525                self.write(")");
8526            } else {
8527                if use_parens {
8528                    self.write(" (");
8529                } else {
8530                    self.write_space();
8531                }
8532                for (i, constraint) in ct.constraints.iter().enumerate() {
8533                    if i > 0 {
8534                        self.write(", ");
8535                    }
8536                    self.generate_table_constraint(constraint)?;
8537                }
8538                if use_parens {
8539                    self.write(")");
8540                }
8541            }
8542        }
8543
8544        if is_clickhouse && (!ct.columns.is_empty() || !ct.constraints.is_empty()) {
8545            if let Some(ref clone_source) = ct.clone_source {
8546                self.write_space();
8547                self.write_keyword("AS");
8548                self.write_space();
8549                self.generate_table(clone_source)?;
8550            }
8551        }
8552
8553        // TSQL ON filegroup or ON filegroup (partition_column) clause
8554        if let Some(ref on_prop) = ct.on_property {
8555            self.write(" ");
8556            self.write_keyword("ON");
8557            self.write(" ");
8558            self.generate_expression(&on_prop.this)?;
8559        }
8560
8561        // BigQuery: WITH PARTITION COLUMNS (col_name col_type, ...)
8562        if !ct.with_partition_columns.is_empty() {
8563            if self.config.pretty {
8564                self.write_newline();
8565            } else {
8566                self.write_space();
8567            }
8568            self.write_keyword("WITH PARTITION COLUMNS");
8569            self.write(" (");
8570            if self.config.pretty {
8571                self.write_newline();
8572                self.indent_level += 1;
8573                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8574                    if i > 0 {
8575                        self.write(",");
8576                        self.write_newline();
8577                    }
8578                    self.write_indent();
8579                    self.generate_column_def(col)?;
8580                }
8581                self.indent_level -= 1;
8582                self.write_newline();
8583            } else {
8584                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8585                    if i > 0 {
8586                        self.write(", ");
8587                    }
8588                    self.generate_column_def(col)?;
8589                }
8590            }
8591            self.write(")");
8592        }
8593
8594        // BigQuery: WITH CONNECTION `project.region.connection`
8595        if let Some(ref conn) = ct.with_connection {
8596            if self.config.pretty {
8597                self.write_newline();
8598            } else {
8599                self.write_space();
8600            }
8601            self.write_keyword("WITH CONNECTION");
8602            self.write_space();
8603            self.generate_table(conn)?;
8604        }
8605
8606        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
8607        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
8608        if !is_clickhouse {
8609            for prop in &ct.properties {
8610                if let Expression::SchemaCommentProperty(_) = prop {
8611                    if self.config.pretty {
8612                        self.write_newline();
8613                    } else {
8614                        self.write_space();
8615                    }
8616                    self.generate_expression(prop)?;
8617                }
8618            }
8619        }
8620
8621        // WITH properties (output after columns if columns exist, otherwise before AS)
8622        if !ct.with_properties.is_empty() {
8623            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
8624            let is_snowflake_special_table = matches!(
8625                self.config.dialect,
8626                Some(crate::dialects::DialectType::Snowflake)
8627            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
8628                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
8629            if is_snowflake_special_table {
8630                for (key, value) in &ct.with_properties {
8631                    self.write_space();
8632                    self.write(key);
8633                    self.write("=");
8634                    self.write(value);
8635                }
8636            } else if self.config.pretty {
8637                self.write_newline();
8638                self.write_keyword("WITH");
8639                self.write(" (");
8640                self.write_newline();
8641                self.indent_level += 1;
8642                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8643                    if i > 0 {
8644                        self.write(",");
8645                        self.write_newline();
8646                    }
8647                    self.write_indent();
8648                    self.write(key);
8649                    self.write("=");
8650                    self.write(value);
8651                }
8652                self.indent_level -= 1;
8653                self.write_newline();
8654                self.write(")");
8655            } else {
8656                self.write_space();
8657                self.write_keyword("WITH");
8658                self.write(" (");
8659                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8660                    if i > 0 {
8661                        self.write(", ");
8662                    }
8663                    self.write(key);
8664                    self.write("=");
8665                    self.write(value);
8666                }
8667                self.write(")");
8668            }
8669        }
8670
8671        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
8672            if is_clickhouse && ct.as_select.is_some() {
8673                let mut pre = Vec::new();
8674                let mut post = Vec::new();
8675                for prop in &ct.properties {
8676                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
8677                        post.push(prop);
8678                    } else {
8679                        pre.push(prop);
8680                    }
8681                }
8682                (pre, post)
8683            } else {
8684                (ct.properties.iter().collect(), Vec::new())
8685            };
8686
8687        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
8688        for prop in pre_as_properties {
8689            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
8690            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
8691                continue;
8692            }
8693            if self.config.pretty {
8694                self.write_newline();
8695            } else {
8696                self.write_space();
8697            }
8698            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
8699            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
8700            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
8701            if let Expression::Properties(props) = prop {
8702                let is_hive_dialect = matches!(
8703                    self.config.dialect,
8704                    Some(crate::dialects::DialectType::Hive)
8705                        | Some(crate::dialects::DialectType::Spark)
8706                        | Some(crate::dialects::DialectType::Databricks)
8707                        | Some(crate::dialects::DialectType::Athena)
8708                );
8709                let is_doris_starrocks = matches!(
8710                    self.config.dialect,
8711                    Some(crate::dialects::DialectType::Doris)
8712                        | Some(crate::dialects::DialectType::StarRocks)
8713                );
8714                if is_hive_dialect {
8715                    self.generate_tblproperties_clause(&props.expressions)?;
8716                } else if is_doris_starrocks {
8717                    self.generate_properties_clause(&props.expressions)?;
8718                } else {
8719                    self.generate_options_clause(&props.expressions)?;
8720                }
8721            } else {
8722                self.generate_expression(prop)?;
8723            }
8724        }
8725
8726        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
8727        for prop in &ct.post_table_properties {
8728            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
8729                self.write(" WITH(");
8730                self.generate_system_versioning_content(svp)?;
8731                self.write(")");
8732            } else if let Expression::Properties(props) = prop {
8733                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
8734                let is_doris_starrocks = matches!(
8735                    self.config.dialect,
8736                    Some(crate::dialects::DialectType::Doris)
8737                        | Some(crate::dialects::DialectType::StarRocks)
8738                );
8739                self.write_space();
8740                if is_doris_starrocks {
8741                    self.generate_properties_clause(&props.expressions)?;
8742                } else {
8743                    self.generate_options_clause(&props.expressions)?;
8744                }
8745            } else {
8746                self.write_space();
8747                self.generate_expression(prop)?;
8748            }
8749        }
8750
8751        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
8752        // Only output for StarRocks target
8753        if let Some(ref rollup) = ct.rollup {
8754            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
8755                self.write_space();
8756                self.generate_rollup_property(rollup)?;
8757            }
8758        }
8759
8760        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
8761        // Only output for MySQL-compatible dialects; strip for others during transpilation
8762        // COMMENT is also used by Hive/Spark so we selectively preserve it
8763        let is_mysql_compatible = matches!(
8764            self.config.dialect,
8765            Some(DialectType::MySQL)
8766                | Some(DialectType::SingleStore)
8767                | Some(DialectType::Doris)
8768                | Some(DialectType::StarRocks)
8769                | None
8770        );
8771        let is_hive_compatible = matches!(
8772            self.config.dialect,
8773            Some(DialectType::Hive)
8774                | Some(DialectType::Spark)
8775                | Some(DialectType::Databricks)
8776                | Some(DialectType::Athena)
8777        );
8778        let mysql_pretty_options =
8779            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
8780        for (key, value) in &ct.mysql_table_options {
8781            // Skip non-MySQL-specific options for non-MySQL targets
8782            let should_output = if is_mysql_compatible {
8783                true
8784            } else if is_hive_compatible && key == "COMMENT" {
8785                true // COMMENT is valid in Hive/Spark table definitions
8786            } else {
8787                false
8788            };
8789            if should_output {
8790                if mysql_pretty_options {
8791                    self.write_newline();
8792                    self.write_indent();
8793                } else {
8794                    self.write_space();
8795                }
8796                self.write_keyword(key);
8797                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
8798                if key == "COMMENT" && !self.config.schema_comment_with_eq {
8799                    self.write_space();
8800                } else {
8801                    self.write("=");
8802                }
8803                self.write(value);
8804            }
8805        }
8806
8807        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
8808        if ct.temporary
8809            && matches!(
8810                self.config.dialect,
8811                Some(DialectType::Spark) | Some(DialectType::Databricks)
8812            )
8813            && ct.as_select.is_none()
8814        {
8815            self.write_space();
8816            self.write_keyword("USING PARQUET");
8817        }
8818
8819        // PostgreSQL INHERITS clause
8820        if !ct.inherits.is_empty() {
8821            self.write_space();
8822            self.write_keyword("INHERITS");
8823            self.write(" (");
8824            for (i, parent) in ct.inherits.iter().enumerate() {
8825                if i > 0 {
8826                    self.write(", ");
8827                }
8828                self.generate_table(parent)?;
8829            }
8830            self.write(")");
8831        }
8832
8833        // CREATE TABLE AS SELECT
8834        if let Some(ref query) = ct.as_select {
8835            self.write_space();
8836            self.write_keyword("AS");
8837            self.write_space();
8838            let source_is_clickhouse =
8839                matches!(self.config.source_dialect, Some(DialectType::ClickHouse));
8840            let wrap_as_select =
8841                ct.as_select_parenthesized && !(is_clickhouse && source_is_clickhouse);
8842            if wrap_as_select {
8843                self.write("(");
8844            }
8845            self.generate_expression(query)?;
8846            if wrap_as_select {
8847                self.write(")");
8848            }
8849
8850            // Teradata: WITH DATA / WITH NO DATA
8851            if let Some(with_data) = ct.with_data {
8852                self.write_space();
8853                self.write_keyword("WITH");
8854                if !with_data {
8855                    self.write_space();
8856                    self.write_keyword("NO");
8857                }
8858                self.write_space();
8859                self.write_keyword("DATA");
8860            }
8861
8862            // Teradata: AND STATISTICS / AND NO STATISTICS
8863            if let Some(with_statistics) = ct.with_statistics {
8864                self.write_space();
8865                self.write_keyword("AND");
8866                if !with_statistics {
8867                    self.write_space();
8868                    self.write_keyword("NO");
8869                }
8870                self.write_space();
8871                self.write_keyword("STATISTICS");
8872            }
8873
8874            // Teradata: Index specifications
8875            for index in &ct.teradata_indexes {
8876                self.write_space();
8877                match index.kind {
8878                    TeradataIndexKind::NoPrimary => {
8879                        self.write_keyword("NO PRIMARY INDEX");
8880                    }
8881                    TeradataIndexKind::Primary => {
8882                        self.write_keyword("PRIMARY INDEX");
8883                    }
8884                    TeradataIndexKind::PrimaryAmp => {
8885                        self.write_keyword("PRIMARY AMP INDEX");
8886                    }
8887                    TeradataIndexKind::Unique => {
8888                        self.write_keyword("UNIQUE INDEX");
8889                    }
8890                    TeradataIndexKind::UniquePrimary => {
8891                        self.write_keyword("UNIQUE PRIMARY INDEX");
8892                    }
8893                    TeradataIndexKind::Secondary => {
8894                        self.write_keyword("INDEX");
8895                    }
8896                }
8897                // Output index name if present
8898                if let Some(ref name) = index.name {
8899                    self.write_space();
8900                    self.write(name);
8901                }
8902                // Output columns if present
8903                if !index.columns.is_empty() {
8904                    self.write(" (");
8905                    for (i, col) in index.columns.iter().enumerate() {
8906                        if i > 0 {
8907                            self.write(", ");
8908                        }
8909                        self.write(col);
8910                    }
8911                    self.write(")");
8912                }
8913            }
8914
8915            // Teradata: ON COMMIT behavior for volatile tables
8916            if let Some(ref on_commit) = ct.on_commit {
8917                self.write_space();
8918                self.write_keyword("ON COMMIT");
8919                self.write_space();
8920                match on_commit {
8921                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8922                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8923                }
8924            }
8925
8926            if !post_as_properties.is_empty() {
8927                for prop in post_as_properties {
8928                    self.write_space();
8929                    self.generate_expression(prop)?;
8930                }
8931            }
8932
8933            // Restore Athena Hive context before early return
8934            self.athena_hive_context = saved_athena_hive_context;
8935            return Ok(());
8936        }
8937
8938        // ON COMMIT behavior (for non-CTAS tables)
8939        if let Some(ref on_commit) = ct.on_commit {
8940            self.write_space();
8941            self.write_keyword("ON COMMIT");
8942            self.write_space();
8943            match on_commit {
8944                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8945                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8946            }
8947        }
8948
8949        // Restore Athena Hive context
8950        self.athena_hive_context = saved_athena_hive_context;
8951
8952        Ok(())
8953    }
8954
8955    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
8956    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
8957    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
8958        // Output column name
8959        self.generate_identifier(&col.name)?;
8960        // Output data type if known
8961        if !matches!(col.data_type, DataType::Unknown) {
8962            self.write_space();
8963            self.generate_data_type(&col.data_type)?;
8964        }
8965        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
8966        for constraint in &col.constraints {
8967            if let ColumnConstraint::Path(path_expr) = constraint {
8968                self.write_space();
8969                self.write_keyword("PATH");
8970                self.write_space();
8971                self.generate_expression(path_expr)?;
8972            }
8973        }
8974        Ok(())
8975    }
8976
8977    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
8978        // Check if this is a TSQL computed column (no data type)
8979        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8980            && col
8981                .constraints
8982                .iter()
8983                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8984        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
8985        let omit_computed_type = !self.config.computed_column_with_type
8986            && col
8987                .constraints
8988                .iter()
8989                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8990
8991        // Check if this is a partition column spec (no data type, type is Unknown)
8992        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
8993        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
8994
8995        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
8996        // Also check the no_type flag for SQLite columns without types
8997        let has_no_type = col.no_type
8998            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8999                && col.constraints.is_empty());
9000
9001        self.generate_identifier(&col.name)?;
9002
9003        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
9004        let serial_expansion = if matches!(
9005            self.config.dialect,
9006            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
9007        ) {
9008            if let DataType::Custom { ref name } = col.data_type {
9009                if name.eq_ignore_ascii_case("SERIAL") {
9010                    Some("INT")
9011                } else if name.eq_ignore_ascii_case("BIGSERIAL") {
9012                    Some("BIGINT")
9013                } else if name.eq_ignore_ascii_case("SMALLSERIAL") {
9014                    Some("SMALLINT")
9015                } else {
9016                    None
9017                }
9018            } else {
9019                None
9020            }
9021        } else {
9022            None
9023        };
9024
9025        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
9026        {
9027            self.write_space();
9028            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
9029            // since ClickHouse uses explicit Nullable() in its type system.
9030            let saved_nullable_depth = self.clickhouse_nullable_depth;
9031            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
9032                self.clickhouse_nullable_depth = -1;
9033            }
9034            if let Some(int_type) = serial_expansion {
9035                // SERIAL -> INT (+ constraints added below)
9036                self.write_keyword(int_type);
9037            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
9038                // For DuckDB: convert unsigned integer types to their unsigned equivalents
9039                let unsigned_type = match &col.data_type {
9040                    DataType::Int { .. } => Some("UINTEGER"),
9041                    DataType::BigInt { .. } => Some("UBIGINT"),
9042                    DataType::SmallInt { .. } => Some("USMALLINT"),
9043                    DataType::TinyInt { .. } => Some("UTINYINT"),
9044                    _ => None,
9045                };
9046                if let Some(utype) = unsigned_type {
9047                    self.write_keyword(utype);
9048                } else {
9049                    self.generate_data_type(&col.data_type)?;
9050                }
9051            } else {
9052                self.generate_data_type(&col.data_type)?;
9053            }
9054            self.clickhouse_nullable_depth = saved_nullable_depth;
9055        }
9056
9057        // MySQL type modifiers (must come right after data type)
9058        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
9059        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
9060            self.write_space();
9061            self.write_keyword("UNSIGNED");
9062        }
9063        if col.zerofill {
9064            self.write_space();
9065            self.write_keyword("ZEROFILL");
9066        }
9067
9068        // Teradata column attributes (must come right after data type, in specific order)
9069        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
9070
9071        if let Some(ref charset) = col.character_set {
9072            self.write_space();
9073            self.write_keyword("CHARACTER SET");
9074            self.write_space();
9075            self.write(charset);
9076        }
9077
9078        if col.uppercase {
9079            self.write_space();
9080            self.write_keyword("UPPERCASE");
9081        }
9082
9083        if let Some(casespecific) = col.casespecific {
9084            self.write_space();
9085            if casespecific {
9086                self.write_keyword("CASESPECIFIC");
9087            } else {
9088                self.write_keyword("NOT CASESPECIFIC");
9089            }
9090        }
9091
9092        if let Some(ref format) = col.format {
9093            self.write_space();
9094            self.write_keyword("FORMAT");
9095            self.write(" '");
9096            self.write(format);
9097            self.write("'");
9098        }
9099
9100        if let Some(ref title) = col.title {
9101            self.write_space();
9102            self.write_keyword("TITLE");
9103            self.write(" '");
9104            self.write(title);
9105            self.write("'");
9106        }
9107
9108        if let Some(length) = col.inline_length {
9109            self.write_space();
9110            self.write_keyword("INLINE LENGTH");
9111            self.write(" ");
9112            self.write(&length.to_string());
9113        }
9114
9115        if let Some(ref compress) = col.compress {
9116            self.write_space();
9117            self.write_keyword("COMPRESS");
9118            if !compress.is_empty() {
9119                // Single string literal: output without parentheses (Teradata syntax)
9120                if compress.len() == 1 {
9121                    if let Expression::Literal(lit) = &compress[0] {
9122                        if let Literal::String(_) = lit.as_ref() {
9123                            self.write_space();
9124                            self.generate_expression(&compress[0])?;
9125                        }
9126                    } else {
9127                        self.write(" (");
9128                        self.generate_expression(&compress[0])?;
9129                        self.write(")");
9130                    }
9131                } else {
9132                    self.write(" (");
9133                    for (i, val) in compress.iter().enumerate() {
9134                        if i > 0 {
9135                            self.write(", ");
9136                        }
9137                        self.generate_expression(val)?;
9138                    }
9139                    self.write(")");
9140                }
9141            }
9142        }
9143
9144        // Column constraints - output in original order if constraint_order is populated
9145        // Otherwise fall back to legacy fixed order for backward compatibility
9146        if !col.constraint_order.is_empty() {
9147            // Use constraint_order for original ordering
9148            // Track indices for constraints stored in the constraints Vec
9149            let mut references_idx = 0;
9150            let mut check_idx = 0;
9151            let mut generated_idx = 0;
9152            let mut collate_idx = 0;
9153            let mut comment_idx = 0;
9154            // The preprocessing in dialects/mod.rs now handles the correct ordering of
9155            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
9156            let defer_not_null_after_identity = false;
9157            let mut pending_not_null_after_identity = false;
9158
9159            for constraint_type in &col.constraint_order {
9160                match constraint_type {
9161                    ConstraintType::PrimaryKey => {
9162                        // Materialize doesn't support PRIMARY KEY column constraints
9163                        if col.primary_key
9164                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
9165                        {
9166                            if let Some(ref cname) = col.primary_key_constraint_name {
9167                                self.write_space();
9168                                self.write_keyword("CONSTRAINT");
9169                                self.write_space();
9170                                self.write(cname);
9171                            }
9172                            self.write_space();
9173                            self.write_keyword("PRIMARY KEY");
9174                            if let Some(ref order) = col.primary_key_order {
9175                                self.write_space();
9176                                match order {
9177                                    SortOrder::Asc => self.write_keyword("ASC"),
9178                                    SortOrder::Desc => self.write_keyword("DESC"),
9179                                }
9180                            }
9181                        }
9182                    }
9183                    ConstraintType::Unique => {
9184                        if col.unique {
9185                            if let Some(ref cname) = col.unique_constraint_name {
9186                                self.write_space();
9187                                self.write_keyword("CONSTRAINT");
9188                                self.write_space();
9189                                self.write(cname);
9190                            }
9191                            self.write_space();
9192                            self.write_keyword("UNIQUE");
9193                            // PostgreSQL 15+: NULLS NOT DISTINCT
9194                            if col.unique_nulls_not_distinct {
9195                                self.write(" NULLS NOT DISTINCT");
9196                            }
9197                        }
9198                    }
9199                    ConstraintType::NotNull => {
9200                        if col.nullable == Some(false) {
9201                            if defer_not_null_after_identity {
9202                                pending_not_null_after_identity = true;
9203                                continue;
9204                            }
9205                            if let Some(ref cname) = col.not_null_constraint_name {
9206                                self.write_space();
9207                                self.write_keyword("CONSTRAINT");
9208                                self.write_space();
9209                                self.write(cname);
9210                            }
9211                            self.write_space();
9212                            self.write_keyword("NOT NULL");
9213                        }
9214                    }
9215                    ConstraintType::Null => {
9216                        if col.nullable == Some(true) {
9217                            self.write_space();
9218                            self.write_keyword("NULL");
9219                        }
9220                    }
9221                    ConstraintType::Default => {
9222                        if let Some(ref default) = col.default {
9223                            self.write_space();
9224                            self.write_keyword("DEFAULT");
9225                            self.write_space();
9226                            self.generate_expression(default)?;
9227                        }
9228                    }
9229                    ConstraintType::AutoIncrement => {
9230                        if col.auto_increment {
9231                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
9232                            if matches!(
9233                                self.config.dialect,
9234                                Some(crate::dialects::DialectType::DuckDB)
9235                            ) {
9236                                // Skip - DuckDB uses sequences or rowid instead
9237                            } else if matches!(
9238                                self.config.dialect,
9239                                Some(crate::dialects::DialectType::Materialize)
9240                            ) {
9241                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
9242                                if !matches!(col.nullable, Some(false)) {
9243                                    self.write_space();
9244                                    self.write_keyword("NOT NULL");
9245                                }
9246                            } else if matches!(
9247                                self.config.dialect,
9248                                Some(crate::dialects::DialectType::PostgreSQL)
9249                            ) {
9250                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
9251                                self.write_space();
9252                                self.generate_auto_increment_keyword(col)?;
9253                            } else if matches!(
9254                                self.config.dialect,
9255                                Some(crate::dialects::DialectType::SQLite)
9256                            ) && !col.primary_key
9257                                && self
9258                                    .sqlite_inline_pk_columns
9259                                    .contains(&col.name.name.to_ascii_lowercase())
9260                            {
9261                                // SQLite requires AUTOINCREMENT after PRIMARY KEY.
9262                                // The table-level primary key is emitted later inline.
9263                            } else {
9264                                self.write_space();
9265                                self.generate_auto_increment_keyword(col)?;
9266                                if pending_not_null_after_identity {
9267                                    self.write_space();
9268                                    self.write_keyword("NOT NULL");
9269                                    pending_not_null_after_identity = false;
9270                                }
9271                            }
9272                        } // close else for DuckDB skip
9273                    }
9274                    ConstraintType::References => {
9275                        // Find next References constraint
9276                        while references_idx < col.constraints.len() {
9277                            if let ColumnConstraint::References(fk_ref) =
9278                                &col.constraints[references_idx]
9279                            {
9280                                // CONSTRAINT name if present
9281                                if let Some(ref name) = fk_ref.constraint_name {
9282                                    self.write_space();
9283                                    self.write_keyword("CONSTRAINT");
9284                                    self.write_space();
9285                                    self.write(name);
9286                                }
9287                                self.write_space();
9288                                if fk_ref.has_foreign_key_keywords {
9289                                    self.write_keyword("FOREIGN KEY");
9290                                    self.write_space();
9291                                }
9292                                self.write_keyword("REFERENCES");
9293                                self.write_space();
9294                                self.generate_table(&fk_ref.table)?;
9295                                if !fk_ref.columns.is_empty() {
9296                                    self.write(" (");
9297                                    for (i, c) in fk_ref.columns.iter().enumerate() {
9298                                        if i > 0 {
9299                                            self.write(", ");
9300                                        }
9301                                        self.generate_identifier(c)?;
9302                                    }
9303                                    self.write(")");
9304                                }
9305                                self.generate_referential_actions(fk_ref)?;
9306                                references_idx += 1;
9307                                break;
9308                            }
9309                            references_idx += 1;
9310                        }
9311                    }
9312                    ConstraintType::Check => {
9313                        // Find next Check constraint
9314                        while check_idx < col.constraints.len() {
9315                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
9316                                // Output CONSTRAINT name if present (only for first CHECK)
9317                                if check_idx == 0 {
9318                                    if let Some(ref cname) = col.check_constraint_name {
9319                                        self.write_space();
9320                                        self.write_keyword("CONSTRAINT");
9321                                        self.write_space();
9322                                        self.write(cname);
9323                                    }
9324                                }
9325                                self.write_space();
9326                                self.write_keyword("CHECK");
9327                                self.write(" (");
9328                                self.generate_expression(expr)?;
9329                                self.write(")");
9330                                check_idx += 1;
9331                                break;
9332                            }
9333                            check_idx += 1;
9334                        }
9335                    }
9336                    ConstraintType::GeneratedAsIdentity => {
9337                        // Find next GeneratedAsIdentity constraint
9338                        while generated_idx < col.constraints.len() {
9339                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
9340                                &col.constraints[generated_idx]
9341                            {
9342                                self.write_space();
9343                                // Redshift uses IDENTITY(start, increment) syntax
9344                                if matches!(
9345                                    self.config.dialect,
9346                                    Some(crate::dialects::DialectType::Redshift)
9347                                ) {
9348                                    self.write_keyword("IDENTITY");
9349                                    self.write("(");
9350                                    if let Some(ref start) = gen.start {
9351                                        self.generate_expression(start)?;
9352                                    } else {
9353                                        self.write("0");
9354                                    }
9355                                    self.write(", ");
9356                                    if let Some(ref incr) = gen.increment {
9357                                        self.generate_expression(incr)?;
9358                                    } else {
9359                                        self.write("1");
9360                                    }
9361                                    self.write(")");
9362                                } else {
9363                                    self.write_keyword("GENERATED");
9364                                    if gen.always {
9365                                        self.write_space();
9366                                        self.write_keyword("ALWAYS");
9367                                    } else {
9368                                        self.write_space();
9369                                        self.write_keyword("BY DEFAULT");
9370                                        if gen.on_null {
9371                                            self.write_space();
9372                                            self.write_keyword("ON NULL");
9373                                        }
9374                                    }
9375                                    self.write_space();
9376                                    self.write_keyword("AS IDENTITY");
9377
9378                                    let has_options = gen.start.is_some()
9379                                        || gen.increment.is_some()
9380                                        || gen.minvalue.is_some()
9381                                        || gen.maxvalue.is_some()
9382                                        || gen.cycle.is_some();
9383                                    if has_options {
9384                                        self.write(" (");
9385                                        let mut first = true;
9386                                        if let Some(ref start) = gen.start {
9387                                            if !first {
9388                                                self.write(" ");
9389                                            }
9390                                            first = false;
9391                                            self.write_keyword("START WITH");
9392                                            self.write_space();
9393                                            self.generate_expression(start)?;
9394                                        }
9395                                        if let Some(ref incr) = gen.increment {
9396                                            if !first {
9397                                                self.write(" ");
9398                                            }
9399                                            first = false;
9400                                            self.write_keyword("INCREMENT BY");
9401                                            self.write_space();
9402                                            self.generate_expression(incr)?;
9403                                        }
9404                                        if let Some(ref minv) = gen.minvalue {
9405                                            if !first {
9406                                                self.write(" ");
9407                                            }
9408                                            first = false;
9409                                            self.write_keyword("MINVALUE");
9410                                            self.write_space();
9411                                            self.generate_expression(minv)?;
9412                                        }
9413                                        if let Some(ref maxv) = gen.maxvalue {
9414                                            if !first {
9415                                                self.write(" ");
9416                                            }
9417                                            first = false;
9418                                            self.write_keyword("MAXVALUE");
9419                                            self.write_space();
9420                                            self.generate_expression(maxv)?;
9421                                        }
9422                                        if let Some(cycle) = gen.cycle {
9423                                            if !first {
9424                                                self.write(" ");
9425                                            }
9426                                            if cycle {
9427                                                self.write_keyword("CYCLE");
9428                                            } else {
9429                                                self.write_keyword("NO CYCLE");
9430                                            }
9431                                        }
9432                                        self.write(")");
9433                                    }
9434                                }
9435                                generated_idx += 1;
9436                                break;
9437                            }
9438                            generated_idx += 1;
9439                        }
9440                    }
9441                    ConstraintType::Collate => {
9442                        // Find next Collate constraint
9443                        while collate_idx < col.constraints.len() {
9444                            if let ColumnConstraint::Collate(collation) =
9445                                &col.constraints[collate_idx]
9446                            {
9447                                self.write_space();
9448                                self.write_keyword("COLLATE");
9449                                self.write_space();
9450                                self.generate_identifier(collation)?;
9451                                collate_idx += 1;
9452                                break;
9453                            }
9454                            collate_idx += 1;
9455                        }
9456                    }
9457                    ConstraintType::Comment => {
9458                        // Find next Comment constraint
9459                        while comment_idx < col.constraints.len() {
9460                            if let ColumnConstraint::Comment(comment) =
9461                                &col.constraints[comment_idx]
9462                            {
9463                                self.write_space();
9464                                self.write_keyword("COMMENT");
9465                                self.write_space();
9466                                self.generate_string_literal(comment)?;
9467                                comment_idx += 1;
9468                                break;
9469                            }
9470                            comment_idx += 1;
9471                        }
9472                    }
9473                    ConstraintType::Tags => {
9474                        // Find next Tags constraint (Snowflake)
9475                        for constraint in &col.constraints {
9476                            if let ColumnConstraint::Tags(tags) = constraint {
9477                                self.write_space();
9478                                self.write_keyword("TAG");
9479                                self.write(" (");
9480                                for (i, expr) in tags.expressions.iter().enumerate() {
9481                                    if i > 0 {
9482                                        self.write(", ");
9483                                    }
9484                                    self.generate_expression(expr)?;
9485                                }
9486                                self.write(")");
9487                                break;
9488                            }
9489                        }
9490                    }
9491                    ConstraintType::ComputedColumn => {
9492                        // Find next ComputedColumn constraint
9493                        for constraint in &col.constraints {
9494                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
9495                                self.write_space();
9496                                self.generate_computed_column_inline(cc)?;
9497                                break;
9498                            }
9499                        }
9500                    }
9501                    ConstraintType::GeneratedAsRow => {
9502                        // Find next GeneratedAsRow constraint
9503                        for constraint in &col.constraints {
9504                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
9505                                self.write_space();
9506                                self.generate_generated_as_row_inline(gar)?;
9507                                break;
9508                            }
9509                        }
9510                    }
9511                    ConstraintType::OnUpdate => {
9512                        if let Some(ref expr) = col.on_update {
9513                            self.write_space();
9514                            self.write_keyword("ON UPDATE");
9515                            self.write_space();
9516                            self.generate_expression(expr)?;
9517                        }
9518                    }
9519                    ConstraintType::Encode => {
9520                        if let Some(ref encoding) = col.encoding {
9521                            self.write_space();
9522                            self.write_keyword("ENCODE");
9523                            self.write_space();
9524                            self.write(encoding);
9525                        }
9526                    }
9527                    ConstraintType::Path => {
9528                        // Find next Path constraint
9529                        for constraint in &col.constraints {
9530                            if let ColumnConstraint::Path(path_expr) = constraint {
9531                                self.write_space();
9532                                self.write_keyword("PATH");
9533                                self.write_space();
9534                                self.generate_expression(path_expr)?;
9535                                break;
9536                            }
9537                        }
9538                    }
9539                }
9540            }
9541            if pending_not_null_after_identity {
9542                self.write_space();
9543                self.write_keyword("NOT NULL");
9544            }
9545        } else {
9546            // Legacy fixed order for backward compatibility
9547            if col.primary_key {
9548                self.write_space();
9549                self.write_keyword("PRIMARY KEY");
9550                if let Some(ref order) = col.primary_key_order {
9551                    self.write_space();
9552                    match order {
9553                        SortOrder::Asc => self.write_keyword("ASC"),
9554                        SortOrder::Desc => self.write_keyword("DESC"),
9555                    }
9556                }
9557            }
9558
9559            if col.unique {
9560                self.write_space();
9561                self.write_keyword("UNIQUE");
9562                // PostgreSQL 15+: NULLS NOT DISTINCT
9563                if col.unique_nulls_not_distinct {
9564                    self.write(" NULLS NOT DISTINCT");
9565                }
9566            }
9567
9568            match col.nullable {
9569                Some(false) => {
9570                    self.write_space();
9571                    self.write_keyword("NOT NULL");
9572                }
9573                Some(true) => {
9574                    self.write_space();
9575                    self.write_keyword("NULL");
9576                }
9577                None => {}
9578            }
9579
9580            if let Some(ref default) = col.default {
9581                self.write_space();
9582                self.write_keyword("DEFAULT");
9583                self.write_space();
9584                self.generate_expression(default)?;
9585            }
9586
9587            if col.auto_increment {
9588                self.write_space();
9589                self.generate_auto_increment_keyword(col)?;
9590            }
9591
9592            // Column-level constraints from Vec
9593            for constraint in &col.constraints {
9594                match constraint {
9595                    ColumnConstraint::References(fk_ref) => {
9596                        self.write_space();
9597                        if fk_ref.has_foreign_key_keywords {
9598                            self.write_keyword("FOREIGN KEY");
9599                            self.write_space();
9600                        }
9601                        self.write_keyword("REFERENCES");
9602                        self.write_space();
9603                        self.generate_table(&fk_ref.table)?;
9604                        if !fk_ref.columns.is_empty() {
9605                            self.write(" (");
9606                            for (i, c) in fk_ref.columns.iter().enumerate() {
9607                                if i > 0 {
9608                                    self.write(", ");
9609                                }
9610                                self.generate_identifier(c)?;
9611                            }
9612                            self.write(")");
9613                        }
9614                        self.generate_referential_actions(fk_ref)?;
9615                    }
9616                    ColumnConstraint::Check(expr) => {
9617                        self.write_space();
9618                        self.write_keyword("CHECK");
9619                        self.write(" (");
9620                        self.generate_expression(expr)?;
9621                        self.write(")");
9622                    }
9623                    ColumnConstraint::GeneratedAsIdentity(gen) => {
9624                        self.write_space();
9625                        // Redshift uses IDENTITY(start, increment) syntax
9626                        if matches!(
9627                            self.config.dialect,
9628                            Some(crate::dialects::DialectType::Redshift)
9629                        ) {
9630                            self.write_keyword("IDENTITY");
9631                            self.write("(");
9632                            if let Some(ref start) = gen.start {
9633                                self.generate_expression(start)?;
9634                            } else {
9635                                self.write("0");
9636                            }
9637                            self.write(", ");
9638                            if let Some(ref incr) = gen.increment {
9639                                self.generate_expression(incr)?;
9640                            } else {
9641                                self.write("1");
9642                            }
9643                            self.write(")");
9644                        } else {
9645                            self.write_keyword("GENERATED");
9646                            if gen.always {
9647                                self.write_space();
9648                                self.write_keyword("ALWAYS");
9649                            } else {
9650                                self.write_space();
9651                                self.write_keyword("BY DEFAULT");
9652                                if gen.on_null {
9653                                    self.write_space();
9654                                    self.write_keyword("ON NULL");
9655                                }
9656                            }
9657                            self.write_space();
9658                            self.write_keyword("AS IDENTITY");
9659
9660                            let has_options = gen.start.is_some()
9661                                || gen.increment.is_some()
9662                                || gen.minvalue.is_some()
9663                                || gen.maxvalue.is_some()
9664                                || gen.cycle.is_some();
9665                            if has_options {
9666                                self.write(" (");
9667                                let mut first = true;
9668                                if let Some(ref start) = gen.start {
9669                                    if !first {
9670                                        self.write(" ");
9671                                    }
9672                                    first = false;
9673                                    self.write_keyword("START WITH");
9674                                    self.write_space();
9675                                    self.generate_expression(start)?;
9676                                }
9677                                if let Some(ref incr) = gen.increment {
9678                                    if !first {
9679                                        self.write(" ");
9680                                    }
9681                                    first = false;
9682                                    self.write_keyword("INCREMENT BY");
9683                                    self.write_space();
9684                                    self.generate_expression(incr)?;
9685                                }
9686                                if let Some(ref minv) = gen.minvalue {
9687                                    if !first {
9688                                        self.write(" ");
9689                                    }
9690                                    first = false;
9691                                    self.write_keyword("MINVALUE");
9692                                    self.write_space();
9693                                    self.generate_expression(minv)?;
9694                                }
9695                                if let Some(ref maxv) = gen.maxvalue {
9696                                    if !first {
9697                                        self.write(" ");
9698                                    }
9699                                    first = false;
9700                                    self.write_keyword("MAXVALUE");
9701                                    self.write_space();
9702                                    self.generate_expression(maxv)?;
9703                                }
9704                                if let Some(cycle) = gen.cycle {
9705                                    if !first {
9706                                        self.write(" ");
9707                                    }
9708                                    if cycle {
9709                                        self.write_keyword("CYCLE");
9710                                    } else {
9711                                        self.write_keyword("NO CYCLE");
9712                                    }
9713                                }
9714                                self.write(")");
9715                            }
9716                        }
9717                    }
9718                    ColumnConstraint::Collate(collation) => {
9719                        self.write_space();
9720                        self.write_keyword("COLLATE");
9721                        self.write_space();
9722                        self.generate_identifier(collation)?;
9723                    }
9724                    ColumnConstraint::Comment(comment) => {
9725                        self.write_space();
9726                        self.write_keyword("COMMENT");
9727                        self.write_space();
9728                        self.generate_string_literal(comment)?;
9729                    }
9730                    ColumnConstraint::Path(path_expr) => {
9731                        self.write_space();
9732                        self.write_keyword("PATH");
9733                        self.write_space();
9734                        self.generate_expression(path_expr)?;
9735                    }
9736                    _ => {} // Other constraints handled above
9737                }
9738            }
9739
9740            // Redshift: ENCODE encoding_type (legacy path)
9741            if let Some(ref encoding) = col.encoding {
9742                self.write_space();
9743                self.write_keyword("ENCODE");
9744                self.write_space();
9745                self.write(encoding);
9746            }
9747        }
9748
9749        // ClickHouse: CODEC(...)
9750        if let Some(ref codec) = col.codec {
9751            self.write_space();
9752            self.write_keyword("CODEC");
9753            self.write("(");
9754            self.write(codec);
9755            self.write(")");
9756        }
9757
9758        if let Some(visible) = col.visible {
9759            self.write_space();
9760            if visible {
9761                self.write_keyword("VISIBLE");
9762            } else {
9763                self.write_keyword("INVISIBLE");
9764            }
9765        }
9766
9767        // ClickHouse: EPHEMERAL [expr]
9768        if let Some(ref ephemeral) = col.ephemeral {
9769            self.write_space();
9770            self.write_keyword("EPHEMERAL");
9771            if let Some(ref expr) = ephemeral {
9772                self.write_space();
9773                self.generate_expression(expr)?;
9774            }
9775        }
9776
9777        // ClickHouse: MATERIALIZED expr
9778        if let Some(ref mat_expr) = col.materialized_expr {
9779            self.write_space();
9780            self.write_keyword("MATERIALIZED");
9781            self.write_space();
9782            self.generate_expression(mat_expr)?;
9783        }
9784
9785        // ClickHouse: ALIAS expr
9786        if let Some(ref alias_expr) = col.alias_expr {
9787            self.write_space();
9788            self.write_keyword("ALIAS");
9789            self.write_space();
9790            self.generate_expression(alias_expr)?;
9791        }
9792
9793        // ClickHouse: TTL expr
9794        if let Some(ref ttl_expr) = col.ttl_expr {
9795            self.write_space();
9796            self.write_keyword("TTL");
9797            self.write_space();
9798            self.generate_expression(ttl_expr)?;
9799        }
9800
9801        // TSQL: NOT FOR REPLICATION
9802        if col.not_for_replication
9803            && matches!(
9804                self.config.dialect,
9805                Some(crate::dialects::DialectType::TSQL)
9806                    | Some(crate::dialects::DialectType::Fabric)
9807            )
9808        {
9809            self.write_space();
9810            self.write_keyword("NOT FOR REPLICATION");
9811        }
9812
9813        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
9814        if !col.options.is_empty() {
9815            self.write_space();
9816            self.generate_options_clause(&col.options)?;
9817        }
9818
9819        // SQLite: Inline PRIMARY KEY from table constraint
9820        // This comes at the end, after all existing column constraints
9821        if !col.primary_key
9822            && self
9823                .sqlite_inline_pk_columns
9824                .contains(&col.name.name.to_ascii_lowercase())
9825        {
9826            self.write_space();
9827            self.write_keyword("PRIMARY KEY");
9828            if matches!(self.config.dialect, Some(DialectType::SQLite)) && col.auto_increment {
9829                self.write_space();
9830                self.generate_auto_increment_keyword(col)?;
9831            }
9832        }
9833
9834        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
9835        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
9836        if serial_expansion.is_some() {
9837            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
9838                self.write_space();
9839                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
9840            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
9841                self.write_space();
9842                self.write_keyword("NOT NULL");
9843            }
9844        }
9845
9846        Ok(())
9847    }
9848
9849    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
9850        match constraint {
9851            TableConstraint::PrimaryKey {
9852                name,
9853                columns,
9854                include_columns,
9855                modifiers,
9856                has_constraint_keyword,
9857            } => {
9858                if let Some(ref n) = name {
9859                    if *has_constraint_keyword {
9860                        self.write_keyword("CONSTRAINT");
9861                        self.write_space();
9862                        self.generate_identifier(n)?;
9863                        self.write_space();
9864                    }
9865                }
9866                self.write_keyword("PRIMARY KEY");
9867                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9868                if let Some(ref clustered) = modifiers.clustered {
9869                    self.write_space();
9870                    self.write_keyword(clustered);
9871                }
9872                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
9873                if let Some(ref n) = name {
9874                    if !*has_constraint_keyword {
9875                        self.write_space();
9876                        self.generate_identifier(n)?;
9877                    }
9878                }
9879                self.write(" (");
9880                for (i, col) in columns.iter().enumerate() {
9881                    if i > 0 {
9882                        self.write(", ");
9883                    }
9884                    self.generate_identifier(col)?;
9885                }
9886                self.write(")");
9887                if !include_columns.is_empty() {
9888                    self.write_space();
9889                    self.write_keyword("INCLUDE");
9890                    self.write(" (");
9891                    for (i, col) in include_columns.iter().enumerate() {
9892                        if i > 0 {
9893                            self.write(", ");
9894                        }
9895                        self.generate_identifier(col)?;
9896                    }
9897                    self.write(")");
9898                }
9899                self.generate_constraint_modifiers(modifiers);
9900            }
9901            TableConstraint::Unique {
9902                name,
9903                columns,
9904                columns_parenthesized,
9905                modifiers,
9906                has_constraint_keyword,
9907                nulls_not_distinct,
9908            } => {
9909                if let Some(ref n) = name {
9910                    if *has_constraint_keyword {
9911                        self.write_keyword("CONSTRAINT");
9912                        self.write_space();
9913                        self.generate_identifier(n)?;
9914                        self.write_space();
9915                    }
9916                }
9917                self.write_keyword("UNIQUE");
9918                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9919                if let Some(ref clustered) = modifiers.clustered {
9920                    self.write_space();
9921                    self.write_keyword(clustered);
9922                }
9923                // PostgreSQL 15+: NULLS NOT DISTINCT
9924                if *nulls_not_distinct {
9925                    self.write(" NULLS NOT DISTINCT");
9926                }
9927                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
9928                if let Some(ref n) = name {
9929                    if !*has_constraint_keyword {
9930                        self.write_space();
9931                        self.generate_identifier(n)?;
9932                    }
9933                }
9934                if *columns_parenthesized {
9935                    self.write(" (");
9936                    for (i, col) in columns.iter().enumerate() {
9937                        if i > 0 {
9938                            self.write(", ");
9939                        }
9940                        self.generate_identifier(col)?;
9941                    }
9942                    self.write(")");
9943                } else {
9944                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
9945                    for col in columns.iter() {
9946                        self.write_space();
9947                        self.generate_identifier(col)?;
9948                    }
9949                }
9950                self.generate_constraint_modifiers(modifiers);
9951            }
9952            TableConstraint::ForeignKey {
9953                name,
9954                columns,
9955                references,
9956                on_delete,
9957                on_update,
9958                modifiers,
9959            } => {
9960                if let Some(ref n) = name {
9961                    self.write_keyword("CONSTRAINT");
9962                    self.write_space();
9963                    self.generate_identifier(n)?;
9964                    self.write_space();
9965                }
9966                self.write_keyword("FOREIGN KEY");
9967                self.write(" (");
9968                for (i, col) in columns.iter().enumerate() {
9969                    if i > 0 {
9970                        self.write(", ");
9971                    }
9972                    self.generate_identifier(col)?;
9973                }
9974                self.write(")");
9975                if let Some(ref refs) = references {
9976                    self.write(" ");
9977                    self.write_keyword("REFERENCES");
9978                    self.write_space();
9979                    self.generate_table(&refs.table)?;
9980                    if !refs.columns.is_empty() {
9981                        if self.config.pretty {
9982                            self.write(" (");
9983                            self.write_newline();
9984                            self.indent_level += 1;
9985                            for (i, col) in refs.columns.iter().enumerate() {
9986                                if i > 0 {
9987                                    self.write(",");
9988                                    self.write_newline();
9989                                }
9990                                self.write_indent();
9991                                self.generate_identifier(col)?;
9992                            }
9993                            self.indent_level -= 1;
9994                            self.write_newline();
9995                            self.write_indent();
9996                            self.write(")");
9997                        } else {
9998                            self.write(" (");
9999                            for (i, col) in refs.columns.iter().enumerate() {
10000                                if i > 0 {
10001                                    self.write(", ");
10002                                }
10003                                self.generate_identifier(col)?;
10004                            }
10005                            self.write(")");
10006                        }
10007                    }
10008                    self.generate_referential_actions(refs)?;
10009                } else {
10010                    // No REFERENCES - output ON DELETE/ON UPDATE directly
10011                    if let Some(ref action) = on_delete {
10012                        self.write_space();
10013                        self.write_keyword("ON DELETE");
10014                        self.write_space();
10015                        self.generate_referential_action(action);
10016                    }
10017                    if let Some(ref action) = on_update {
10018                        self.write_space();
10019                        self.write_keyword("ON UPDATE");
10020                        self.write_space();
10021                        self.generate_referential_action(action);
10022                    }
10023                }
10024                self.generate_constraint_modifiers(modifiers);
10025            }
10026            TableConstraint::Check {
10027                name,
10028                expression,
10029                modifiers,
10030            } => {
10031                if let Some(ref n) = name {
10032                    self.write_keyword("CONSTRAINT");
10033                    self.write_space();
10034                    self.generate_identifier(n)?;
10035                    self.write_space();
10036                }
10037                self.write_keyword("CHECK");
10038                self.write(" (");
10039                self.generate_expression(expression)?;
10040                self.write(")");
10041                self.generate_constraint_modifiers(modifiers);
10042            }
10043            TableConstraint::Assume { name, expression } => {
10044                if let Some(ref n) = name {
10045                    self.write_keyword("CONSTRAINT");
10046                    self.write_space();
10047                    self.generate_identifier(n)?;
10048                    self.write_space();
10049                }
10050                self.write_keyword("ASSUME");
10051                self.write(" (");
10052                self.generate_expression(expression)?;
10053                self.write(")");
10054            }
10055            TableConstraint::Default {
10056                name,
10057                expression,
10058                column,
10059            } => {
10060                if let Some(ref n) = name {
10061                    self.write_keyword("CONSTRAINT");
10062                    self.write_space();
10063                    self.generate_identifier(n)?;
10064                    self.write_space();
10065                }
10066                self.write_keyword("DEFAULT");
10067                self.write_space();
10068                self.generate_expression(expression)?;
10069                self.write_space();
10070                self.write_keyword("FOR");
10071                self.write_space();
10072                self.generate_identifier(column)?;
10073            }
10074            TableConstraint::Index {
10075                name,
10076                columns,
10077                kind,
10078                modifiers,
10079                use_key_keyword,
10080                expression,
10081                index_type,
10082                granularity,
10083            } => {
10084                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
10085                if expression.is_some() {
10086                    self.write_keyword("INDEX");
10087                    if let Some(ref n) = name {
10088                        self.write_space();
10089                        self.generate_identifier(n)?;
10090                    }
10091                    if let Some(ref expr) = expression {
10092                        self.write_space();
10093                        self.generate_expression(expr)?;
10094                    }
10095                    if let Some(ref idx_type) = index_type {
10096                        self.write_space();
10097                        self.write_keyword("TYPE");
10098                        self.write_space();
10099                        self.generate_expression(idx_type)?;
10100                    }
10101                    if let Some(ref gran) = granularity {
10102                        self.write_space();
10103                        self.write_keyword("GRANULARITY");
10104                        self.write_space();
10105                        self.generate_expression(gran)?;
10106                    }
10107                } else {
10108                    // Standard INDEX syntax
10109                    // Determine the index keyword to use
10110                    // MySQL normalizes KEY to INDEX
10111                    use crate::dialects::DialectType;
10112                    let index_keyword = if *use_key_keyword
10113                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
10114                    {
10115                        "KEY"
10116                    } else {
10117                        "INDEX"
10118                    };
10119
10120                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
10121                    if let Some(ref k) = kind {
10122                        self.write_keyword(k);
10123                        // For UNIQUE, don't add INDEX/KEY keyword
10124                        if k != "UNIQUE" {
10125                            self.write_space();
10126                            self.write_keyword(index_keyword);
10127                        }
10128                    } else {
10129                        self.write_keyword(index_keyword);
10130                    }
10131
10132                    // Output USING before name if using_before_columns is true and there's no name
10133                    if modifiers.using_before_columns && name.is_none() {
10134                        if let Some(ref using) = modifiers.using {
10135                            self.write_space();
10136                            self.write_keyword("USING");
10137                            self.write_space();
10138                            self.write_keyword(using);
10139                        }
10140                    }
10141
10142                    // Output index name if present
10143                    if let Some(ref n) = name {
10144                        self.write_space();
10145                        self.generate_identifier(n)?;
10146                    }
10147
10148                    // Output USING after name but before columns if using_before_columns and there's a name
10149                    if modifiers.using_before_columns && name.is_some() {
10150                        if let Some(ref using) = modifiers.using {
10151                            self.write_space();
10152                            self.write_keyword("USING");
10153                            self.write_space();
10154                            self.write_keyword(using);
10155                        }
10156                    }
10157
10158                    // Output columns
10159                    self.write(" (");
10160                    for (i, col) in columns.iter().enumerate() {
10161                        if i > 0 {
10162                            self.write(", ");
10163                        }
10164                        self.generate_identifier(col)?;
10165                    }
10166                    self.write(")");
10167
10168                    // Output USING after columns if not using_before_columns
10169                    if !modifiers.using_before_columns {
10170                        if let Some(ref using) = modifiers.using {
10171                            self.write_space();
10172                            self.write_keyword("USING");
10173                            self.write_space();
10174                            self.write_keyword(using);
10175                        }
10176                    }
10177
10178                    // Output other constraint modifiers (but skip USING since we already handled it)
10179                    self.generate_constraint_modifiers_without_using(modifiers);
10180                }
10181            }
10182            TableConstraint::Projection { name, expression } => {
10183                // ClickHouse: PROJECTION name (SELECT ...)
10184                self.write_keyword("PROJECTION");
10185                self.write_space();
10186                self.generate_identifier(name)?;
10187                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
10188                    if let Expression::Raw(raw) = expression {
10189                        if raw
10190                            .sql
10191                            .trim_start()
10192                            .to_ascii_uppercase()
10193                            .starts_with("INDEX ")
10194                        {
10195                            self.write_space();
10196                            self.write(raw.sql.trim());
10197                            return Ok(());
10198                        }
10199                    }
10200                }
10201                self.write(" (");
10202                self.generate_expression(expression)?;
10203                self.write(")");
10204            }
10205            TableConstraint::Like { source, options } => {
10206                self.write_keyword("LIKE");
10207                self.write_space();
10208                self.generate_table(source)?;
10209                for (action, prop) in options {
10210                    self.write_space();
10211                    match action {
10212                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
10213                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
10214                    }
10215                    self.write_space();
10216                    self.write_keyword(prop);
10217                }
10218            }
10219            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
10220                self.write_keyword("PERIOD FOR SYSTEM_TIME");
10221                self.write(" (");
10222                self.generate_identifier(start_col)?;
10223                self.write(", ");
10224                self.generate_identifier(end_col)?;
10225                self.write(")");
10226            }
10227            TableConstraint::Exclude {
10228                name,
10229                using,
10230                elements,
10231                include_columns,
10232                where_clause,
10233                with_params,
10234                using_index_tablespace,
10235                modifiers: _,
10236            } => {
10237                if let Some(ref n) = name {
10238                    self.write_keyword("CONSTRAINT");
10239                    self.write_space();
10240                    self.generate_identifier(n)?;
10241                    self.write_space();
10242                }
10243                self.write_keyword("EXCLUDE");
10244                if let Some(ref method) = using {
10245                    self.write_space();
10246                    self.write_keyword("USING");
10247                    self.write_space();
10248                    self.write(method);
10249                    self.write("(");
10250                } else {
10251                    self.write(" (");
10252                }
10253                for (i, elem) in elements.iter().enumerate() {
10254                    if i > 0 {
10255                        self.write(", ");
10256                    }
10257                    self.write(&elem.expression);
10258                    self.write_space();
10259                    self.write_keyword("WITH");
10260                    self.write_space();
10261                    self.write(&elem.operator);
10262                }
10263                self.write(")");
10264                if !include_columns.is_empty() {
10265                    self.write_space();
10266                    self.write_keyword("INCLUDE");
10267                    self.write(" (");
10268                    for (i, col) in include_columns.iter().enumerate() {
10269                        if i > 0 {
10270                            self.write(", ");
10271                        }
10272                        self.generate_identifier(col)?;
10273                    }
10274                    self.write(")");
10275                }
10276                if !with_params.is_empty() {
10277                    self.write_space();
10278                    self.write_keyword("WITH");
10279                    self.write(" (");
10280                    for (i, (key, val)) in with_params.iter().enumerate() {
10281                        if i > 0 {
10282                            self.write(", ");
10283                        }
10284                        self.write(key);
10285                        self.write("=");
10286                        self.write(val);
10287                    }
10288                    self.write(")");
10289                }
10290                if let Some(ref tablespace) = using_index_tablespace {
10291                    self.write_space();
10292                    self.write_keyword("USING INDEX TABLESPACE");
10293                    self.write_space();
10294                    self.write(tablespace);
10295                }
10296                if let Some(ref where_expr) = where_clause {
10297                    self.write_space();
10298                    self.write_keyword("WHERE");
10299                    self.write(" (");
10300                    self.generate_expression(where_expr)?;
10301                    self.write(")");
10302                }
10303            }
10304            TableConstraint::Tags(tags) => {
10305                self.write_keyword("TAG");
10306                self.write(" (");
10307                for (i, expr) in tags.expressions.iter().enumerate() {
10308                    if i > 0 {
10309                        self.write(", ");
10310                    }
10311                    self.generate_expression(expr)?;
10312                }
10313                self.write(")");
10314            }
10315            TableConstraint::InitiallyDeferred { deferred } => {
10316                self.write_keyword("INITIALLY");
10317                self.write_space();
10318                if *deferred {
10319                    self.write_keyword("DEFERRED");
10320                } else {
10321                    self.write_keyword("IMMEDIATE");
10322                }
10323            }
10324        }
10325        Ok(())
10326    }
10327
10328    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
10329        // Output USING BTREE/HASH (MySQL) - comes first
10330        if let Some(using) = &modifiers.using {
10331            self.write_space();
10332            self.write_keyword("USING");
10333            self.write_space();
10334            self.write_keyword(using);
10335        }
10336        // Output ENFORCED/NOT ENFORCED
10337        if let Some(enforced) = modifiers.enforced {
10338            self.write_space();
10339            if enforced {
10340                self.write_keyword("ENFORCED");
10341            } else {
10342                self.write_keyword("NOT ENFORCED");
10343            }
10344        }
10345        // Output DEFERRABLE/NOT DEFERRABLE
10346        if let Some(deferrable) = modifiers.deferrable {
10347            self.write_space();
10348            if deferrable {
10349                self.write_keyword("DEFERRABLE");
10350            } else {
10351                self.write_keyword("NOT DEFERRABLE");
10352            }
10353        }
10354        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
10355        if let Some(initially_deferred) = modifiers.initially_deferred {
10356            self.write_space();
10357            if initially_deferred {
10358                self.write_keyword("INITIALLY DEFERRED");
10359            } else {
10360                self.write_keyword("INITIALLY IMMEDIATE");
10361            }
10362        }
10363        // Output NORELY
10364        if modifiers.norely {
10365            self.write_space();
10366            self.write_keyword("NORELY");
10367        }
10368        // Output RELY
10369        if modifiers.rely {
10370            self.write_space();
10371            self.write_keyword("RELY");
10372        }
10373        // Output NOT VALID (PostgreSQL)
10374        if modifiers.not_valid {
10375            self.write_space();
10376            self.write_keyword("NOT VALID");
10377        }
10378        // Output ON CONFLICT (SQLite)
10379        if let Some(on_conflict) = &modifiers.on_conflict {
10380            self.write_space();
10381            self.write_keyword("ON CONFLICT");
10382            self.write_space();
10383            self.write_keyword(on_conflict);
10384        }
10385        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
10386        if !modifiers.with_options.is_empty() {
10387            self.write_space();
10388            self.write_keyword("WITH");
10389            self.write(" (");
10390            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
10391                if i > 0 {
10392                    self.write(", ");
10393                }
10394                self.write(key);
10395                self.write("=");
10396                self.write(value);
10397            }
10398            self.write(")");
10399        }
10400        // Output TSQL ON filegroup
10401        if let Some(ref fg) = modifiers.on_filegroup {
10402            self.write_space();
10403            self.write_keyword("ON");
10404            self.write_space();
10405            let _ = self.generate_identifier(fg);
10406        }
10407    }
10408
10409    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
10410    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
10411        // Output ENFORCED/NOT ENFORCED
10412        if let Some(enforced) = modifiers.enforced {
10413            self.write_space();
10414            if enforced {
10415                self.write_keyword("ENFORCED");
10416            } else {
10417                self.write_keyword("NOT ENFORCED");
10418            }
10419        }
10420        // Output DEFERRABLE/NOT DEFERRABLE
10421        if let Some(deferrable) = modifiers.deferrable {
10422            self.write_space();
10423            if deferrable {
10424                self.write_keyword("DEFERRABLE");
10425            } else {
10426                self.write_keyword("NOT DEFERRABLE");
10427            }
10428        }
10429        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
10430        if let Some(initially_deferred) = modifiers.initially_deferred {
10431            self.write_space();
10432            if initially_deferred {
10433                self.write_keyword("INITIALLY DEFERRED");
10434            } else {
10435                self.write_keyword("INITIALLY IMMEDIATE");
10436            }
10437        }
10438        // Output NORELY
10439        if modifiers.norely {
10440            self.write_space();
10441            self.write_keyword("NORELY");
10442        }
10443        // Output RELY
10444        if modifiers.rely {
10445            self.write_space();
10446            self.write_keyword("RELY");
10447        }
10448        // Output NOT VALID (PostgreSQL)
10449        if modifiers.not_valid {
10450            self.write_space();
10451            self.write_keyword("NOT VALID");
10452        }
10453        // Output ON CONFLICT (SQLite)
10454        if let Some(on_conflict) = &modifiers.on_conflict {
10455            self.write_space();
10456            self.write_keyword("ON CONFLICT");
10457            self.write_space();
10458            self.write_keyword(on_conflict);
10459        }
10460        // Output MySQL index-specific modifiers
10461        self.generate_index_specific_modifiers(modifiers);
10462    }
10463
10464    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
10465    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
10466        if let Some(ref comment) = modifiers.comment {
10467            self.write_space();
10468            self.write_keyword("COMMENT");
10469            self.write(" '");
10470            self.write(comment);
10471            self.write("'");
10472        }
10473        if let Some(visible) = modifiers.visible {
10474            self.write_space();
10475            if visible {
10476                self.write_keyword("VISIBLE");
10477            } else {
10478                self.write_keyword("INVISIBLE");
10479            }
10480        }
10481        if let Some(ref attr) = modifiers.engine_attribute {
10482            self.write_space();
10483            self.write_keyword("ENGINE_ATTRIBUTE");
10484            self.write(" = '");
10485            self.write(attr);
10486            self.write("'");
10487        }
10488        if let Some(ref parser) = modifiers.with_parser {
10489            self.write_space();
10490            self.write_keyword("WITH PARSER");
10491            self.write_space();
10492            self.write(parser);
10493        }
10494    }
10495
10496    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
10497        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
10498        if !fk_ref.match_after_actions {
10499            if let Some(ref match_type) = fk_ref.match_type {
10500                self.write_space();
10501                self.write_keyword("MATCH");
10502                self.write_space();
10503                match match_type {
10504                    MatchType::Full => self.write_keyword("FULL"),
10505                    MatchType::Partial => self.write_keyword("PARTIAL"),
10506                    MatchType::Simple => self.write_keyword("SIMPLE"),
10507                }
10508            }
10509        }
10510
10511        // Output ON UPDATE and ON DELETE in the original order
10512        if fk_ref.on_update_first {
10513            if let Some(ref action) = fk_ref.on_update {
10514                self.write_space();
10515                self.write_keyword("ON UPDATE");
10516                self.write_space();
10517                self.generate_referential_action(action);
10518            }
10519            if let Some(ref action) = fk_ref.on_delete {
10520                self.write_space();
10521                self.write_keyword("ON DELETE");
10522                self.write_space();
10523                self.generate_referential_action(action);
10524            }
10525        } else {
10526            if let Some(ref action) = fk_ref.on_delete {
10527                self.write_space();
10528                self.write_keyword("ON DELETE");
10529                self.write_space();
10530                self.generate_referential_action(action);
10531            }
10532            if let Some(ref action) = fk_ref.on_update {
10533                self.write_space();
10534                self.write_keyword("ON UPDATE");
10535                self.write_space();
10536                self.generate_referential_action(action);
10537            }
10538        }
10539
10540        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
10541        if fk_ref.match_after_actions {
10542            if let Some(ref match_type) = fk_ref.match_type {
10543                self.write_space();
10544                self.write_keyword("MATCH");
10545                self.write_space();
10546                match match_type {
10547                    MatchType::Full => self.write_keyword("FULL"),
10548                    MatchType::Partial => self.write_keyword("PARTIAL"),
10549                    MatchType::Simple => self.write_keyword("SIMPLE"),
10550                }
10551            }
10552        }
10553
10554        // DEFERRABLE / NOT DEFERRABLE
10555        if let Some(deferrable) = fk_ref.deferrable {
10556            self.write_space();
10557            if deferrable {
10558                self.write_keyword("DEFERRABLE");
10559            } else {
10560                self.write_keyword("NOT DEFERRABLE");
10561            }
10562        }
10563
10564        Ok(())
10565    }
10566
10567    fn generate_referential_action(&mut self, action: &ReferentialAction) {
10568        match action {
10569            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
10570            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
10571            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
10572            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
10573            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
10574        }
10575    }
10576
10577    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
10578        // TSQL: IF NOT OBJECT_ID(...) IS NULL BEGIN DROP TABLE ...; END
10579        if let Some(ref object_id_args) = dt.object_id_args {
10580            if matches!(
10581                self.config.dialect,
10582                Some(crate::dialects::DialectType::TSQL)
10583                    | Some(crate::dialects::DialectType::Fabric)
10584            ) {
10585                self.write_keyword("IF NOT OBJECT_ID");
10586                self.write("(");
10587                self.write(object_id_args);
10588                self.write(")");
10589                self.write_space();
10590                self.write_keyword("IS NULL BEGIN DROP TABLE");
10591                self.write_space();
10592                for (i, table) in dt.names.iter().enumerate() {
10593                    if i > 0 {
10594                        self.write(", ");
10595                    }
10596                    self.generate_table(table)?;
10597                }
10598                self.write("; ");
10599                self.write_keyword("END");
10600                return Ok(());
10601            }
10602        }
10603
10604        // Athena: DROP TABLE uses Hive engine (backticks)
10605        let saved_athena_hive_context = self.athena_hive_context;
10606        if matches!(
10607            self.config.dialect,
10608            Some(crate::dialects::DialectType::Athena)
10609        ) {
10610            self.athena_hive_context = true;
10611        }
10612
10613        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
10614        for comment in &dt.leading_comments {
10615            self.write_formatted_comment(comment);
10616            self.write_space();
10617        }
10618        if dt.iceberg {
10619            self.write_keyword("DROP ICEBERG TABLE");
10620        } else {
10621            self.write_keyword("DROP TABLE");
10622        }
10623
10624        if dt.if_exists {
10625            self.write_space();
10626            self.write_keyword("IF EXISTS");
10627        }
10628
10629        self.write_space();
10630        for (i, table) in dt.names.iter().enumerate() {
10631            if i > 0 {
10632                self.write(", ");
10633            }
10634            self.generate_table(table)?;
10635        }
10636
10637        if dt.cascade_constraints {
10638            self.write_space();
10639            self.write_keyword("CASCADE CONSTRAINTS");
10640        } else if dt.cascade {
10641            self.write_space();
10642            self.write_keyword("CASCADE");
10643        }
10644
10645        if dt.restrict {
10646            self.write_space();
10647            self.write_keyword("RESTRICT");
10648        }
10649
10650        if dt.purge {
10651            self.write_space();
10652            self.write_keyword("PURGE");
10653        }
10654
10655        if dt.sync {
10656            self.write_space();
10657            self.write_keyword("SYNC");
10658        }
10659
10660        // Restore Athena Hive context
10661        self.athena_hive_context = saved_athena_hive_context;
10662
10663        Ok(())
10664    }
10665
10666    fn generate_undrop(&mut self, u: &Undrop) -> Result<()> {
10667        self.write_keyword("UNDROP");
10668        self.write_space();
10669        self.write_keyword(&u.kind);
10670        if u.if_exists {
10671            self.write_space();
10672            self.write_keyword("IF EXISTS");
10673        }
10674        self.write_space();
10675        self.generate_table(&u.name)?;
10676        if let Some(rename_to) = &u.rename_to {
10677            self.write_space();
10678            self.write_keyword("RENAME TO");
10679            self.write_space();
10680            self.generate_table(rename_to)?;
10681        }
10682        Ok(())
10683    }
10684
10685    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
10686        // Athena: ALTER TABLE uses Hive engine (backticks)
10687        let saved_athena_hive_context = self.athena_hive_context;
10688        if matches!(
10689            self.config.dialect,
10690            Some(crate::dialects::DialectType::Athena)
10691        ) {
10692            self.athena_hive_context = true;
10693        }
10694
10695        self.write_keyword("ALTER");
10696        // Write table modifier (e.g., ICEBERG) unless target is DuckDB
10697        if let Some(ref modifier) = at.table_modifier {
10698            if !matches!(
10699                self.config.dialect,
10700                Some(crate::dialects::DialectType::DuckDB)
10701            ) {
10702                self.write_space();
10703                self.write_keyword(modifier);
10704            }
10705        }
10706        self.write(" ");
10707        self.write_keyword("TABLE");
10708        if at.if_exists {
10709            self.write_space();
10710            self.write_keyword("IF EXISTS");
10711        }
10712        self.write_space();
10713        self.generate_table(&at.name)?;
10714
10715        // ClickHouse: ON CLUSTER clause
10716        if let Some(ref on_cluster) = at.on_cluster {
10717            self.write_space();
10718            self.generate_on_cluster(on_cluster)?;
10719        }
10720
10721        // Hive: PARTITION(key=value, ...) clause
10722        if let Some(ref partition) = at.partition {
10723            self.write_space();
10724            self.write_keyword("PARTITION");
10725            self.write("(");
10726            for (i, (key, value)) in partition.iter().enumerate() {
10727                if i > 0 {
10728                    self.write(", ");
10729                }
10730                self.generate_identifier(key)?;
10731                self.write(" = ");
10732                self.generate_expression(value)?;
10733            }
10734            self.write(")");
10735        }
10736
10737        // TSQL: WITH CHECK / WITH NOCHECK modifier
10738        if let Some(ref with_check) = at.with_check {
10739            self.write_space();
10740            self.write_keyword(with_check);
10741        }
10742
10743        if self.config.pretty {
10744            // In pretty mode, format actions with newlines and indentation
10745            self.write_newline();
10746            self.indent_level += 1;
10747            for (i, action) in at.actions.iter().enumerate() {
10748                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
10749                let is_continuation = i > 0
10750                    && matches!(
10751                        (&at.actions[i - 1], action),
10752                        (
10753                            AlterTableAction::AddColumn { .. },
10754                            AlterTableAction::AddColumn { .. }
10755                        ) | (
10756                            AlterTableAction::AddConstraint(_),
10757                            AlterTableAction::AddConstraint(_)
10758                        )
10759                    );
10760                if i > 0 {
10761                    self.write(",");
10762                    self.write_newline();
10763                }
10764                self.write_indent();
10765                self.generate_alter_action_with_continuation(action, is_continuation)?;
10766            }
10767            self.indent_level -= 1;
10768        } else {
10769            for (i, action) in at.actions.iter().enumerate() {
10770                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
10771                let is_continuation = i > 0
10772                    && matches!(
10773                        (&at.actions[i - 1], action),
10774                        (
10775                            AlterTableAction::AddColumn { .. },
10776                            AlterTableAction::AddColumn { .. }
10777                        ) | (
10778                            AlterTableAction::AddConstraint(_),
10779                            AlterTableAction::AddConstraint(_)
10780                        )
10781                    );
10782                if i > 0 {
10783                    self.write(",");
10784                }
10785                self.write_space();
10786                self.generate_alter_action_with_continuation(action, is_continuation)?;
10787            }
10788        }
10789
10790        // MySQL ALTER TABLE trailing options
10791        if let Some(ref algorithm) = at.algorithm {
10792            self.write(", ");
10793            self.write_keyword("ALGORITHM");
10794            self.write("=");
10795            self.write_keyword(algorithm);
10796        }
10797        if let Some(ref lock) = at.lock {
10798            self.write(", ");
10799            self.write_keyword("LOCK");
10800            self.write("=");
10801            self.write_keyword(lock);
10802        }
10803
10804        // Restore Athena Hive context
10805        self.athena_hive_context = saved_athena_hive_context;
10806
10807        Ok(())
10808    }
10809
10810    fn generate_alter_action_with_continuation(
10811        &mut self,
10812        action: &AlterTableAction,
10813        is_continuation: bool,
10814    ) -> Result<()> {
10815        match action {
10816            AlterTableAction::AddColumn {
10817                column,
10818                if_not_exists,
10819                position,
10820            } => {
10821                use crate::dialects::DialectType;
10822                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
10823                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
10824                // For other dialects, repeat ADD COLUMN for each
10825                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
10826                let is_tsql_like = matches!(
10827                    self.config.dialect,
10828                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
10829                );
10830                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
10831                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
10832
10833                if is_continuation && (is_snowflake || is_tsql_like) {
10834                    // Don't write ADD keyword for continuation in Snowflake/TSQL
10835                } else if is_snowflake {
10836                    self.write_keyword("ADD");
10837                    self.write_space();
10838                } else if is_athena {
10839                    // Athena uses ADD COLUMNS (col_def) syntax
10840                    self.write_keyword("ADD COLUMNS");
10841                    self.write(" (");
10842                } else if self.config.alter_table_include_column_keyword {
10843                    self.write_keyword("ADD COLUMN");
10844                    self.write_space();
10845                } else {
10846                    // Dialects like Oracle and TSQL don't use COLUMN keyword
10847                    self.write_keyword("ADD");
10848                    self.write_space();
10849                }
10850
10851                if *if_not_exists {
10852                    self.write_keyword("IF NOT EXISTS");
10853                    self.write_space();
10854                }
10855                self.generate_column_def(column)?;
10856
10857                // Close parenthesis for Athena
10858                if is_athena {
10859                    self.write(")");
10860                }
10861
10862                // Column position (FIRST or AFTER)
10863                if let Some(pos) = position {
10864                    self.write_space();
10865                    match pos {
10866                        ColumnPosition::First => self.write_keyword("FIRST"),
10867                        ColumnPosition::After(col_name) => {
10868                            self.write_keyword("AFTER");
10869                            self.write_space();
10870                            self.generate_identifier(col_name)?;
10871                        }
10872                    }
10873                }
10874            }
10875            AlterTableAction::DropColumn {
10876                name,
10877                if_exists,
10878                cascade,
10879            } => {
10880                self.write_keyword("DROP COLUMN");
10881                if *if_exists {
10882                    self.write_space();
10883                    self.write_keyword("IF EXISTS");
10884                }
10885                self.write_space();
10886                self.generate_identifier(name)?;
10887                if *cascade {
10888                    self.write_space();
10889                    self.write_keyword("CASCADE");
10890                }
10891            }
10892            AlterTableAction::DropColumns { names } => {
10893                self.write_keyword("DROP COLUMNS");
10894                self.write(" (");
10895                for (i, name) in names.iter().enumerate() {
10896                    if i > 0 {
10897                        self.write(", ");
10898                    }
10899                    self.generate_identifier(name)?;
10900                }
10901                self.write(")");
10902            }
10903            AlterTableAction::RenameColumn {
10904                old_name,
10905                new_name,
10906                if_exists,
10907            } => {
10908                self.write_keyword("RENAME COLUMN");
10909                if *if_exists {
10910                    self.write_space();
10911                    self.write_keyword("IF EXISTS");
10912                }
10913                self.write_space();
10914                self.generate_identifier(old_name)?;
10915                self.write_space();
10916                self.write_keyword("TO");
10917                self.write_space();
10918                self.generate_identifier(new_name)?;
10919            }
10920            AlterTableAction::AlterColumn {
10921                name,
10922                action,
10923                use_modify_keyword,
10924            } => {
10925                use crate::dialects::DialectType;
10926                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
10927                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
10928                let use_modify = *use_modify_keyword
10929                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
10930                        && matches!(action, AlterColumnAction::SetDataType { .. }));
10931                if use_modify {
10932                    self.write_keyword("MODIFY COLUMN");
10933                    self.write_space();
10934                    self.generate_identifier(name)?;
10935                    // For MODIFY COLUMN, output the type directly
10936                    if let AlterColumnAction::SetDataType {
10937                        data_type,
10938                        using: _,
10939                        collate,
10940                    } = action
10941                    {
10942                        self.write_space();
10943                        self.generate_data_type(data_type)?;
10944                        // Output COLLATE clause if present
10945                        if let Some(collate_name) = collate {
10946                            self.write_space();
10947                            self.write_keyword("COLLATE");
10948                            self.write_space();
10949                            // Output as single-quoted string
10950                            self.write(&format!("'{}'", collate_name));
10951                        }
10952                    } else {
10953                        self.write_space();
10954                        self.generate_alter_column_action(action)?;
10955                    }
10956                } else if matches!(self.config.dialect, Some(DialectType::Hive))
10957                    && matches!(action, AlterColumnAction::SetDataType { .. })
10958                {
10959                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
10960                    self.write_keyword("CHANGE COLUMN");
10961                    self.write_space();
10962                    self.generate_identifier(name)?;
10963                    self.write_space();
10964                    self.generate_identifier(name)?;
10965                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
10966                        self.write_space();
10967                        self.generate_data_type(data_type)?;
10968                    }
10969                } else {
10970                    self.write_keyword("ALTER COLUMN");
10971                    self.write_space();
10972                    self.generate_identifier(name)?;
10973                    self.write_space();
10974                    self.generate_alter_column_action(action)?;
10975                }
10976            }
10977            AlterTableAction::RenameTable(new_name) => {
10978                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
10979                let mysql_like = matches!(
10980                    self.config.dialect,
10981                    Some(DialectType::MySQL)
10982                        | Some(DialectType::Doris)
10983                        | Some(DialectType::StarRocks)
10984                        | Some(DialectType::SingleStore)
10985                );
10986                if mysql_like {
10987                    self.write_keyword("RENAME");
10988                } else {
10989                    self.write_keyword("RENAME TO");
10990                }
10991                self.write_space();
10992                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
10993                let rename_table_with_db = !matches!(
10994                    self.config.dialect,
10995                    Some(DialectType::Doris)
10996                        | Some(DialectType::DuckDB)
10997                        | Some(DialectType::BigQuery)
10998                        | Some(DialectType::PostgreSQL)
10999                );
11000                if !rename_table_with_db {
11001                    let mut stripped = new_name.clone();
11002                    stripped.schema = None;
11003                    stripped.catalog = None;
11004                    self.generate_table(&stripped)?;
11005                } else {
11006                    self.generate_table(new_name)?;
11007                }
11008            }
11009            AlterTableAction::AddConstraint(constraint) => {
11010                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
11011                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
11012                if !is_continuation {
11013                    self.write_keyword("ADD");
11014                    self.write_space();
11015                }
11016                self.generate_table_constraint(constraint)?;
11017            }
11018            AlterTableAction::DropConstraint { name, if_exists } => {
11019                self.write_keyword("DROP CONSTRAINT");
11020                if *if_exists {
11021                    self.write_space();
11022                    self.write_keyword("IF EXISTS");
11023                }
11024                self.write_space();
11025                self.generate_identifier(name)?;
11026            }
11027            AlterTableAction::DropForeignKey { name } => {
11028                self.write_keyword("DROP FOREIGN KEY");
11029                self.write_space();
11030                self.generate_identifier(name)?;
11031            }
11032            AlterTableAction::DropPartition {
11033                partitions,
11034                if_exists,
11035            } => {
11036                self.write_keyword("DROP");
11037                if *if_exists {
11038                    self.write_space();
11039                    self.write_keyword("IF EXISTS");
11040                }
11041                for (i, partition) in partitions.iter().enumerate() {
11042                    if i > 0 {
11043                        self.write(",");
11044                    }
11045                    self.write_space();
11046                    self.write_keyword("PARTITION");
11047                    // Check for special ClickHouse partition formats
11048                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
11049                        // ClickHouse: PARTITION <expression>
11050                        self.write_space();
11051                        self.generate_expression(&partition[0].1)?;
11052                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
11053                        // ClickHouse: PARTITION ALL
11054                        self.write_space();
11055                        self.write_keyword("ALL");
11056                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
11057                        // ClickHouse: PARTITION ID 'string'
11058                        self.write_space();
11059                        self.write_keyword("ID");
11060                        self.write_space();
11061                        self.generate_expression(&partition[0].1)?;
11062                    } else {
11063                        // Standard SQL: PARTITION(key=value, ...)
11064                        self.write("(");
11065                        for (j, (key, value)) in partition.iter().enumerate() {
11066                            if j > 0 {
11067                                self.write(", ");
11068                            }
11069                            self.generate_identifier(key)?;
11070                            self.write(" = ");
11071                            self.generate_expression(value)?;
11072                        }
11073                        self.write(")");
11074                    }
11075                }
11076            }
11077            AlterTableAction::Delete { where_clause } => {
11078                self.write_keyword("DELETE");
11079                self.write_space();
11080                self.write_keyword("WHERE");
11081                self.write_space();
11082                self.generate_expression(where_clause)?;
11083            }
11084            AlterTableAction::SwapWith(target) => {
11085                self.write_keyword("SWAP WITH");
11086                self.write_space();
11087                self.generate_table(target)?;
11088            }
11089            AlterTableAction::SetProperty { properties } => {
11090                use crate::dialects::DialectType;
11091                self.write_keyword("SET");
11092                // Trino/Presto use SET PROPERTIES syntax with spaces around =
11093                let is_trino_presto = matches!(
11094                    self.config.dialect,
11095                    Some(DialectType::Trino) | Some(DialectType::Presto)
11096                );
11097                if is_trino_presto {
11098                    self.write_space();
11099                    self.write_keyword("PROPERTIES");
11100                }
11101                let eq = if is_trino_presto { " = " } else { "=" };
11102                for (i, (key, value)) in properties.iter().enumerate() {
11103                    if i > 0 {
11104                        self.write(",");
11105                    }
11106                    self.write_space();
11107                    // Handle quoted property names for Trino
11108                    if key.contains(' ') {
11109                        self.generate_string_literal(key)?;
11110                    } else {
11111                        self.write(key);
11112                    }
11113                    self.write(eq);
11114                    self.generate_expression(value)?;
11115                }
11116            }
11117            AlterTableAction::UnsetProperty { properties } => {
11118                self.write_keyword("UNSET");
11119                for (i, name) in properties.iter().enumerate() {
11120                    if i > 0 {
11121                        self.write(",");
11122                    }
11123                    self.write_space();
11124                    self.write(name);
11125                }
11126            }
11127            AlterTableAction::ClusterBy { expressions } => {
11128                self.write_keyword("CLUSTER BY");
11129                self.write(" (");
11130                for (i, expr) in expressions.iter().enumerate() {
11131                    if i > 0 {
11132                        self.write(", ");
11133                    }
11134                    self.generate_expression(expr)?;
11135                }
11136                self.write(")");
11137            }
11138            AlterTableAction::SetTag { expressions } => {
11139                self.write_keyword("SET TAG");
11140                for (i, (key, value)) in expressions.iter().enumerate() {
11141                    if i > 0 {
11142                        self.write(",");
11143                    }
11144                    self.write_space();
11145                    self.write(key);
11146                    self.write(" = ");
11147                    self.generate_expression(value)?;
11148                }
11149            }
11150            AlterTableAction::UnsetTag { names } => {
11151                self.write_keyword("UNSET TAG");
11152                for (i, name) in names.iter().enumerate() {
11153                    if i > 0 {
11154                        self.write(",");
11155                    }
11156                    self.write_space();
11157                    self.write(name);
11158                }
11159            }
11160            AlterTableAction::SetOptions { expressions } => {
11161                self.write_keyword("SET");
11162                self.write(" (");
11163                for (i, expr) in expressions.iter().enumerate() {
11164                    if i > 0 {
11165                        self.write(", ");
11166                    }
11167                    self.generate_expression(expr)?;
11168                }
11169                self.write(")");
11170            }
11171            AlterTableAction::AlterIndex { name, visible } => {
11172                self.write_keyword("ALTER INDEX");
11173                self.write_space();
11174                self.generate_identifier(name)?;
11175                self.write_space();
11176                if *visible {
11177                    self.write_keyword("VISIBLE");
11178                } else {
11179                    self.write_keyword("INVISIBLE");
11180                }
11181            }
11182            AlterTableAction::SetAttribute { attribute } => {
11183                self.write_keyword("SET");
11184                self.write_space();
11185                self.write_keyword(attribute);
11186            }
11187            AlterTableAction::SetStageFileFormat { options } => {
11188                self.write_keyword("SET");
11189                self.write_space();
11190                self.write_keyword("STAGE_FILE_FORMAT");
11191                self.write(" = (");
11192                if let Some(opts) = options {
11193                    self.generate_space_separated_properties(opts)?;
11194                }
11195                self.write(")");
11196            }
11197            AlterTableAction::SetStageCopyOptions { options } => {
11198                self.write_keyword("SET");
11199                self.write_space();
11200                self.write_keyword("STAGE_COPY_OPTIONS");
11201                self.write(" = (");
11202                if let Some(opts) = options {
11203                    self.generate_space_separated_properties(opts)?;
11204                }
11205                self.write(")");
11206            }
11207            AlterTableAction::AddColumns { columns, cascade } => {
11208                // Oracle uses ADD (...) without COLUMNS keyword
11209                // Hive/Spark uses ADD COLUMNS (...)
11210                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
11211                if is_oracle {
11212                    self.write_keyword("ADD");
11213                } else {
11214                    self.write_keyword("ADD COLUMNS");
11215                }
11216                self.write(" (");
11217                for (i, col) in columns.iter().enumerate() {
11218                    if i > 0 {
11219                        self.write(", ");
11220                    }
11221                    self.generate_column_def(col)?;
11222                }
11223                self.write(")");
11224                if *cascade {
11225                    self.write_space();
11226                    self.write_keyword("CASCADE");
11227                }
11228            }
11229            AlterTableAction::ChangeColumn {
11230                old_name,
11231                new_name,
11232                data_type,
11233                comment,
11234                cascade,
11235            } => {
11236                use crate::dialects::DialectType;
11237                let is_spark = matches!(
11238                    self.config.dialect,
11239                    Some(DialectType::Spark) | Some(DialectType::Databricks)
11240                );
11241                let is_rename = old_name.name != new_name.name;
11242
11243                if is_spark {
11244                    if is_rename {
11245                        // Spark: RENAME COLUMN old TO new
11246                        self.write_keyword("RENAME COLUMN");
11247                        self.write_space();
11248                        self.generate_identifier(old_name)?;
11249                        self.write_space();
11250                        self.write_keyword("TO");
11251                        self.write_space();
11252                        self.generate_identifier(new_name)?;
11253                    } else if comment.is_some() {
11254                        // Spark: ALTER COLUMN old COMMENT 'comment'
11255                        self.write_keyword("ALTER COLUMN");
11256                        self.write_space();
11257                        self.generate_identifier(old_name)?;
11258                        self.write_space();
11259                        self.write_keyword("COMMENT");
11260                        self.write_space();
11261                        self.write("'");
11262                        self.write(comment.as_ref().unwrap());
11263                        self.write("'");
11264                    } else if data_type.is_some() {
11265                        // Spark: ALTER COLUMN old TYPE data_type
11266                        self.write_keyword("ALTER COLUMN");
11267                        self.write_space();
11268                        self.generate_identifier(old_name)?;
11269                        self.write_space();
11270                        self.write_keyword("TYPE");
11271                        self.write_space();
11272                        self.generate_data_type(data_type.as_ref().unwrap())?;
11273                    } else {
11274                        // Fallback to CHANGE COLUMN
11275                        self.write_keyword("CHANGE COLUMN");
11276                        self.write_space();
11277                        self.generate_identifier(old_name)?;
11278                        self.write_space();
11279                        self.generate_identifier(new_name)?;
11280                    }
11281                } else {
11282                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
11283                    if data_type.is_some() {
11284                        self.write_keyword("CHANGE COLUMN");
11285                    } else {
11286                        self.write_keyword("CHANGE");
11287                    }
11288                    self.write_space();
11289                    self.generate_identifier(old_name)?;
11290                    self.write_space();
11291                    self.generate_identifier(new_name)?;
11292                    if let Some(ref dt) = data_type {
11293                        self.write_space();
11294                        self.generate_data_type(dt)?;
11295                    }
11296                    if let Some(ref c) = comment {
11297                        self.write_space();
11298                        self.write_keyword("COMMENT");
11299                        self.write_space();
11300                        self.write("'");
11301                        self.write(c);
11302                        self.write("'");
11303                    }
11304                    if *cascade {
11305                        self.write_space();
11306                        self.write_keyword("CASCADE");
11307                    }
11308                }
11309            }
11310            AlterTableAction::AddPartition {
11311                partition,
11312                if_not_exists,
11313                location,
11314            } => {
11315                self.write_keyword("ADD");
11316                self.write_space();
11317                if *if_not_exists {
11318                    self.write_keyword("IF NOT EXISTS");
11319                    self.write_space();
11320                }
11321                self.generate_expression(partition)?;
11322                if let Some(ref loc) = location {
11323                    self.write_space();
11324                    self.write_keyword("LOCATION");
11325                    self.write_space();
11326                    self.generate_expression(loc)?;
11327                }
11328            }
11329            AlterTableAction::AlterSortKey {
11330                this,
11331                expressions,
11332                compound,
11333            } => {
11334                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
11335                self.write_keyword("ALTER");
11336                if *compound {
11337                    self.write_space();
11338                    self.write_keyword("COMPOUND");
11339                }
11340                self.write_space();
11341                self.write_keyword("SORTKEY");
11342                self.write_space();
11343                if let Some(style) = this {
11344                    self.write_keyword(style);
11345                } else if !expressions.is_empty() {
11346                    self.write("(");
11347                    for (i, expr) in expressions.iter().enumerate() {
11348                        if i > 0 {
11349                            self.write(", ");
11350                        }
11351                        self.generate_expression(expr)?;
11352                    }
11353                    self.write(")");
11354                }
11355            }
11356            AlterTableAction::AlterDistStyle { style, distkey } => {
11357                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
11358                self.write_keyword("ALTER");
11359                self.write_space();
11360                self.write_keyword("DISTSTYLE");
11361                self.write_space();
11362                self.write_keyword(style);
11363                if let Some(col) = distkey {
11364                    self.write_space();
11365                    self.write_keyword("DISTKEY");
11366                    self.write_space();
11367                    self.generate_identifier(col)?;
11368                }
11369            }
11370            AlterTableAction::SetTableProperties { properties } => {
11371                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
11372                self.write_keyword("SET TABLE PROPERTIES");
11373                self.write(" (");
11374                for (i, (key, value)) in properties.iter().enumerate() {
11375                    if i > 0 {
11376                        self.write(", ");
11377                    }
11378                    self.generate_expression(key)?;
11379                    self.write(" = ");
11380                    self.generate_expression(value)?;
11381                }
11382                self.write(")");
11383            }
11384            AlterTableAction::SetLocation { location } => {
11385                // Redshift: SET LOCATION 's3://bucket/folder/'
11386                self.write_keyword("SET LOCATION");
11387                self.write_space();
11388                self.write("'");
11389                self.write(location);
11390                self.write("'");
11391            }
11392            AlterTableAction::SetFileFormat { format } => {
11393                // Redshift: SET FILE FORMAT AVRO
11394                self.write_keyword("SET FILE FORMAT");
11395                self.write_space();
11396                self.write_keyword(format);
11397            }
11398            AlterTableAction::ReplacePartition { partition, source } => {
11399                // ClickHouse: REPLACE PARTITION expr FROM source
11400                self.write_keyword("REPLACE PARTITION");
11401                self.write_space();
11402                self.generate_expression(partition)?;
11403                if let Some(src) = source {
11404                    self.write_space();
11405                    self.write_keyword("FROM");
11406                    self.write_space();
11407                    self.generate_expression(src)?;
11408                }
11409            }
11410            AlterTableAction::Raw { sql } => {
11411                self.write(sql);
11412            }
11413        }
11414        Ok(())
11415    }
11416
11417    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
11418        match action {
11419            AlterColumnAction::SetDataType {
11420                data_type,
11421                using,
11422                collate,
11423            } => {
11424                use crate::dialects::DialectType;
11425                // Dialect-specific type change syntax:
11426                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
11427                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
11428                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
11429                let is_no_prefix = matches!(
11430                    self.config.dialect,
11431                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
11432                );
11433                let is_type_only = matches!(
11434                    self.config.dialect,
11435                    Some(DialectType::Redshift)
11436                        | Some(DialectType::Spark)
11437                        | Some(DialectType::Databricks)
11438                );
11439                if is_type_only {
11440                    self.write_keyword("TYPE");
11441                    self.write_space();
11442                } else if !is_no_prefix {
11443                    self.write_keyword("SET DATA TYPE");
11444                    self.write_space();
11445                }
11446                self.generate_data_type(data_type)?;
11447                if let Some(ref collation) = collate {
11448                    self.write_space();
11449                    self.write_keyword("COLLATE");
11450                    self.write_space();
11451                    self.write(collation);
11452                }
11453                if let Some(ref using_expr) = using {
11454                    self.write_space();
11455                    self.write_keyword("USING");
11456                    self.write_space();
11457                    self.generate_expression(using_expr)?;
11458                }
11459            }
11460            AlterColumnAction::SetDefault(expr) => {
11461                self.write_keyword("SET DEFAULT");
11462                self.write_space();
11463                self.generate_expression(expr)?;
11464            }
11465            AlterColumnAction::DropDefault => {
11466                self.write_keyword("DROP DEFAULT");
11467            }
11468            AlterColumnAction::SetNotNull => {
11469                self.write_keyword("SET NOT NULL");
11470            }
11471            AlterColumnAction::DropNotNull => {
11472                self.write_keyword("DROP NOT NULL");
11473            }
11474            AlterColumnAction::Comment(comment) => {
11475                self.write_keyword("COMMENT");
11476                self.write_space();
11477                self.generate_string_literal(comment)?;
11478            }
11479            AlterColumnAction::SetVisible => {
11480                self.write_keyword("SET VISIBLE");
11481            }
11482            AlterColumnAction::SetInvisible => {
11483                self.write_keyword("SET INVISIBLE");
11484            }
11485        }
11486        Ok(())
11487    }
11488
11489    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
11490        self.write_keyword("CREATE");
11491
11492        if ci.unique {
11493            self.write_space();
11494            self.write_keyword("UNIQUE");
11495        }
11496
11497        // TSQL CLUSTERED/NONCLUSTERED modifier
11498        if let Some(ref clustered) = ci.clustered {
11499            self.write_space();
11500            self.write_keyword(clustered);
11501        }
11502
11503        self.write_space();
11504        self.write_keyword("INDEX");
11505
11506        // PostgreSQL CONCURRENTLY modifier
11507        if ci.concurrently {
11508            self.write_space();
11509            self.write_keyword("CONCURRENTLY");
11510        }
11511
11512        if ci.if_not_exists {
11513            self.write_space();
11514            self.write_keyword("IF NOT EXISTS");
11515        }
11516
11517        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
11518        if !ci.name.name.is_empty() {
11519            self.write_space();
11520            self.generate_identifier(&ci.name)?;
11521        }
11522        self.write_space();
11523        self.write_keyword("ON");
11524        // Hive uses ON TABLE
11525        if matches!(self.config.dialect, Some(DialectType::Hive)) {
11526            self.write_space();
11527            self.write_keyword("TABLE");
11528        }
11529        self.write_space();
11530        self.generate_table(&ci.table)?;
11531
11532        // Column list (optional for COLUMNSTORE indexes)
11533        // Standard SQL convention: ON t(a) without space before paren
11534        if !ci.columns.is_empty() || ci.using.is_some() {
11535            let space_before_paren = false;
11536
11537            if let Some(ref using) = ci.using {
11538                self.write_space();
11539                self.write_keyword("USING");
11540                self.write_space();
11541                self.write(using);
11542                if space_before_paren {
11543                    self.write(" (");
11544                } else {
11545                    self.write("(");
11546                }
11547            } else {
11548                if space_before_paren {
11549                    self.write(" (");
11550                } else {
11551                    self.write("(");
11552                }
11553            }
11554            for (i, col) in ci.columns.iter().enumerate() {
11555                if i > 0 {
11556                    self.write(", ");
11557                }
11558                self.generate_identifier(&col.column)?;
11559                if let Some(ref opclass) = col.opclass {
11560                    self.write_space();
11561                    self.write(opclass);
11562                }
11563                if col.desc {
11564                    self.write_space();
11565                    self.write_keyword("DESC");
11566                } else if col.asc {
11567                    self.write_space();
11568                    self.write_keyword("ASC");
11569                }
11570                if let Some(nulls_first) = col.nulls_first {
11571                    self.write_space();
11572                    self.write_keyword("NULLS");
11573                    self.write_space();
11574                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
11575                }
11576            }
11577            self.write(")");
11578        }
11579
11580        // PostgreSQL INCLUDE (col1, col2) clause
11581        if !ci.include_columns.is_empty() {
11582            self.write_space();
11583            self.write_keyword("INCLUDE");
11584            self.write(" (");
11585            for (i, col) in ci.include_columns.iter().enumerate() {
11586                if i > 0 {
11587                    self.write(", ");
11588                }
11589                self.generate_identifier(col)?;
11590            }
11591            self.write(")");
11592        }
11593
11594        // TSQL: WITH (option=value, ...) clause
11595        if !ci.with_options.is_empty() {
11596            self.write_space();
11597            self.write_keyword("WITH");
11598            self.write(" (");
11599            for (i, (key, value)) in ci.with_options.iter().enumerate() {
11600                if i > 0 {
11601                    self.write(", ");
11602                }
11603                self.write(key);
11604                self.write("=");
11605                self.write(value);
11606            }
11607            self.write(")");
11608        }
11609
11610        // PostgreSQL WHERE clause for partial indexes
11611        if let Some(ref where_clause) = ci.where_clause {
11612            self.write_space();
11613            self.write_keyword("WHERE");
11614            self.write_space();
11615            self.generate_expression(where_clause)?;
11616        }
11617
11618        // TSQL: ON filegroup or partition scheme clause
11619        if let Some(ref on_fg) = ci.on_filegroup {
11620            self.write_space();
11621            self.write_keyword("ON");
11622            self.write_space();
11623            self.write(on_fg);
11624        }
11625
11626        Ok(())
11627    }
11628
11629    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
11630        self.write_keyword("DROP INDEX");
11631
11632        if di.concurrently {
11633            self.write_space();
11634            self.write_keyword("CONCURRENTLY");
11635        }
11636
11637        if di.if_exists {
11638            self.write_space();
11639            self.write_keyword("IF EXISTS");
11640        }
11641
11642        self.write_space();
11643        self.generate_table(&di.name)?;
11644
11645        if let Some(ref table) = di.table {
11646            self.write_space();
11647            self.write_keyword("ON");
11648            self.write_space();
11649            self.generate_table(table)?;
11650        }
11651
11652        Ok(())
11653    }
11654
11655    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
11656        self.write_keyword("CREATE");
11657
11658        // MySQL: ALGORITHM=...
11659        if let Some(ref algorithm) = cv.algorithm {
11660            self.write_space();
11661            self.write_keyword("ALGORITHM");
11662            self.write("=");
11663            self.write_keyword(algorithm);
11664        }
11665
11666        // MySQL: DEFINER=...
11667        if let Some(ref definer) = cv.definer {
11668            self.write_space();
11669            self.write_keyword("DEFINER");
11670            self.write("=");
11671            self.write(definer);
11672        }
11673
11674        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword, unless it appeared after view name)
11675        if cv.security_sql_style && !cv.security_after_name {
11676            if let Some(ref security) = cv.security {
11677                self.write_space();
11678                self.write_keyword("SQL SECURITY");
11679                self.write_space();
11680                match security {
11681                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
11682                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
11683                    FunctionSecurity::None => self.write_keyword("NONE"),
11684                }
11685            }
11686        }
11687
11688        if cv.or_alter {
11689            self.write_space();
11690            self.write_keyword("OR ALTER");
11691        } else if cv.or_replace {
11692            self.write_space();
11693            self.write_keyword("OR REPLACE");
11694        }
11695
11696        if cv.temporary {
11697            self.write_space();
11698            self.write_keyword("TEMPORARY");
11699        }
11700
11701        if cv.materialized {
11702            self.write_space();
11703            self.write_keyword("MATERIALIZED");
11704        }
11705
11706        // Snowflake: SECURE VIEW
11707        if cv.secure {
11708            self.write_space();
11709            self.write_keyword("SECURE");
11710        }
11711
11712        self.write_space();
11713        self.write_keyword("VIEW");
11714
11715        if cv.if_not_exists {
11716            self.write_space();
11717            self.write_keyword("IF NOT EXISTS");
11718        }
11719
11720        self.write_space();
11721        self.generate_table(&cv.name)?;
11722
11723        // ClickHouse: ON CLUSTER clause
11724        if let Some(ref on_cluster) = cv.on_cluster {
11725            self.write_space();
11726            self.generate_on_cluster(on_cluster)?;
11727        }
11728
11729        // ClickHouse: TO destination_table
11730        if let Some(ref to_table) = cv.to_table {
11731            self.write_space();
11732            self.write_keyword("TO");
11733            self.write_space();
11734            self.generate_table(to_table)?;
11735        }
11736
11737        // For regular VIEW: columns come before COPY GRANTS
11738        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
11739        if !cv.materialized {
11740            // Regular VIEW: columns first
11741            if let Some(ref schema) = cv.schema {
11742                self.write(" (");
11743                for (i, expr) in schema.expressions.iter().enumerate() {
11744                    if i > 0 {
11745                        self.write(", ");
11746                    }
11747                    self.generate_expression(expr)?;
11748                }
11749                self.write(")");
11750            } else if !cv.columns.is_empty() {
11751                self.write(" (");
11752                for (i, col) in cv.columns.iter().enumerate() {
11753                    if i > 0 {
11754                        self.write(", ");
11755                    }
11756                    self.generate_identifier(&col.name)?;
11757                    // BigQuery: OPTIONS (key=value, ...) on view column
11758                    if !col.options.is_empty() {
11759                        self.write_space();
11760                        self.generate_options_clause(&col.options)?;
11761                    }
11762                    if let Some(ref comment) = col.comment {
11763                        self.write_space();
11764                        self.write_keyword("COMMENT");
11765                        self.write_space();
11766                        self.generate_string_literal(comment)?;
11767                    }
11768                }
11769                self.write(")");
11770            }
11771
11772            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
11773            // Also handles SQL SECURITY after view name (security_after_name)
11774            if !cv.security_sql_style || cv.security_after_name {
11775                if let Some(ref security) = cv.security {
11776                    self.write_space();
11777                    if cv.security_sql_style {
11778                        self.write_keyword("SQL SECURITY");
11779                    } else {
11780                        self.write_keyword("SECURITY");
11781                    }
11782                    self.write_space();
11783                    match security {
11784                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
11785                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
11786                        FunctionSecurity::None => self.write_keyword("NONE"),
11787                    }
11788                }
11789            }
11790
11791            // Snowflake: COPY GRANTS
11792            if cv.copy_grants {
11793                self.write_space();
11794                self.write_keyword("COPY GRANTS");
11795            }
11796        } else {
11797            // MATERIALIZED VIEW: COPY GRANTS first
11798            if cv.copy_grants {
11799                self.write_space();
11800                self.write_keyword("COPY GRANTS");
11801            }
11802
11803            // Doris: If we have a schema (typed columns), generate that instead
11804            if let Some(ref schema) = cv.schema {
11805                self.write(" (");
11806                for (i, expr) in schema.expressions.iter().enumerate() {
11807                    if i > 0 {
11808                        self.write(", ");
11809                    }
11810                    self.generate_expression(expr)?;
11811                }
11812                self.write(")");
11813            } else if !cv.columns.is_empty() {
11814                // Then columns (simple column names without types)
11815                self.write(" (");
11816                for (i, col) in cv.columns.iter().enumerate() {
11817                    if i > 0 {
11818                        self.write(", ");
11819                    }
11820                    self.generate_identifier(&col.name)?;
11821                    // BigQuery: OPTIONS (key=value, ...) on view column
11822                    if !col.options.is_empty() {
11823                        self.write_space();
11824                        self.generate_options_clause(&col.options)?;
11825                    }
11826                    if let Some(ref comment) = col.comment {
11827                        self.write_space();
11828                        self.write_keyword("COMMENT");
11829                        self.write_space();
11830                        self.generate_string_literal(comment)?;
11831                    }
11832                }
11833                self.write(")");
11834            }
11835
11836            // Doris: KEY (columns) for materialized views
11837            if let Some(ref unique_key) = cv.unique_key {
11838                self.write_space();
11839                self.write_keyword("KEY");
11840                self.write(" (");
11841                for (i, expr) in unique_key.expressions.iter().enumerate() {
11842                    if i > 0 {
11843                        self.write(", ");
11844                    }
11845                    self.generate_expression(expr)?;
11846                }
11847                self.write(")");
11848            }
11849        }
11850
11851        if let Some(ref row_access_policy) = cv.row_access_policy {
11852            self.write_space();
11853            self.write_keyword("WITH");
11854            self.write_space();
11855            self.write(row_access_policy);
11856        }
11857
11858        // Snowflake: COMMENT = 'text'
11859        if let Some(ref comment) = cv.comment {
11860            self.write_space();
11861            self.write_keyword("COMMENT");
11862            self.write("=");
11863            self.generate_string_literal(comment)?;
11864        }
11865
11866        // Snowflake: TAG (name='value', ...)
11867        if !cv.tags.is_empty() {
11868            self.write_space();
11869            self.write_keyword("TAG");
11870            self.write(" (");
11871            for (i, (name, value)) in cv.tags.iter().enumerate() {
11872                if i > 0 {
11873                    self.write(", ");
11874                }
11875                self.write(name);
11876                self.write("='");
11877                self.write(value);
11878                self.write("'");
11879            }
11880            self.write(")");
11881        }
11882
11883        // BigQuery: OPTIONS (key=value, ...)
11884        if !cv.options.is_empty() {
11885            self.write_space();
11886            self.generate_options_clause(&cv.options)?;
11887        }
11888
11889        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
11890        if let Some(ref build) = cv.build {
11891            self.write_space();
11892            self.write_keyword("BUILD");
11893            self.write_space();
11894            self.write_keyword(build);
11895        }
11896
11897        // Doris: REFRESH clause for materialized views
11898        if let Some(ref refresh) = cv.refresh {
11899            self.write_space();
11900            self.generate_refresh_trigger_property(refresh)?;
11901        }
11902
11903        // Redshift: AUTO REFRESH YES|NO for materialized views
11904        if let Some(auto_refresh) = cv.auto_refresh {
11905            self.write_space();
11906            self.write_keyword("AUTO REFRESH");
11907            self.write_space();
11908            if auto_refresh {
11909                self.write_keyword("YES");
11910            } else {
11911                self.write_keyword("NO");
11912            }
11913        }
11914
11915        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
11916        for prop in &cv.table_properties {
11917            self.write_space();
11918            self.generate_expression(prop)?;
11919        }
11920
11921        // ClickHouse: POPULATE / EMPTY before AS
11922        if let Some(ref population) = cv.clickhouse_population {
11923            self.write_space();
11924            self.write_keyword(population);
11925        }
11926
11927        // Only output AS clause if there's a real query (not just NULL placeholder)
11928        if !matches!(&cv.query, Expression::Null(_)) {
11929            self.write_space();
11930            self.write_keyword("AS");
11931            self.write_space();
11932
11933            // Teradata: LOCKING clause (between AS and query)
11934            if let Some(ref mode) = cv.locking_mode {
11935                self.write_keyword("LOCKING");
11936                self.write_space();
11937                self.write_keyword(mode);
11938                if let Some(ref access) = cv.locking_access {
11939                    self.write_space();
11940                    self.write_keyword("FOR");
11941                    self.write_space();
11942                    self.write_keyword(access);
11943                }
11944                self.write_space();
11945            }
11946
11947            if cv.query_parenthesized {
11948                self.write("(");
11949            }
11950            self.generate_expression(&cv.query)?;
11951            if cv.query_parenthesized {
11952                self.write(")");
11953            }
11954        }
11955
11956        // Redshift: WITH NO SCHEMA BINDING (after query)
11957        if cv.no_schema_binding {
11958            self.write_space();
11959            self.write_keyword("WITH NO SCHEMA BINDING");
11960        }
11961
11962        Ok(())
11963    }
11964
11965    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
11966        self.write_keyword("DROP");
11967
11968        if dv.materialized {
11969            self.write_space();
11970            self.write_keyword("MATERIALIZED");
11971        }
11972
11973        self.write_space();
11974        self.write_keyword("VIEW");
11975
11976        if dv.if_exists {
11977            self.write_space();
11978            self.write_keyword("IF EXISTS");
11979        }
11980
11981        self.write_space();
11982        self.generate_table(&dv.name)?;
11983
11984        Ok(())
11985    }
11986
11987    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
11988        match tr.target {
11989            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
11990            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
11991        }
11992        if tr.if_exists {
11993            self.write_space();
11994            self.write_keyword("IF EXISTS");
11995        }
11996        self.write_space();
11997        self.generate_table(&tr.table)?;
11998
11999        // ClickHouse: ON CLUSTER clause
12000        if let Some(ref on_cluster) = tr.on_cluster {
12001            self.write_space();
12002            self.generate_on_cluster(on_cluster)?;
12003        }
12004
12005        // Check if first table has a * (multi-table with star)
12006        if !tr.extra_tables.is_empty() {
12007            // Check if the first entry matches the main table (star case)
12008            let skip_first = if let Some(first) = tr.extra_tables.first() {
12009                first.table.name == tr.table.name && first.star
12010            } else {
12011                false
12012            };
12013
12014            // PostgreSQL normalizes away the * suffix (it's the default behavior)
12015            let strip_star = matches!(
12016                self.config.dialect,
12017                Some(crate::dialects::DialectType::PostgreSQL)
12018                    | Some(crate::dialects::DialectType::Redshift)
12019            );
12020            if skip_first && !strip_star {
12021                self.write("*");
12022            }
12023
12024            // Generate additional tables
12025            for (i, entry) in tr.extra_tables.iter().enumerate() {
12026                if i == 0 && skip_first {
12027                    continue; // Already handled the star for first table
12028                }
12029                self.write(", ");
12030                self.generate_table(&entry.table)?;
12031                if entry.star && !strip_star {
12032                    self.write("*");
12033                }
12034            }
12035        }
12036
12037        // RESTART/CONTINUE IDENTITY
12038        if let Some(identity) = &tr.identity {
12039            self.write_space();
12040            match identity {
12041                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
12042                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
12043            }
12044        }
12045
12046        if tr.cascade {
12047            self.write_space();
12048            self.write_keyword("CASCADE");
12049        }
12050
12051        if tr.restrict {
12052            self.write_space();
12053            self.write_keyword("RESTRICT");
12054        }
12055
12056        // Output Hive PARTITION clause
12057        if let Some(ref partition) = tr.partition {
12058            self.write_space();
12059            self.generate_expression(partition)?;
12060        }
12061
12062        Ok(())
12063    }
12064
12065    fn generate_use(&mut self, u: &Use) -> Result<()> {
12066        // Teradata uses "DATABASE <name>" instead of "USE <name>"
12067        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
12068            self.write_keyword("DATABASE");
12069            self.write_space();
12070            self.generate_identifier(&u.this)?;
12071            return Ok(());
12072        }
12073
12074        self.write_keyword("USE");
12075
12076        if let Some(kind) = &u.kind {
12077            self.write_space();
12078            match kind {
12079                UseKind::Database => self.write_keyword("DATABASE"),
12080                UseKind::Schema => self.write_keyword("SCHEMA"),
12081                UseKind::Role => self.write_keyword("ROLE"),
12082                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
12083                UseKind::Catalog => self.write_keyword("CATALOG"),
12084                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
12085            }
12086        }
12087
12088        self.write_space();
12089        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
12090        // without quoting, since these are keywords not identifiers
12091        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
12092            self.write(&u.this.name);
12093        } else {
12094            self.generate_identifier(&u.this)?;
12095        }
12096        Ok(())
12097    }
12098
12099    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
12100        self.write_keyword("CACHE");
12101        if c.lazy {
12102            self.write_space();
12103            self.write_keyword("LAZY");
12104        }
12105        self.write_space();
12106        self.write_keyword("TABLE");
12107        self.write_space();
12108        self.generate_identifier(&c.table)?;
12109
12110        // OPTIONS clause
12111        if !c.options.is_empty() {
12112            self.write_space();
12113            self.write_keyword("OPTIONS");
12114            self.write("(");
12115            for (i, (key, value)) in c.options.iter().enumerate() {
12116                if i > 0 {
12117                    self.write(", ");
12118                }
12119                self.generate_expression(key)?;
12120                self.write(" = ");
12121                self.generate_expression(value)?;
12122            }
12123            self.write(")");
12124        }
12125
12126        // AS query
12127        if let Some(query) = &c.query {
12128            self.write_space();
12129            self.write_keyword("AS");
12130            self.write_space();
12131            self.generate_expression(query)?;
12132        }
12133
12134        Ok(())
12135    }
12136
12137    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
12138        self.write_keyword("UNCACHE TABLE");
12139        if u.if_exists {
12140            self.write_space();
12141            self.write_keyword("IF EXISTS");
12142        }
12143        self.write_space();
12144        self.generate_identifier(&u.table)?;
12145        Ok(())
12146    }
12147
12148    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
12149        self.write_keyword("LOAD DATA");
12150        if l.local {
12151            self.write_space();
12152            self.write_keyword("LOCAL");
12153        }
12154        self.write_space();
12155        self.write_keyword("INPATH");
12156        self.write_space();
12157        self.write("'");
12158        self.write(&l.inpath);
12159        self.write("'");
12160
12161        if l.overwrite {
12162            self.write_space();
12163            self.write_keyword("OVERWRITE");
12164        }
12165
12166        self.write_space();
12167        self.write_keyword("INTO TABLE");
12168        self.write_space();
12169        self.generate_expression(&l.table)?;
12170
12171        // PARTITION clause
12172        if !l.partition.is_empty() {
12173            self.write_space();
12174            self.write_keyword("PARTITION");
12175            self.write("(");
12176            for (i, (col, val)) in l.partition.iter().enumerate() {
12177                if i > 0 {
12178                    self.write(", ");
12179                }
12180                self.generate_identifier(col)?;
12181                self.write(" = ");
12182                self.generate_expression(val)?;
12183            }
12184            self.write(")");
12185        }
12186
12187        // INPUTFORMAT clause
12188        if let Some(fmt) = &l.input_format {
12189            self.write_space();
12190            self.write_keyword("INPUTFORMAT");
12191            self.write_space();
12192            self.write("'");
12193            self.write(fmt);
12194            self.write("'");
12195        }
12196
12197        // SERDE clause
12198        if let Some(serde) = &l.serde {
12199            self.write_space();
12200            self.write_keyword("SERDE");
12201            self.write_space();
12202            self.write("'");
12203            self.write(serde);
12204            self.write("'");
12205        }
12206
12207        Ok(())
12208    }
12209
12210    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
12211        self.write_keyword("PRAGMA");
12212        self.write_space();
12213
12214        // Schema prefix if present
12215        if let Some(schema) = &p.schema {
12216            self.generate_identifier(schema)?;
12217            self.write(".");
12218        }
12219
12220        // Pragma name
12221        self.generate_identifier(&p.name)?;
12222
12223        // Value assignment or function call
12224        if p.use_assignment_syntax {
12225            self.write(" = ");
12226            if let Some(value) = &p.value {
12227                self.generate_expression(value)?;
12228            } else if let Some(arg) = p.args.first() {
12229                self.generate_expression(arg)?;
12230            }
12231        } else if !p.args.is_empty() {
12232            self.write("(");
12233            for (i, arg) in p.args.iter().enumerate() {
12234                if i > 0 {
12235                    self.write(", ");
12236                }
12237                self.generate_expression(arg)?;
12238            }
12239            self.write(")");
12240        }
12241
12242        Ok(())
12243    }
12244
12245    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
12246        self.write_keyword("GRANT");
12247        self.write_space();
12248
12249        // Privileges (with optional column lists)
12250        for (i, privilege) in g.privileges.iter().enumerate() {
12251            if i > 0 {
12252                self.write(", ");
12253            }
12254            self.write_keyword(&privilege.name);
12255            // Output column list if present: SELECT(col1, col2)
12256            if !privilege.columns.is_empty() {
12257                self.write("(");
12258                for (j, col) in privilege.columns.iter().enumerate() {
12259                    if j > 0 {
12260                        self.write(", ");
12261                    }
12262                    self.write(col);
12263                }
12264                self.write(")");
12265            }
12266        }
12267
12268        self.write_space();
12269        self.write_keyword("ON");
12270        self.write_space();
12271
12272        // Object kind (TABLE, SCHEMA, etc.)
12273        if let Some(kind) = &g.kind {
12274            self.write_keyword(kind);
12275            self.write_space();
12276        }
12277
12278        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
12279        {
12280            use crate::dialects::DialectType;
12281            let should_upper = matches!(
12282                self.config.dialect,
12283                Some(DialectType::PostgreSQL)
12284                    | Some(DialectType::CockroachDB)
12285                    | Some(DialectType::Materialize)
12286                    | Some(DialectType::RisingWave)
12287            ) && (g.kind.as_deref() == Some("FUNCTION")
12288                || g.kind.as_deref() == Some("PROCEDURE"));
12289            if should_upper {
12290                use crate::expressions::Identifier;
12291                let upper_id = Identifier {
12292                    name: g.securable.name.to_ascii_uppercase(),
12293                    quoted: g.securable.quoted,
12294                    ..g.securable.clone()
12295                };
12296                self.generate_identifier(&upper_id)?;
12297            } else {
12298                self.generate_identifier(&g.securable)?;
12299            }
12300        }
12301
12302        // Function parameter types (if present)
12303        if !g.function_params.is_empty() {
12304            self.write("(");
12305            for (i, param) in g.function_params.iter().enumerate() {
12306                if i > 0 {
12307                    self.write(", ");
12308                }
12309                self.write(param);
12310            }
12311            self.write(")");
12312        }
12313
12314        self.write_space();
12315        self.write_keyword("TO");
12316        self.write_space();
12317
12318        // Principals
12319        for (i, principal) in g.principals.iter().enumerate() {
12320            if i > 0 {
12321                self.write(", ");
12322            }
12323            if principal.is_role {
12324                self.write_keyword("ROLE");
12325                self.write_space();
12326            } else if principal.is_group {
12327                self.write_keyword("GROUP");
12328                self.write_space();
12329            } else if principal.is_share {
12330                self.write_keyword("SHARE");
12331                self.write_space();
12332            }
12333            self.generate_identifier(&principal.name)?;
12334        }
12335
12336        // WITH GRANT OPTION
12337        if g.grant_option {
12338            self.write_space();
12339            self.write_keyword("WITH GRANT OPTION");
12340        }
12341
12342        // TSQL: AS principal
12343        if let Some(ref principal) = g.as_principal {
12344            self.write_space();
12345            self.write_keyword("AS");
12346            self.write_space();
12347            self.generate_identifier(principal)?;
12348        }
12349
12350        Ok(())
12351    }
12352
12353    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
12354        self.write_keyword("REVOKE");
12355        self.write_space();
12356
12357        // GRANT OPTION FOR
12358        if r.grant_option {
12359            self.write_keyword("GRANT OPTION FOR");
12360            self.write_space();
12361        }
12362
12363        // Privileges (with optional column lists)
12364        for (i, privilege) in r.privileges.iter().enumerate() {
12365            if i > 0 {
12366                self.write(", ");
12367            }
12368            self.write_keyword(&privilege.name);
12369            // Output column list if present: SELECT(col1, col2)
12370            if !privilege.columns.is_empty() {
12371                self.write("(");
12372                for (j, col) in privilege.columns.iter().enumerate() {
12373                    if j > 0 {
12374                        self.write(", ");
12375                    }
12376                    self.write(col);
12377                }
12378                self.write(")");
12379            }
12380        }
12381
12382        self.write_space();
12383        self.write_keyword("ON");
12384        self.write_space();
12385
12386        // Object kind
12387        if let Some(kind) = &r.kind {
12388            self.write_keyword(kind);
12389            self.write_space();
12390        }
12391
12392        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
12393        {
12394            use crate::dialects::DialectType;
12395            let should_upper = matches!(
12396                self.config.dialect,
12397                Some(DialectType::PostgreSQL)
12398                    | Some(DialectType::CockroachDB)
12399                    | Some(DialectType::Materialize)
12400                    | Some(DialectType::RisingWave)
12401            ) && (r.kind.as_deref() == Some("FUNCTION")
12402                || r.kind.as_deref() == Some("PROCEDURE"));
12403            if should_upper {
12404                use crate::expressions::Identifier;
12405                let upper_id = Identifier {
12406                    name: r.securable.name.to_ascii_uppercase(),
12407                    quoted: r.securable.quoted,
12408                    ..r.securable.clone()
12409                };
12410                self.generate_identifier(&upper_id)?;
12411            } else {
12412                self.generate_identifier(&r.securable)?;
12413            }
12414        }
12415
12416        // Function parameter types (if present)
12417        if !r.function_params.is_empty() {
12418            self.write("(");
12419            for (i, param) in r.function_params.iter().enumerate() {
12420                if i > 0 {
12421                    self.write(", ");
12422                }
12423                self.write(param);
12424            }
12425            self.write(")");
12426        }
12427
12428        self.write_space();
12429        self.write_keyword("FROM");
12430        self.write_space();
12431
12432        // Principals
12433        for (i, principal) in r.principals.iter().enumerate() {
12434            if i > 0 {
12435                self.write(", ");
12436            }
12437            if principal.is_role {
12438                self.write_keyword("ROLE");
12439                self.write_space();
12440            } else if principal.is_group {
12441                self.write_keyword("GROUP");
12442                self.write_space();
12443            } else if principal.is_share {
12444                self.write_keyword("SHARE");
12445                self.write_space();
12446            }
12447            self.generate_identifier(&principal.name)?;
12448        }
12449
12450        // CASCADE or RESTRICT
12451        if r.cascade {
12452            self.write_space();
12453            self.write_keyword("CASCADE");
12454        } else if r.restrict {
12455            self.write_space();
12456            self.write_keyword("RESTRICT");
12457        }
12458
12459        Ok(())
12460    }
12461
12462    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
12463        self.write_keyword("COMMENT");
12464
12465        // IF EXISTS
12466        if c.exists {
12467            self.write_space();
12468            self.write_keyword("IF EXISTS");
12469        }
12470
12471        self.write_space();
12472        self.write_keyword("ON");
12473
12474        // MATERIALIZED
12475        if c.materialized {
12476            self.write_space();
12477            self.write_keyword("MATERIALIZED");
12478        }
12479
12480        self.write_space();
12481        self.write_keyword(&c.kind);
12482        self.write_space();
12483
12484        // Object name
12485        self.generate_expression(&c.this)?;
12486
12487        self.write_space();
12488        self.write_keyword("IS");
12489        self.write_space();
12490
12491        // Comment expression
12492        self.generate_expression(&c.expression)?;
12493
12494        Ok(())
12495    }
12496
12497    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
12498        self.write_keyword("SET");
12499
12500        for (i, item) in s.items.iter().enumerate() {
12501            if i > 0 {
12502                self.write(",");
12503            }
12504            self.write_space();
12505
12506            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY, VARIABLE)
12507            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
12508            if let Some(ref kind) = item.kind {
12509                // For VARIABLE kind, only output the keyword for dialects that require it
12510                // (Spark, Databricks, DuckDB) - matching Python sqlglot's
12511                // SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD flag
12512                if has_variable_kind {
12513                    if matches!(
12514                        self.config.dialect,
12515                        Some(DialectType::Spark | DialectType::Databricks | DialectType::DuckDB)
12516                    ) {
12517                        self.write_keyword("VARIABLE");
12518                        self.write_space();
12519                    }
12520                } else {
12521                    self.write_keyword(kind);
12522                    self.write_space();
12523                }
12524            }
12525
12526            // Check for special SET forms by name
12527            let name_str = match &item.name {
12528                Expression::Identifier(id) => Some(id.name.as_str()),
12529                _ => None,
12530            };
12531
12532            let is_transaction = name_str == Some("TRANSACTION");
12533            let is_character_set = name_str == Some("CHARACTER SET");
12534            let is_names = name_str == Some("NAMES");
12535            let is_collate = name_str == Some("COLLATE");
12536            let is_identity_insert = name_str == Some("IDENTITY_INSERT");
12537            let is_value_only =
12538                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
12539
12540            if is_transaction {
12541                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
12542                self.write_keyword("TRANSACTION");
12543                if let Expression::Identifier(id) = &item.value {
12544                    if !id.name.is_empty() {
12545                        self.write_space();
12546                        self.write(&id.name);
12547                    }
12548                }
12549            } else if is_character_set {
12550                // Output: SET CHARACTER SET <charset>
12551                self.write_keyword("CHARACTER SET");
12552                self.write_space();
12553                self.generate_set_value(&item.value)?;
12554            } else if is_names {
12555                // Output: SET NAMES <charset>
12556                self.write_keyword("NAMES");
12557                self.write_space();
12558                self.generate_set_value(&item.value)?;
12559            } else if is_collate {
12560                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
12561                self.write_keyword("COLLATE");
12562                self.write_space();
12563                self.generate_set_value(&item.value)?;
12564            } else if is_identity_insert {
12565                // T-SQL: SET IDENTITY_INSERT <table> ON|OFF
12566                self.write_keyword("IDENTITY_INSERT");
12567                self.write_space();
12568                self.generate_identity_insert_value(&item.value)?;
12569            } else if has_variable_kind {
12570                // Output: SET [VARIABLE] <name> = <value>
12571                // VARIABLE keyword already written above if dialect requires it
12572                if let Some(ns) = name_str {
12573                    self.write(ns);
12574                } else {
12575                    self.generate_expression(&item.name)?;
12576                }
12577                self.write(" = ");
12578                self.generate_set_value(&item.value)?;
12579            } else if is_value_only {
12580                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
12581                self.generate_expression(&item.name)?;
12582            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
12583                // SET key value without = (TSQL style)
12584                self.generate_expression(&item.name)?;
12585                self.write_space();
12586                self.generate_set_value(&item.value)?;
12587            } else {
12588                // Standard: variable = value
12589                // SET item names should not be quoted (they are config parameter names, not column refs)
12590                match &item.name {
12591                    Expression::Identifier(id) => {
12592                        self.write(&id.name);
12593                    }
12594                    _ => {
12595                        self.generate_expression(&item.name)?;
12596                    }
12597                }
12598                self.write(" = ");
12599                self.generate_set_value(&item.value)?;
12600            }
12601        }
12602
12603        Ok(())
12604    }
12605
12606    fn generate_identity_insert_value(&mut self, value: &Expression) -> Result<()> {
12607        if let Expression::Tuple(tuple) = value {
12608            if tuple.expressions.len() == 2 {
12609                self.generate_expression(&tuple.expressions[0])?;
12610                self.write_space();
12611                self.generate_set_value(&tuple.expressions[1])?;
12612                return Ok(());
12613            }
12614        }
12615
12616        self.generate_set_value(value)
12617    }
12618
12619    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
12620    /// directly to avoid reserved keyword quoting.
12621    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
12622        if let Expression::Identifier(id) = value {
12623            match id.name.as_str() {
12624                "DEFAULT" | "ON" | "OFF" => {
12625                    self.write_keyword(&id.name);
12626                    return Ok(());
12627                }
12628                _ => {}
12629            }
12630        }
12631        self.generate_expression(value)
12632    }
12633
12634    // ==================== Phase 4: Additional DDL Generation ====================
12635
12636    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
12637        self.write_keyword("ALTER");
12638        // MySQL modifiers before VIEW
12639        if let Some(ref algorithm) = av.algorithm {
12640            self.write_space();
12641            self.write_keyword("ALGORITHM");
12642            self.write(" = ");
12643            self.write_keyword(algorithm);
12644        }
12645        if let Some(ref definer) = av.definer {
12646            self.write_space();
12647            self.write_keyword("DEFINER");
12648            self.write(" = ");
12649            self.write(definer);
12650        }
12651        if let Some(ref sql_security) = av.sql_security {
12652            self.write_space();
12653            self.write_keyword("SQL SECURITY");
12654            self.write(" = ");
12655            self.write_keyword(sql_security);
12656        }
12657        self.write_space();
12658        self.write_keyword("VIEW");
12659        self.write_space();
12660        self.generate_table(&av.name)?;
12661
12662        // Hive: Column aliases with optional COMMENT
12663        if !av.columns.is_empty() {
12664            self.write(" (");
12665            for (i, col) in av.columns.iter().enumerate() {
12666                if i > 0 {
12667                    self.write(", ");
12668                }
12669                self.generate_identifier(&col.name)?;
12670                if let Some(ref comment) = col.comment {
12671                    self.write_space();
12672                    self.write_keyword("COMMENT");
12673                    self.write(" ");
12674                    self.generate_string_literal(comment)?;
12675                }
12676            }
12677            self.write(")");
12678        }
12679
12680        // TSQL: WITH option before actions
12681        if let Some(ref opt) = av.with_option {
12682            self.write_space();
12683            self.write_keyword("WITH");
12684            self.write_space();
12685            self.write_keyword(opt);
12686        }
12687
12688        for action in &av.actions {
12689            self.write_space();
12690            match action {
12691                AlterViewAction::Rename(new_name) => {
12692                    self.write_keyword("RENAME TO");
12693                    self.write_space();
12694                    self.generate_table(new_name)?;
12695                }
12696                AlterViewAction::OwnerTo(owner) => {
12697                    self.write_keyword("OWNER TO");
12698                    self.write_space();
12699                    self.generate_identifier(owner)?;
12700                }
12701                AlterViewAction::SetSchema(schema) => {
12702                    self.write_keyword("SET SCHEMA");
12703                    self.write_space();
12704                    self.generate_identifier(schema)?;
12705                }
12706                AlterViewAction::SetAuthorization(auth) => {
12707                    self.write_keyword("SET AUTHORIZATION");
12708                    self.write_space();
12709                    self.write(auth);
12710                }
12711                AlterViewAction::AlterColumn { name, action } => {
12712                    self.write_keyword("ALTER COLUMN");
12713                    self.write_space();
12714                    self.generate_identifier(name)?;
12715                    self.write_space();
12716                    self.generate_alter_column_action(action)?;
12717                }
12718                AlterViewAction::AsSelect(query) => {
12719                    self.write_keyword("AS");
12720                    self.write_space();
12721                    self.generate_expression(query)?;
12722                }
12723                AlterViewAction::SetTblproperties(props) => {
12724                    self.write_keyword("SET TBLPROPERTIES");
12725                    self.write(" (");
12726                    for (i, (key, value)) in props.iter().enumerate() {
12727                        if i > 0 {
12728                            self.write(", ");
12729                        }
12730                        self.generate_string_literal(key)?;
12731                        self.write("=");
12732                        self.generate_string_literal(value)?;
12733                    }
12734                    self.write(")");
12735                }
12736                AlterViewAction::UnsetTblproperties(keys) => {
12737                    self.write_keyword("UNSET TBLPROPERTIES");
12738                    self.write(" (");
12739                    for (i, key) in keys.iter().enumerate() {
12740                        if i > 0 {
12741                            self.write(", ");
12742                        }
12743                        self.generate_string_literal(key)?;
12744                    }
12745                    self.write(")");
12746                }
12747            }
12748        }
12749
12750        Ok(())
12751    }
12752
12753    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
12754        self.write_keyword("ALTER INDEX");
12755        self.write_space();
12756        self.generate_identifier(&ai.name)?;
12757
12758        if let Some(table) = &ai.table {
12759            self.write_space();
12760            self.write_keyword("ON");
12761            self.write_space();
12762            self.generate_table(table)?;
12763        }
12764
12765        for action in &ai.actions {
12766            self.write_space();
12767            match action {
12768                AlterIndexAction::Rename(new_name) => {
12769                    self.write_keyword("RENAME TO");
12770                    self.write_space();
12771                    self.generate_identifier(new_name)?;
12772                }
12773                AlterIndexAction::SetTablespace(tablespace) => {
12774                    self.write_keyword("SET TABLESPACE");
12775                    self.write_space();
12776                    self.generate_identifier(tablespace)?;
12777                }
12778                AlterIndexAction::Visible(visible) => {
12779                    if *visible {
12780                        self.write_keyword("VISIBLE");
12781                    } else {
12782                        self.write_keyword("INVISIBLE");
12783                    }
12784                }
12785            }
12786        }
12787
12788        Ok(())
12789    }
12790
12791    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
12792        // Output leading comments
12793        for comment in &cs.leading_comments {
12794            self.write_formatted_comment(comment);
12795            self.write_space();
12796        }
12797
12798        // Athena: CREATE SCHEMA uses Hive engine (backticks)
12799        let saved_athena_hive_context = self.athena_hive_context;
12800        if matches!(
12801            self.config.dialect,
12802            Some(crate::dialects::DialectType::Athena)
12803        ) {
12804            self.athena_hive_context = true;
12805        }
12806
12807        self.write_keyword("CREATE SCHEMA");
12808
12809        if cs.if_not_exists {
12810            self.write_space();
12811            self.write_keyword("IF NOT EXISTS");
12812        }
12813
12814        self.write_space();
12815        for (i, part) in cs.name.iter().enumerate() {
12816            if i > 0 {
12817                self.write(".");
12818            }
12819            self.generate_identifier(part)?;
12820        }
12821
12822        if let Some(ref clone_parts) = cs.clone_from {
12823            self.write_keyword(" CLONE ");
12824            for (i, part) in clone_parts.iter().enumerate() {
12825                if i > 0 {
12826                    self.write(".");
12827                }
12828                self.generate_identifier(part)?;
12829            }
12830        }
12831
12832        if let Some(ref at_clause) = cs.at_clause {
12833            self.write_space();
12834            self.generate_expression(at_clause)?;
12835        }
12836
12837        if let Some(auth) = &cs.authorization {
12838            self.write_space();
12839            self.write_keyword("AUTHORIZATION");
12840            self.write_space();
12841            self.generate_identifier(auth)?;
12842        }
12843
12844        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
12845        // Separate WITH properties from other properties
12846        let with_properties: Vec<_> = cs
12847            .properties
12848            .iter()
12849            .filter(|p| matches!(p, Expression::Property(_)))
12850            .collect();
12851        let other_properties: Vec<_> = cs
12852            .properties
12853            .iter()
12854            .filter(|p| !matches!(p, Expression::Property(_)))
12855            .collect();
12856
12857        // Generate WITH (props) if we have Property expressions
12858        if !with_properties.is_empty() {
12859            self.write_space();
12860            self.write_keyword("WITH");
12861            self.write(" (");
12862            for (i, prop) in with_properties.iter().enumerate() {
12863                if i > 0 {
12864                    self.write(", ");
12865                }
12866                self.generate_expression(prop)?;
12867            }
12868            self.write(")");
12869        }
12870
12871        // Generate other properties (like DEFAULT COLLATE)
12872        for prop in other_properties {
12873            self.write_space();
12874            self.generate_expression(prop)?;
12875        }
12876
12877        // Restore Athena Hive context
12878        self.athena_hive_context = saved_athena_hive_context;
12879
12880        Ok(())
12881    }
12882
12883    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
12884        self.write_keyword("DROP SCHEMA");
12885
12886        if ds.if_exists {
12887            self.write_space();
12888            self.write_keyword("IF EXISTS");
12889        }
12890
12891        self.write_space();
12892        self.generate_identifier(&ds.name)?;
12893
12894        if ds.cascade {
12895            self.write_space();
12896            self.write_keyword("CASCADE");
12897        }
12898
12899        Ok(())
12900    }
12901
12902    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
12903        self.write_keyword("DROP NAMESPACE");
12904
12905        if dn.if_exists {
12906            self.write_space();
12907            self.write_keyword("IF EXISTS");
12908        }
12909
12910        self.write_space();
12911        self.generate_identifier(&dn.name)?;
12912
12913        if dn.cascade {
12914            self.write_space();
12915            self.write_keyword("CASCADE");
12916        }
12917
12918        Ok(())
12919    }
12920
12921    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
12922        self.write_keyword("CREATE DATABASE");
12923
12924        if cd.if_not_exists {
12925            self.write_space();
12926            self.write_keyword("IF NOT EXISTS");
12927        }
12928
12929        self.write_space();
12930        self.generate_identifier(&cd.name)?;
12931
12932        if let Some(ref clone_src) = cd.clone_from {
12933            self.write_keyword(" CLONE ");
12934            self.generate_identifier(clone_src)?;
12935        }
12936
12937        // AT/BEFORE clause for time travel (Snowflake)
12938        if let Some(ref at_clause) = cd.at_clause {
12939            self.write_space();
12940            self.generate_expression(at_clause)?;
12941        }
12942
12943        for option in &cd.options {
12944            self.write_space();
12945            match option {
12946                DatabaseOption::CharacterSet(charset) => {
12947                    self.write_keyword("CHARACTER SET");
12948                    self.write(" = ");
12949                    self.write(&format!("'{}'", charset));
12950                }
12951                DatabaseOption::Collate(collate) => {
12952                    self.write_keyword("COLLATE");
12953                    self.write(" = ");
12954                    self.write(&format!("'{}'", collate));
12955                }
12956                DatabaseOption::Owner(owner) => {
12957                    self.write_keyword("OWNER");
12958                    self.write(" = ");
12959                    self.generate_identifier(owner)?;
12960                }
12961                DatabaseOption::Template(template) => {
12962                    self.write_keyword("TEMPLATE");
12963                    self.write(" = ");
12964                    self.generate_identifier(template)?;
12965                }
12966                DatabaseOption::Encoding(encoding) => {
12967                    self.write_keyword("ENCODING");
12968                    self.write(" = ");
12969                    self.write(&format!("'{}'", encoding));
12970                }
12971                DatabaseOption::Location(location) => {
12972                    self.write_keyword("LOCATION");
12973                    self.write(" = ");
12974                    self.write(&format!("'{}'", location));
12975                }
12976            }
12977        }
12978
12979        Ok(())
12980    }
12981
12982    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
12983        self.write_keyword("DROP DATABASE");
12984
12985        if dd.if_exists {
12986            self.write_space();
12987            self.write_keyword("IF EXISTS");
12988        }
12989
12990        self.write_space();
12991        self.generate_identifier(&dd.name)?;
12992
12993        if dd.sync {
12994            self.write_space();
12995            self.write_keyword("SYNC");
12996        }
12997
12998        Ok(())
12999    }
13000
13001    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
13002        self.write_keyword("CREATE");
13003
13004        if cf.or_alter {
13005            self.write_space();
13006            self.write_keyword("OR ALTER");
13007        } else if cf.or_replace {
13008            self.write_space();
13009            self.write_keyword("OR REPLACE");
13010        }
13011
13012        if cf.temporary {
13013            self.write_space();
13014            self.write_keyword("TEMPORARY");
13015        }
13016
13017        self.write_space();
13018        if cf.is_table_function {
13019            self.write_keyword("TABLE FUNCTION");
13020        } else {
13021            self.write_keyword("FUNCTION");
13022        }
13023
13024        if cf.if_not_exists {
13025            self.write_space();
13026            self.write_keyword("IF NOT EXISTS");
13027        }
13028
13029        self.write_space();
13030        self.generate_table(&cf.name)?;
13031        if cf.has_parens {
13032            let func_multiline = self.config.pretty
13033                && matches!(
13034                    self.config.dialect,
13035                    Some(crate::dialects::DialectType::TSQL)
13036                        | Some(crate::dialects::DialectType::Fabric)
13037                )
13038                && !cf.parameters.is_empty();
13039            if func_multiline {
13040                self.write("(\n");
13041                self.indent_level += 2;
13042                self.write_indent();
13043                self.generate_function_parameters(&cf.parameters)?;
13044                self.write("\n");
13045                self.indent_level -= 2;
13046                self.write(")");
13047            } else {
13048                self.write("(");
13049                self.generate_function_parameters(&cf.parameters)?;
13050                self.write(")");
13051            }
13052        }
13053
13054        // Output RETURNS clause (always comes first after parameters)
13055        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
13056        let use_multiline = self.config.pretty
13057            && matches!(
13058                self.config.dialect,
13059                Some(crate::dialects::DialectType::BigQuery)
13060                    | Some(crate::dialects::DialectType::TSQL)
13061                    | Some(crate::dialects::DialectType::Fabric)
13062            );
13063
13064        if cf.language_first {
13065            // LANGUAGE first, then SQL data access, then RETURNS
13066            if let Some(lang) = &cf.language {
13067                if use_multiline {
13068                    self.write_newline();
13069                } else {
13070                    self.write_space();
13071                }
13072                self.write_keyword("LANGUAGE");
13073                self.write_space();
13074                self.write(lang);
13075            }
13076
13077            // SQL data access comes after LANGUAGE in this case
13078            if let Some(sql_data) = &cf.sql_data_access {
13079                self.write_space();
13080                match sql_data {
13081                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
13082                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
13083                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
13084                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
13085                }
13086            }
13087
13088            if let Some(ref rtb) = cf.returns_table_body {
13089                if use_multiline {
13090                    self.write_newline();
13091                } else {
13092                    self.write_space();
13093                }
13094                self.write_keyword("RETURNS");
13095                self.write_space();
13096                self.write(rtb);
13097            } else if let Some(return_type) = &cf.return_type {
13098                if use_multiline {
13099                    self.write_newline();
13100                } else {
13101                    self.write_space();
13102                }
13103                self.write_keyword("RETURNS");
13104                self.write_space();
13105                self.generate_function_return_type(return_type)?;
13106            }
13107        } else {
13108            // RETURNS first (default)
13109            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
13110            let is_duckdb = matches!(
13111                self.config.dialect,
13112                Some(crate::dialects::DialectType::DuckDB)
13113            );
13114            if let Some(ref rtb) = cf.returns_table_body {
13115                if !(is_duckdb && rtb.is_empty()) {
13116                    if use_multiline {
13117                        self.write_newline();
13118                    } else {
13119                        self.write_space();
13120                    }
13121                    self.write_keyword("RETURNS");
13122                    self.write_space();
13123                    self.write(rtb);
13124                }
13125            } else if let Some(return_type) = &cf.return_type {
13126                // DuckDB: skip all RETURNS (DuckDB macros don't use RETURNS clause)
13127                if !is_duckdb {
13128                    let is_table_return = matches!(return_type, crate::expressions::DataType::Custom { ref name } if name.eq_ignore_ascii_case("TABLE"));
13129                    if use_multiline {
13130                        self.write_newline();
13131                    } else {
13132                        self.write_space();
13133                    }
13134                    self.write_keyword("RETURNS");
13135                    self.write_space();
13136                    if is_table_return {
13137                        self.write_keyword("TABLE");
13138                    } else {
13139                        self.generate_function_return_type(return_type)?;
13140                    }
13141                }
13142            }
13143        }
13144
13145        // If we have property_order, use it to output properties in original order
13146        if !cf.property_order.is_empty() {
13147            // For BigQuery, OPTIONS must come before AS - reorder if needed
13148            let is_bigquery = matches!(
13149                self.config.dialect,
13150                Some(crate::dialects::DialectType::BigQuery)
13151            );
13152            let is_postgres = matches!(
13153                self.config.dialect,
13154                Some(crate::dialects::DialectType::PostgreSQL)
13155            );
13156            let property_order = if is_bigquery {
13157                // Move Options before As if both are present
13158                let mut reordered = Vec::new();
13159                let mut has_as = false;
13160                let mut has_options = false;
13161                for prop in &cf.property_order {
13162                    match prop {
13163                        FunctionPropertyKind::As => has_as = true,
13164                        FunctionPropertyKind::Options => has_options = true,
13165                        _ => {}
13166                    }
13167                }
13168                if has_as && has_options {
13169                    // Output all props except As and Options, then Options, then As
13170                    for prop in &cf.property_order {
13171                        if *prop != FunctionPropertyKind::As
13172                            && *prop != FunctionPropertyKind::Options
13173                        {
13174                            reordered.push(*prop);
13175                        }
13176                    }
13177                    reordered.push(FunctionPropertyKind::Options);
13178                    reordered.push(FunctionPropertyKind::As);
13179                    reordered
13180                } else {
13181                    cf.property_order.clone()
13182                }
13183            } else if is_postgres
13184                && cf.property_order.contains(&FunctionPropertyKind::As)
13185                && cf.property_order.contains(&FunctionPropertyKind::NullInput)
13186            {
13187                let mut reordered: Vec<_> = cf
13188                    .property_order
13189                    .iter()
13190                    .copied()
13191                    .filter(|prop| *prop != FunctionPropertyKind::As)
13192                    .collect();
13193                reordered.push(FunctionPropertyKind::As);
13194                reordered
13195            } else {
13196                cf.property_order.clone()
13197            };
13198
13199            for prop in &property_order {
13200                match prop {
13201                    FunctionPropertyKind::Set => {
13202                        self.generate_function_set_options(cf)?;
13203                    }
13204                    FunctionPropertyKind::As => {
13205                        self.generate_function_body(cf)?;
13206                    }
13207                    FunctionPropertyKind::Using => {
13208                        self.generate_function_using_resources(cf)?;
13209                    }
13210                    FunctionPropertyKind::Language => {
13211                        if !cf.language_first {
13212                            // Only output here if not already output above
13213                            if let Some(lang) = &cf.language {
13214                                // Only BigQuery uses multiline formatting
13215                                let use_multiline = self.config.pretty
13216                                    && matches!(
13217                                        self.config.dialect,
13218                                        Some(crate::dialects::DialectType::BigQuery)
13219                                    );
13220                                if use_multiline {
13221                                    self.write_newline();
13222                                } else {
13223                                    self.write_space();
13224                                }
13225                                self.write_keyword("LANGUAGE");
13226                                self.write_space();
13227                                self.write(lang);
13228                            }
13229                        }
13230                    }
13231                    FunctionPropertyKind::Determinism => {
13232                        self.generate_function_determinism(cf)?;
13233                    }
13234                    FunctionPropertyKind::NullInput => {
13235                        self.generate_function_null_input(cf)?;
13236                    }
13237                    FunctionPropertyKind::Security => {
13238                        self.generate_function_security(cf)?;
13239                    }
13240                    FunctionPropertyKind::SqlDataAccess => {
13241                        if !cf.language_first {
13242                            // Only output here if not already output above
13243                            self.generate_function_sql_data_access(cf)?;
13244                        }
13245                    }
13246                    FunctionPropertyKind::Options => {
13247                        if !cf.options.is_empty() {
13248                            self.write_space();
13249                            self.generate_options_clause(&cf.options)?;
13250                        }
13251                    }
13252                    FunctionPropertyKind::Environment => {
13253                        if !cf.environment.is_empty() {
13254                            self.write_space();
13255                            self.generate_environment_clause(&cf.environment)?;
13256                        }
13257                    }
13258                    FunctionPropertyKind::Handler => {
13259                        if let Some(ref h) = cf.handler {
13260                            self.write_space();
13261                            self.write_keyword("HANDLER");
13262                            if cf.handler_uses_eq {
13263                                self.write(" = ");
13264                            } else {
13265                                self.write_space();
13266                            }
13267                            self.write("'");
13268                            self.write(h);
13269                            self.write("'");
13270                        }
13271                    }
13272                    FunctionPropertyKind::RuntimeVersion => {
13273                        if let Some(ref runtime_version) = cf.runtime_version {
13274                            self.write_space();
13275                            self.write_keyword("RUNTIME_VERSION");
13276                            self.write("='");
13277                            self.write(runtime_version);
13278                            self.write("'");
13279                        }
13280                    }
13281                    FunctionPropertyKind::Packages => {
13282                        if let Some(ref packages) = cf.packages {
13283                            self.write_space();
13284                            self.write_keyword("PACKAGES");
13285                            self.write("=(");
13286                            for (i, package) in packages.iter().enumerate() {
13287                                if i > 0 {
13288                                    self.write(", ");
13289                                }
13290                                self.write("'");
13291                                self.write(package);
13292                                self.write("'");
13293                            }
13294                            self.write(")");
13295                        }
13296                    }
13297                    FunctionPropertyKind::ParameterStyle => {
13298                        if let Some(ref ps) = cf.parameter_style {
13299                            self.write_space();
13300                            self.write_keyword("PARAMETER STYLE");
13301                            self.write_space();
13302                            self.write_keyword(ps);
13303                        }
13304                    }
13305                }
13306            }
13307
13308            // Output OPTIONS if not tracked in property_order (legacy)
13309            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
13310            {
13311                self.write_space();
13312                self.generate_options_clause(&cf.options)?;
13313            }
13314
13315            // Output ENVIRONMENT if not tracked in property_order (legacy)
13316            if !cf.environment.is_empty()
13317                && !cf
13318                    .property_order
13319                    .contains(&FunctionPropertyKind::Environment)
13320            {
13321                self.write_space();
13322                self.generate_environment_clause(&cf.environment)?;
13323            }
13324        } else {
13325            // Legacy behavior when property_order is empty
13326            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
13327            if matches!(
13328                self.config.dialect,
13329                Some(crate::dialects::DialectType::BigQuery)
13330            ) {
13331                self.generate_function_determinism(cf)?;
13332            }
13333
13334            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
13335            let use_multiline = self.config.pretty
13336                && matches!(
13337                    self.config.dialect,
13338                    Some(crate::dialects::DialectType::BigQuery)
13339                );
13340
13341            if !cf.language_first {
13342                if let Some(lang) = &cf.language {
13343                    if use_multiline {
13344                        self.write_newline();
13345                    } else {
13346                        self.write_space();
13347                    }
13348                    self.write_keyword("LANGUAGE");
13349                    self.write_space();
13350                    self.write(lang);
13351                }
13352
13353                // SQL data access characteristic comes after LANGUAGE
13354                self.generate_function_sql_data_access(cf)?;
13355            }
13356
13357            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
13358            if !matches!(
13359                self.config.dialect,
13360                Some(crate::dialects::DialectType::BigQuery)
13361            ) {
13362                self.generate_function_determinism(cf)?;
13363            }
13364
13365            self.generate_function_null_input(cf)?;
13366            self.generate_function_security(cf)?;
13367            self.generate_function_set_options(cf)?;
13368
13369            // BigQuery: OPTIONS (key=value, ...) - comes before AS
13370            if !cf.options.is_empty() {
13371                self.write_space();
13372                self.generate_options_clause(&cf.options)?;
13373            }
13374
13375            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
13376            if !cf.environment.is_empty() {
13377                self.write_space();
13378                self.generate_environment_clause(&cf.environment)?;
13379            }
13380
13381            if let Some(ref h) = cf.handler {
13382                self.write_space();
13383                self.write_keyword("HANDLER");
13384                if cf.handler_uses_eq {
13385                    self.write(" = ");
13386                } else {
13387                    self.write_space();
13388                }
13389                self.write("'");
13390                self.write(h);
13391                self.write("'");
13392            }
13393
13394            if let Some(ref runtime_version) = cf.runtime_version {
13395                self.write_space();
13396                self.write_keyword("RUNTIME_VERSION");
13397                self.write("='");
13398                self.write(runtime_version);
13399                self.write("'");
13400            }
13401
13402            if let Some(ref packages) = cf.packages {
13403                self.write_space();
13404                self.write_keyword("PACKAGES");
13405                self.write("=(");
13406                for (i, package) in packages.iter().enumerate() {
13407                    if i > 0 {
13408                        self.write(", ");
13409                    }
13410                    self.write("'");
13411                    self.write(package);
13412                    self.write("'");
13413                }
13414                self.write(")");
13415            }
13416
13417            self.generate_function_body(cf)?;
13418            self.generate_function_using_resources(cf)?;
13419        }
13420
13421        Ok(())
13422    }
13423
13424    fn generate_function_return_type(&mut self, return_type: &DataType) -> Result<()> {
13425        if matches!(
13426            self.config.dialect,
13427            Some(crate::dialects::DialectType::PostgreSQL)
13428        ) {
13429            if let DataType::Custom { name } = return_type {
13430                if name.eq_ignore_ascii_case("integer") {
13431                    self.write_keyword("INT");
13432                    return Ok(());
13433                }
13434            }
13435        }
13436
13437        self.generate_data_type(return_type)
13438    }
13439
13440    /// Generate SET options for CREATE FUNCTION
13441    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
13442        for opt in &cf.set_options {
13443            self.write_space();
13444            self.write_keyword("SET");
13445            self.write_space();
13446            self.write(&opt.name);
13447            match &opt.value {
13448                FunctionSetValue::Value { value, use_to } => {
13449                    if *use_to {
13450                        self.write(" TO ");
13451                    } else {
13452                        self.write(" = ");
13453                    }
13454                    self.write(value);
13455                }
13456                FunctionSetValue::FromCurrent => {
13457                    self.write_space();
13458                    self.write_keyword("FROM CURRENT");
13459                }
13460            }
13461        }
13462        Ok(())
13463    }
13464
13465    fn generate_function_using_resources(&mut self, cf: &CreateFunction) -> Result<()> {
13466        if cf.using_resources.is_empty() {
13467            return Ok(());
13468        }
13469
13470        self.write_space();
13471        self.write_keyword("USING");
13472        for resource in &cf.using_resources {
13473            self.write_space();
13474            self.write_keyword(&resource.kind);
13475            self.write_space();
13476            self.generate_string_literal(&resource.uri)?;
13477        }
13478        Ok(())
13479    }
13480
13481    /// Generate function body (AS clause)
13482    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
13483        if let Some(body) = &cf.body {
13484            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
13485            self.write_space();
13486            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
13487            let use_multiline = self.config.pretty
13488                && matches!(
13489                    self.config.dialect,
13490                    Some(crate::dialects::DialectType::BigQuery)
13491                );
13492            match body {
13493                FunctionBody::Block(block) => {
13494                    self.write_keyword("AS");
13495                    if matches!(
13496                        self.config.dialect,
13497                        Some(crate::dialects::DialectType::TSQL)
13498                    ) {
13499                        self.write(" BEGIN ");
13500                        self.write(block);
13501                        self.write(" END");
13502                    } else if matches!(
13503                        self.config.dialect,
13504                        Some(crate::dialects::DialectType::PostgreSQL)
13505                    ) {
13506                        self.write(" $$");
13507                        self.write(block);
13508                        self.write("$$");
13509                    } else {
13510                        // Escape content for single-quoted output
13511                        let escaped = self.escape_block_for_single_quote(block);
13512                        // In BigQuery pretty mode, body content goes on new line
13513                        if use_multiline {
13514                            self.write_newline();
13515                        } else {
13516                            self.write(" ");
13517                        }
13518                        self.write("'");
13519                        self.write(&escaped);
13520                        self.write("'");
13521                    }
13522                }
13523                FunctionBody::StringLiteral(s) => {
13524                    self.write_keyword("AS");
13525                    // In BigQuery pretty mode, body content goes on new line
13526                    if use_multiline {
13527                        self.write_newline();
13528                    } else {
13529                        self.write(" ");
13530                    }
13531                    self.write("'");
13532                    self.write(s);
13533                    self.write("'");
13534                }
13535                FunctionBody::Expression(expr) => {
13536                    self.write_keyword("AS");
13537                    self.write_space();
13538                    self.generate_expression(expr)?;
13539                }
13540                FunctionBody::External(name) => {
13541                    self.write_keyword("EXTERNAL NAME");
13542                    self.write(" '");
13543                    self.write(name);
13544                    self.write("'");
13545                }
13546                FunctionBody::Return(expr) => {
13547                    if matches!(
13548                        self.config.dialect,
13549                        Some(crate::dialects::DialectType::DuckDB)
13550                    ) {
13551                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
13552                        self.write_keyword("AS");
13553                        self.write_space();
13554                        // Check both returns_table_body marker and return_type = Custom "TABLE"
13555                        let is_table_return = cf.returns_table_body.is_some()
13556                            || matches!(&cf.return_type, Some(crate::expressions::DataType::Custom { ref name }) if name.eq_ignore_ascii_case("TABLE"));
13557                        if is_table_return {
13558                            self.write_keyword("TABLE");
13559                            self.write_space();
13560                        }
13561                        self.generate_expression(expr)?;
13562                    } else {
13563                        if self.config.create_function_return_as {
13564                            self.write_keyword("AS");
13565                            // TSQL pretty: newline between AS and RETURN
13566                            if self.config.pretty
13567                                && matches!(
13568                                    self.config.dialect,
13569                                    Some(crate::dialects::DialectType::TSQL)
13570                                        | Some(crate::dialects::DialectType::Fabric)
13571                                )
13572                            {
13573                                self.write_newline();
13574                            } else {
13575                                self.write_space();
13576                            }
13577                        }
13578                        self.write_keyword("RETURN");
13579                        self.write_space();
13580                        self.generate_expression(expr)?;
13581                    }
13582                }
13583                FunctionBody::Statements(stmts) => {
13584                    self.write_keyword("AS");
13585                    self.write(" BEGIN ");
13586                    for (i, stmt) in stmts.iter().enumerate() {
13587                        if i > 0 {
13588                            self.write(" ");
13589                        }
13590                        self.generate_expression(stmt)?;
13591                        self.write(";");
13592                    }
13593                    self.write(" END");
13594                }
13595                FunctionBody::RawBlock(text) => {
13596                    self.write_newline();
13597                    self.write(text);
13598                }
13599                FunctionBody::DollarQuoted { content, tag } => {
13600                    self.write_keyword("AS");
13601                    self.write(" ");
13602                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
13603                    let supports_dollar_quoting = matches!(
13604                        self.config.dialect,
13605                        Some(crate::dialects::DialectType::PostgreSQL)
13606                            | Some(crate::dialects::DialectType::Databricks)
13607                            | Some(crate::dialects::DialectType::Redshift)
13608                            | Some(crate::dialects::DialectType::DuckDB)
13609                    );
13610                    if supports_dollar_quoting {
13611                        // Output in dollar-quoted format
13612                        self.write("$");
13613                        if let Some(t) = tag {
13614                            self.write(t);
13615                        }
13616                        self.write("$");
13617                        self.write(content);
13618                        self.write("$");
13619                        if let Some(t) = tag {
13620                            self.write(t);
13621                        }
13622                        self.write("$");
13623                    } else {
13624                        // Convert to single-quoted string for other dialects
13625                        let escaped = self.escape_block_for_single_quote(content);
13626                        self.write("'");
13627                        self.write(&escaped);
13628                        self.write("'");
13629                    }
13630                }
13631            }
13632        }
13633        Ok(())
13634    }
13635
13636    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
13637    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
13638        if let Some(det) = cf.deterministic {
13639            self.write_space();
13640            if matches!(
13641                self.config.dialect,
13642                Some(crate::dialects::DialectType::BigQuery)
13643            ) {
13644                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
13645                if det {
13646                    self.write_keyword("DETERMINISTIC");
13647                } else {
13648                    self.write_keyword("NOT DETERMINISTIC");
13649                }
13650            } else {
13651                // PostgreSQL and others use IMMUTABLE/VOLATILE
13652                if det {
13653                    self.write_keyword("IMMUTABLE");
13654                } else {
13655                    self.write_keyword("VOLATILE");
13656                }
13657            }
13658        }
13659        Ok(())
13660    }
13661
13662    /// Generate null input handling clause
13663    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
13664        if let Some(returns_null) = cf.returns_null_on_null_input {
13665            self.write_space();
13666            if returns_null {
13667                if cf.strict {
13668                    self.write_keyword("STRICT");
13669                } else {
13670                    self.write_keyword("RETURNS NULL ON NULL INPUT");
13671                }
13672            } else {
13673                self.write_keyword("CALLED ON NULL INPUT");
13674            }
13675        }
13676        Ok(())
13677    }
13678
13679    /// Generate security clause
13680    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
13681        if let Some(security) = &cf.security {
13682            self.write_space();
13683            // MySQL uses SQL SECURITY prefix
13684            if matches!(
13685                self.config.dialect,
13686                Some(crate::dialects::DialectType::MySQL)
13687            ) {
13688                self.write_keyword("SQL SECURITY");
13689            } else {
13690                self.write_keyword("SECURITY");
13691            }
13692            self.write_space();
13693            match security {
13694                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
13695                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
13696                FunctionSecurity::None => self.write_keyword("NONE"),
13697            }
13698        }
13699        Ok(())
13700    }
13701
13702    /// Generate SQL data access clause
13703    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
13704        if let Some(sql_data) = &cf.sql_data_access {
13705            self.write_space();
13706            match sql_data {
13707                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
13708                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
13709                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
13710                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
13711            }
13712        }
13713        Ok(())
13714    }
13715
13716    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
13717        for (i, param) in params.iter().enumerate() {
13718            if i > 0 {
13719                self.write(", ");
13720            }
13721
13722            if let Some(mode) = &param.mode {
13723                if let Some(text) = &param.mode_text {
13724                    self.write(text);
13725                } else {
13726                    match mode {
13727                        ParameterMode::In => self.write_keyword("IN"),
13728                        ParameterMode::Out => self.write_keyword("OUT"),
13729                        ParameterMode::InOut => self.write_keyword("INOUT"),
13730                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
13731                    }
13732                }
13733                self.write_space();
13734            }
13735
13736            if let Some(name) = &param.name {
13737                self.generate_identifier(name)?;
13738                // Skip space and type for empty Custom types (e.g., DuckDB macros)
13739                let skip_type =
13740                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
13741                if !skip_type {
13742                    self.write_space();
13743                    self.generate_data_type(&param.data_type)?;
13744                }
13745            } else {
13746                self.generate_data_type(&param.data_type)?;
13747            }
13748
13749            if let Some(default) = &param.default {
13750                if self.config.parameter_default_equals {
13751                    self.write(" = ");
13752                } else {
13753                    self.write(" DEFAULT ");
13754                }
13755                self.generate_expression(default)?;
13756            }
13757        }
13758
13759        Ok(())
13760    }
13761
13762    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
13763        self.write_keyword("DROP FUNCTION");
13764
13765        if df.if_exists {
13766            self.write_space();
13767            self.write_keyword("IF EXISTS");
13768        }
13769
13770        self.write_space();
13771        self.generate_table(&df.name)?;
13772
13773        if let Some(params) = &df.parameters {
13774            self.write(" (");
13775            for (i, dt) in params.iter().enumerate() {
13776                if i > 0 {
13777                    self.write(", ");
13778                }
13779                self.generate_data_type(dt)?;
13780            }
13781            self.write(")");
13782        }
13783
13784        if df.cascade {
13785            self.write_space();
13786            self.write_keyword("CASCADE");
13787        }
13788
13789        Ok(())
13790    }
13791
13792    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
13793        self.write_keyword("CREATE");
13794
13795        if cp.or_alter {
13796            self.write_space();
13797            self.write_keyword("OR ALTER");
13798        } else if cp.or_replace {
13799            self.write_space();
13800            self.write_keyword("OR REPLACE");
13801        }
13802
13803        self.write_space();
13804        if cp.use_proc_keyword {
13805            self.write_keyword("PROC");
13806        } else {
13807            self.write_keyword("PROCEDURE");
13808        }
13809
13810        if cp.if_not_exists {
13811            self.write_space();
13812            self.write_keyword("IF NOT EXISTS");
13813        }
13814
13815        self.write_space();
13816        self.generate_table(&cp.name)?;
13817        if cp.has_parens {
13818            self.write("(");
13819            self.generate_function_parameters(&cp.parameters)?;
13820            self.write(")");
13821        } else if !cp.parameters.is_empty() {
13822            // TSQL: unparenthesized parameters
13823            self.write_space();
13824            self.generate_function_parameters(&cp.parameters)?;
13825        }
13826
13827        // RETURNS clause (Snowflake)
13828        if let Some(return_type) = &cp.return_type {
13829            self.write_space();
13830            self.write_keyword("RETURNS");
13831            self.write_space();
13832            self.generate_data_type(return_type)?;
13833        }
13834
13835        // EXECUTE AS clause (Snowflake)
13836        if let Some(execute_as) = &cp.execute_as {
13837            self.write_space();
13838            self.write_keyword("EXECUTE AS");
13839            self.write_space();
13840            self.write_keyword(execute_as);
13841        }
13842
13843        if let Some(lang) = &cp.language {
13844            self.write_space();
13845            self.write_keyword("LANGUAGE");
13846            self.write_space();
13847            self.write(lang);
13848        }
13849
13850        if let Some(security) = &cp.security {
13851            self.write_space();
13852            self.write_keyword("SECURITY");
13853            self.write_space();
13854            match security {
13855                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
13856                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
13857                FunctionSecurity::None => self.write_keyword("NONE"),
13858            }
13859        }
13860
13861        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
13862        if !cp.with_options.is_empty() {
13863            self.write_space();
13864            self.write_keyword("WITH");
13865            self.write_space();
13866            for (i, opt) in cp.with_options.iter().enumerate() {
13867                if i > 0 {
13868                    self.write(", ");
13869                }
13870                self.write(opt);
13871            }
13872        }
13873
13874        if let Some(body) = &cp.body {
13875            self.write_space();
13876            match body {
13877                FunctionBody::Block(block) => {
13878                    self.write_keyword("AS");
13879                    if matches!(
13880                        self.config.dialect,
13881                        Some(crate::dialects::DialectType::TSQL)
13882                    ) {
13883                        self.write(" BEGIN ");
13884                        self.write(block);
13885                        self.write(" END");
13886                    } else if matches!(
13887                        self.config.dialect,
13888                        Some(crate::dialects::DialectType::PostgreSQL)
13889                    ) {
13890                        self.write(" $$");
13891                        self.write(block);
13892                        self.write("$$");
13893                    } else {
13894                        // Escape content for single-quoted output
13895                        let escaped = self.escape_block_for_single_quote(block);
13896                        self.write(" '");
13897                        self.write(&escaped);
13898                        self.write("'");
13899                    }
13900                }
13901                FunctionBody::StringLiteral(s) => {
13902                    self.write_keyword("AS");
13903                    self.write(" '");
13904                    self.write(s);
13905                    self.write("'");
13906                }
13907                FunctionBody::Expression(expr) => {
13908                    self.write_keyword("AS");
13909                    self.write_space();
13910                    self.generate_expression(expr)?;
13911                }
13912                FunctionBody::External(name) => {
13913                    self.write_keyword("EXTERNAL NAME");
13914                    self.write(" '");
13915                    self.write(name);
13916                    self.write("'");
13917                }
13918                FunctionBody::Return(expr) => {
13919                    self.write_keyword("RETURN");
13920                    self.write_space();
13921                    self.generate_expression(expr)?;
13922                }
13923                FunctionBody::Statements(stmts) => {
13924                    self.write_keyword("AS");
13925                    self.write(" BEGIN ");
13926                    for (i, stmt) in stmts.iter().enumerate() {
13927                        if i > 0 {
13928                            self.write(" ");
13929                        }
13930                        self.generate_expression(stmt)?;
13931                        self.write(";");
13932                    }
13933                    self.write(" END");
13934                }
13935                FunctionBody::RawBlock(text) => {
13936                    self.write_newline();
13937                    self.write(text);
13938                }
13939                FunctionBody::DollarQuoted { content, tag } => {
13940                    self.write_keyword("AS");
13941                    self.write(" ");
13942                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
13943                    let supports_dollar_quoting = matches!(
13944                        self.config.dialect,
13945                        Some(crate::dialects::DialectType::PostgreSQL)
13946                            | Some(crate::dialects::DialectType::Databricks)
13947                            | Some(crate::dialects::DialectType::Redshift)
13948                            | Some(crate::dialects::DialectType::DuckDB)
13949                    );
13950                    if supports_dollar_quoting {
13951                        // Output in dollar-quoted format
13952                        self.write("$");
13953                        if let Some(t) = tag {
13954                            self.write(t);
13955                        }
13956                        self.write("$");
13957                        self.write(content);
13958                        self.write("$");
13959                        if let Some(t) = tag {
13960                            self.write(t);
13961                        }
13962                        self.write("$");
13963                    } else {
13964                        // Convert to single-quoted string for other dialects
13965                        let escaped = self.escape_block_for_single_quote(content);
13966                        self.write("'");
13967                        self.write(&escaped);
13968                        self.write("'");
13969                    }
13970                }
13971            }
13972        }
13973
13974        Ok(())
13975    }
13976
13977    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
13978        self.write_keyword("DROP PROCEDURE");
13979
13980        if dp.if_exists {
13981            self.write_space();
13982            self.write_keyword("IF EXISTS");
13983        }
13984
13985        self.write_space();
13986        self.generate_table(&dp.name)?;
13987
13988        if let Some(params) = &dp.parameters {
13989            self.write(" (");
13990            for (i, dt) in params.iter().enumerate() {
13991                if i > 0 {
13992                    self.write(", ");
13993                }
13994                self.generate_data_type(dt)?;
13995            }
13996            self.write(")");
13997        }
13998
13999        if dp.cascade {
14000            self.write_space();
14001            self.write_keyword("CASCADE");
14002        }
14003
14004        Ok(())
14005    }
14006
14007    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
14008        self.write_keyword("CREATE");
14009
14010        if cs.or_replace {
14011            self.write_space();
14012            self.write_keyword("OR REPLACE");
14013        }
14014
14015        if cs.temporary {
14016            self.write_space();
14017            self.write_keyword("TEMPORARY");
14018        }
14019
14020        self.write_space();
14021        self.write_keyword("SEQUENCE");
14022
14023        if cs.if_not_exists {
14024            self.write_space();
14025            self.write_keyword("IF NOT EXISTS");
14026        }
14027
14028        self.write_space();
14029        self.generate_table(&cs.name)?;
14030
14031        // Output AS <type> if present
14032        if let Some(as_type) = &cs.as_type {
14033            self.write_space();
14034            self.write_keyword("AS");
14035            self.write_space();
14036            self.generate_data_type(as_type)?;
14037        }
14038
14039        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
14040        if let Some(comment) = &cs.comment {
14041            self.write_space();
14042            self.write_keyword("COMMENT");
14043            self.write("=");
14044            self.generate_string_literal(comment)?;
14045        }
14046
14047        // If property_order is available, use it to preserve original order
14048        if !cs.property_order.is_empty() {
14049            for prop in &cs.property_order {
14050                match prop {
14051                    SeqPropKind::Start => {
14052                        if let Some(start) = cs.start {
14053                            self.write_space();
14054                            self.write_keyword("START WITH");
14055                            self.write(&format!(" {}", start));
14056                        }
14057                    }
14058                    SeqPropKind::Increment => {
14059                        if let Some(inc) = cs.increment {
14060                            self.write_space();
14061                            self.write_keyword("INCREMENT BY");
14062                            self.write(&format!(" {}", inc));
14063                        }
14064                    }
14065                    SeqPropKind::Minvalue => {
14066                        if let Some(min) = &cs.minvalue {
14067                            self.write_space();
14068                            match min {
14069                                SequenceBound::Value(v) => {
14070                                    self.write_keyword("MINVALUE");
14071                                    self.write(&format!(" {}", v));
14072                                }
14073                                SequenceBound::None => {
14074                                    self.write_keyword("NO MINVALUE");
14075                                }
14076                            }
14077                        }
14078                    }
14079                    SeqPropKind::Maxvalue => {
14080                        if let Some(max) = &cs.maxvalue {
14081                            self.write_space();
14082                            match max {
14083                                SequenceBound::Value(v) => {
14084                                    self.write_keyword("MAXVALUE");
14085                                    self.write(&format!(" {}", v));
14086                                }
14087                                SequenceBound::None => {
14088                                    self.write_keyword("NO MAXVALUE");
14089                                }
14090                            }
14091                        }
14092                    }
14093                    SeqPropKind::Cache => {
14094                        if let Some(cache) = cs.cache {
14095                            self.write_space();
14096                            self.write_keyword("CACHE");
14097                            self.write(&format!(" {}", cache));
14098                        }
14099                    }
14100                    SeqPropKind::NoCache => {
14101                        self.write_space();
14102                        self.write_keyword("NO CACHE");
14103                    }
14104                    SeqPropKind::NoCacheWord => {
14105                        self.write_space();
14106                        self.write_keyword("NOCACHE");
14107                    }
14108                    SeqPropKind::Cycle => {
14109                        self.write_space();
14110                        self.write_keyword("CYCLE");
14111                    }
14112                    SeqPropKind::NoCycle => {
14113                        self.write_space();
14114                        self.write_keyword("NO CYCLE");
14115                    }
14116                    SeqPropKind::NoCycleWord => {
14117                        self.write_space();
14118                        self.write_keyword("NOCYCLE");
14119                    }
14120                    SeqPropKind::OwnedBy => {
14121                        // Skip OWNED BY NONE (it's a no-op)
14122                        if !cs.owned_by_none {
14123                            if let Some(owned) = &cs.owned_by {
14124                                self.write_space();
14125                                self.write_keyword("OWNED BY");
14126                                self.write_space();
14127                                self.generate_table(owned)?;
14128                            }
14129                        }
14130                    }
14131                    SeqPropKind::Order => {
14132                        self.write_space();
14133                        self.write_keyword("ORDER");
14134                    }
14135                    SeqPropKind::NoOrder => {
14136                        self.write_space();
14137                        self.write_keyword("NOORDER");
14138                    }
14139                    SeqPropKind::Comment => {
14140                        // COMMENT is output above, before property_order iteration
14141                    }
14142                    SeqPropKind::Sharing => {
14143                        if let Some(val) = &cs.sharing {
14144                            self.write_space();
14145                            self.write(&format!("SHARING={}", val));
14146                        }
14147                    }
14148                    SeqPropKind::Keep => {
14149                        self.write_space();
14150                        self.write_keyword("KEEP");
14151                    }
14152                    SeqPropKind::NoKeep => {
14153                        self.write_space();
14154                        self.write_keyword("NOKEEP");
14155                    }
14156                    SeqPropKind::Scale => {
14157                        self.write_space();
14158                        self.write_keyword("SCALE");
14159                        if let Some(modifier) = &cs.scale_modifier {
14160                            if !modifier.is_empty() {
14161                                self.write_space();
14162                                self.write_keyword(modifier);
14163                            }
14164                        }
14165                    }
14166                    SeqPropKind::NoScale => {
14167                        self.write_space();
14168                        self.write_keyword("NOSCALE");
14169                    }
14170                    SeqPropKind::Shard => {
14171                        self.write_space();
14172                        self.write_keyword("SHARD");
14173                        if let Some(modifier) = &cs.shard_modifier {
14174                            if !modifier.is_empty() {
14175                                self.write_space();
14176                                self.write_keyword(modifier);
14177                            }
14178                        }
14179                    }
14180                    SeqPropKind::NoShard => {
14181                        self.write_space();
14182                        self.write_keyword("NOSHARD");
14183                    }
14184                    SeqPropKind::Session => {
14185                        self.write_space();
14186                        self.write_keyword("SESSION");
14187                    }
14188                    SeqPropKind::Global => {
14189                        self.write_space();
14190                        self.write_keyword("GLOBAL");
14191                    }
14192                    SeqPropKind::NoMinvalueWord => {
14193                        self.write_space();
14194                        self.write_keyword("NOMINVALUE");
14195                    }
14196                    SeqPropKind::NoMaxvalueWord => {
14197                        self.write_space();
14198                        self.write_keyword("NOMAXVALUE");
14199                    }
14200                }
14201            }
14202        } else {
14203            // Fallback: default order for backwards compatibility
14204            if let Some(inc) = cs.increment {
14205                self.write_space();
14206                self.write_keyword("INCREMENT BY");
14207                self.write(&format!(" {}", inc));
14208            }
14209
14210            if let Some(min) = &cs.minvalue {
14211                self.write_space();
14212                match min {
14213                    SequenceBound::Value(v) => {
14214                        self.write_keyword("MINVALUE");
14215                        self.write(&format!(" {}", v));
14216                    }
14217                    SequenceBound::None => {
14218                        self.write_keyword("NO MINVALUE");
14219                    }
14220                }
14221            }
14222
14223            if let Some(max) = &cs.maxvalue {
14224                self.write_space();
14225                match max {
14226                    SequenceBound::Value(v) => {
14227                        self.write_keyword("MAXVALUE");
14228                        self.write(&format!(" {}", v));
14229                    }
14230                    SequenceBound::None => {
14231                        self.write_keyword("NO MAXVALUE");
14232                    }
14233                }
14234            }
14235
14236            if let Some(start) = cs.start {
14237                self.write_space();
14238                self.write_keyword("START WITH");
14239                self.write(&format!(" {}", start));
14240            }
14241
14242            if let Some(cache) = cs.cache {
14243                self.write_space();
14244                self.write_keyword("CACHE");
14245                self.write(&format!(" {}", cache));
14246            }
14247
14248            if cs.cycle {
14249                self.write_space();
14250                self.write_keyword("CYCLE");
14251            }
14252
14253            if let Some(owned) = &cs.owned_by {
14254                self.write_space();
14255                self.write_keyword("OWNED BY");
14256                self.write_space();
14257                self.generate_table(owned)?;
14258            }
14259        }
14260
14261        Ok(())
14262    }
14263
14264    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
14265        self.write_keyword("DROP SEQUENCE");
14266
14267        if ds.if_exists {
14268            self.write_space();
14269            self.write_keyword("IF EXISTS");
14270        }
14271
14272        self.write_space();
14273        self.generate_table(&ds.name)?;
14274
14275        if ds.cascade {
14276            self.write_space();
14277            self.write_keyword("CASCADE");
14278        }
14279
14280        Ok(())
14281    }
14282
14283    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
14284        self.write_keyword("ALTER SEQUENCE");
14285
14286        if als.if_exists {
14287            self.write_space();
14288            self.write_keyword("IF EXISTS");
14289        }
14290
14291        self.write_space();
14292        self.generate_table(&als.name)?;
14293
14294        if let Some(inc) = als.increment {
14295            self.write_space();
14296            self.write_keyword("INCREMENT BY");
14297            self.write(&format!(" {}", inc));
14298        }
14299
14300        if let Some(min) = &als.minvalue {
14301            self.write_space();
14302            match min {
14303                SequenceBound::Value(v) => {
14304                    self.write_keyword("MINVALUE");
14305                    self.write(&format!(" {}", v));
14306                }
14307                SequenceBound::None => {
14308                    self.write_keyword("NO MINVALUE");
14309                }
14310            }
14311        }
14312
14313        if let Some(max) = &als.maxvalue {
14314            self.write_space();
14315            match max {
14316                SequenceBound::Value(v) => {
14317                    self.write_keyword("MAXVALUE");
14318                    self.write(&format!(" {}", v));
14319                }
14320                SequenceBound::None => {
14321                    self.write_keyword("NO MAXVALUE");
14322                }
14323            }
14324        }
14325
14326        if let Some(start) = als.start {
14327            self.write_space();
14328            self.write_keyword("START WITH");
14329            self.write(&format!(" {}", start));
14330        }
14331
14332        if let Some(restart) = &als.restart {
14333            self.write_space();
14334            self.write_keyword("RESTART");
14335            if let Some(val) = restart {
14336                self.write_keyword(" WITH");
14337                self.write(&format!(" {}", val));
14338            }
14339        }
14340
14341        if let Some(cache) = als.cache {
14342            self.write_space();
14343            self.write_keyword("CACHE");
14344            self.write(&format!(" {}", cache));
14345        }
14346
14347        if let Some(cycle) = als.cycle {
14348            self.write_space();
14349            if cycle {
14350                self.write_keyword("CYCLE");
14351            } else {
14352                self.write_keyword("NO CYCLE");
14353            }
14354        }
14355
14356        if let Some(owned) = &als.owned_by {
14357            self.write_space();
14358            self.write_keyword("OWNED BY");
14359            self.write_space();
14360            if let Some(table) = owned {
14361                self.generate_table(table)?;
14362            } else {
14363                self.write_keyword("NONE");
14364            }
14365        }
14366
14367        Ok(())
14368    }
14369
14370    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
14371        self.write_keyword("CREATE");
14372
14373        if ct.or_alter {
14374            self.write_space();
14375            self.write_keyword("OR ALTER");
14376        } else if ct.or_replace {
14377            self.write_space();
14378            self.write_keyword("OR REPLACE");
14379        }
14380
14381        if ct.constraint {
14382            self.write_space();
14383            self.write_keyword("CONSTRAINT");
14384        }
14385
14386        self.write_space();
14387        self.write_keyword("TRIGGER");
14388        self.write_space();
14389        self.generate_identifier(&ct.name)?;
14390
14391        self.write_space();
14392        match ct.timing {
14393            TriggerTiming::Before => self.write_keyword("BEFORE"),
14394            TriggerTiming::After => self.write_keyword("AFTER"),
14395            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
14396        }
14397
14398        // Events
14399        for (i, event) in ct.events.iter().enumerate() {
14400            if i > 0 {
14401                self.write_keyword(" OR");
14402            }
14403            self.write_space();
14404            match event {
14405                TriggerEvent::Insert => self.write_keyword("INSERT"),
14406                TriggerEvent::Update(cols) => {
14407                    self.write_keyword("UPDATE");
14408                    if let Some(cols) = cols {
14409                        self.write_space();
14410                        self.write_keyword("OF");
14411                        for (j, col) in cols.iter().enumerate() {
14412                            if j > 0 {
14413                                self.write(",");
14414                            }
14415                            self.write_space();
14416                            self.generate_identifier(col)?;
14417                        }
14418                    }
14419                }
14420                TriggerEvent::Delete => self.write_keyword("DELETE"),
14421                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
14422            }
14423        }
14424
14425        self.write_space();
14426        self.write_keyword("ON");
14427        self.write_space();
14428        self.generate_table(&ct.table)?;
14429
14430        // Referencing clause
14431        if let Some(ref_clause) = &ct.referencing {
14432            self.write_space();
14433            self.write_keyword("REFERENCING");
14434            if let Some(old_table) = &ref_clause.old_table {
14435                self.write_space();
14436                self.write_keyword("OLD TABLE AS");
14437                self.write_space();
14438                self.generate_identifier(old_table)?;
14439            }
14440            if let Some(new_table) = &ref_clause.new_table {
14441                self.write_space();
14442                self.write_keyword("NEW TABLE AS");
14443                self.write_space();
14444                self.generate_identifier(new_table)?;
14445            }
14446            if let Some(old_row) = &ref_clause.old_row {
14447                self.write_space();
14448                self.write_keyword("OLD ROW AS");
14449                self.write_space();
14450                self.generate_identifier(old_row)?;
14451            }
14452            if let Some(new_row) = &ref_clause.new_row {
14453                self.write_space();
14454                self.write_keyword("NEW ROW AS");
14455                self.write_space();
14456                self.generate_identifier(new_row)?;
14457            }
14458        }
14459
14460        // Deferrable options for constraint triggers (must come before FOR EACH)
14461        if let Some(deferrable) = ct.deferrable {
14462            self.write_space();
14463            if deferrable {
14464                self.write_keyword("DEFERRABLE");
14465            } else {
14466                self.write_keyword("NOT DEFERRABLE");
14467            }
14468        }
14469
14470        if let Some(initially) = ct.initially_deferred {
14471            self.write_space();
14472            self.write_keyword("INITIALLY");
14473            self.write_space();
14474            if initially {
14475                self.write_keyword("DEFERRED");
14476            } else {
14477                self.write_keyword("IMMEDIATE");
14478            }
14479        }
14480
14481        if let Some(for_each) = ct.for_each {
14482            self.write_space();
14483            self.write_keyword("FOR EACH");
14484            self.write_space();
14485            match for_each {
14486                TriggerForEach::Row => self.write_keyword("ROW"),
14487                TriggerForEach::Statement => self.write_keyword("STATEMENT"),
14488            }
14489        }
14490
14491        // When clause
14492        if let Some(when) = &ct.when {
14493            self.write_space();
14494            self.write_keyword("WHEN");
14495            if ct.when_paren {
14496                self.write(" (");
14497                self.generate_expression(when)?;
14498                self.write(")");
14499            } else {
14500                self.write_space();
14501                self.generate_expression(when)?;
14502            }
14503        }
14504
14505        // Body
14506        self.write_space();
14507        match &ct.body {
14508            TriggerBody::Execute { function, args } => {
14509                self.write_keyword("EXECUTE FUNCTION");
14510                self.write_space();
14511                self.generate_table(function)?;
14512                self.write("(");
14513                for (i, arg) in args.iter().enumerate() {
14514                    if i > 0 {
14515                        self.write(", ");
14516                    }
14517                    self.generate_expression(arg)?;
14518                }
14519                self.write(")");
14520            }
14521            TriggerBody::Block(block) => {
14522                self.write_keyword("BEGIN");
14523                self.write_space();
14524                self.write(block);
14525                self.write_space();
14526                self.write_keyword("END");
14527            }
14528        }
14529
14530        Ok(())
14531    }
14532
14533    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
14534        self.write_keyword("DROP TRIGGER");
14535
14536        if dt.if_exists {
14537            self.write_space();
14538            self.write_keyword("IF EXISTS");
14539        }
14540
14541        self.write_space();
14542        self.generate_identifier(&dt.name)?;
14543
14544        if let Some(table) = &dt.table {
14545            self.write_space();
14546            self.write_keyword("ON");
14547            self.write_space();
14548            self.generate_table(table)?;
14549        }
14550
14551        if dt.cascade {
14552            self.write_space();
14553            self.write_keyword("CASCADE");
14554        }
14555
14556        Ok(())
14557    }
14558
14559    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
14560        self.write_keyword("CREATE TYPE");
14561
14562        if ct.if_not_exists {
14563            self.write_space();
14564            self.write_keyword("IF NOT EXISTS");
14565        }
14566
14567        self.write_space();
14568        self.generate_table(&ct.name)?;
14569
14570        if let TypeDefinition::Base {
14571            input,
14572            output,
14573            internallength,
14574        } = &ct.definition
14575        {
14576            if input.is_empty() && output.is_empty() && internallength.is_none() {
14577                return Ok(());
14578            }
14579        }
14580
14581        self.write_space();
14582        self.write_keyword("AS");
14583        self.write_space();
14584
14585        match &ct.definition {
14586            TypeDefinition::Enum(values) => {
14587                self.write_keyword("ENUM");
14588                self.write(" (");
14589                for (i, val) in values.iter().enumerate() {
14590                    if i > 0 {
14591                        self.write(", ");
14592                    }
14593                    self.write(&format!("'{}'", val));
14594                }
14595                self.write(")");
14596            }
14597            TypeDefinition::Composite(attrs) => {
14598                self.write("(");
14599                for (i, attr) in attrs.iter().enumerate() {
14600                    if i > 0 {
14601                        self.write(", ");
14602                    }
14603                    self.generate_identifier(&attr.name)?;
14604                    self.write_space();
14605                    self.generate_data_type(&attr.data_type)?;
14606                    if let Some(collate) = &attr.collate {
14607                        self.write_space();
14608                        self.write_keyword("COLLATE");
14609                        self.write_space();
14610                        self.generate_identifier(collate)?;
14611                    }
14612                }
14613                self.write(")");
14614            }
14615            TypeDefinition::Range {
14616                subtype,
14617                subtype_diff,
14618                canonical,
14619            } => {
14620                self.write_keyword("RANGE");
14621                self.write(" (");
14622                if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
14623                    self.write("subtype");
14624                } else {
14625                    self.write_keyword("SUBTYPE");
14626                }
14627                self.write(" = ");
14628                self.generate_data_type(subtype)?;
14629                if let Some(diff) = subtype_diff {
14630                    self.write(", ");
14631                    if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
14632                        self.write("subtype_diff");
14633                    } else {
14634                        self.write_keyword("SUBTYPE_DIFF");
14635                    }
14636                    self.write(" = ");
14637                    self.write(diff);
14638                }
14639                if let Some(canon) = canonical {
14640                    self.write(", ");
14641                    if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
14642                        self.write("canonical");
14643                    } else {
14644                        self.write_keyword("CANONICAL");
14645                    }
14646                    self.write(" = ");
14647                    self.write(canon);
14648                }
14649                self.write(")");
14650            }
14651            TypeDefinition::Base {
14652                input,
14653                output,
14654                internallength,
14655            } => {
14656                self.write("(");
14657                self.write_keyword("INPUT");
14658                self.write(" = ");
14659                self.write(input);
14660                self.write(", ");
14661                self.write_keyword("OUTPUT");
14662                self.write(" = ");
14663                self.write(output);
14664                if let Some(len) = internallength {
14665                    self.write(", ");
14666                    self.write_keyword("INTERNALLENGTH");
14667                    self.write(" = ");
14668                    self.write(&len.to_string());
14669                }
14670                self.write(")");
14671            }
14672            TypeDefinition::Domain {
14673                base_type,
14674                default,
14675                constraints,
14676            } => {
14677                self.generate_data_type(base_type)?;
14678                if let Some(def) = default {
14679                    self.write_space();
14680                    self.write_keyword("DEFAULT");
14681                    self.write_space();
14682                    self.generate_expression(def)?;
14683                }
14684                for constr in constraints {
14685                    self.write_space();
14686                    if let Some(name) = &constr.name {
14687                        self.write_keyword("CONSTRAINT");
14688                        self.write_space();
14689                        self.generate_identifier(name)?;
14690                        self.write_space();
14691                    }
14692                    self.write_keyword("CHECK");
14693                    self.write(" (");
14694                    self.generate_expression(&constr.check)?;
14695                    self.write(")");
14696                }
14697            }
14698        }
14699
14700        Ok(())
14701    }
14702
14703    fn generate_create_task(&mut self, task: &crate::expressions::CreateTask) -> Result<()> {
14704        self.write_keyword("CREATE");
14705        if task.or_replace {
14706            self.write_space();
14707            self.write_keyword("OR REPLACE");
14708        }
14709        self.write_space();
14710        self.write_keyword("TASK");
14711        if task.if_not_exists {
14712            self.write_space();
14713            self.write_keyword("IF NOT EXISTS");
14714        }
14715        self.write_space();
14716        self.write(&task.name);
14717        if !task.properties.is_empty() {
14718            // Properties already include leading whitespace from tokens_to_sql
14719            if !task.properties.starts_with('\n') && !task.properties.starts_with(' ') {
14720                self.write_space();
14721            }
14722            self.write(&task.properties);
14723        }
14724        self.write_space();
14725        self.write_keyword("AS");
14726        self.write_space();
14727        self.generate_expression(&task.body)?;
14728        Ok(())
14729    }
14730
14731    fn generate_try_catch(&mut self, try_catch: &TryCatch) -> Result<()> {
14732        self.write_keyword("BEGIN TRY");
14733        self.generate_tsql_block_statements(&try_catch.try_body)?;
14734        self.write_keyword("END TRY");
14735
14736        if let Some(catch_body) = &try_catch.catch_body {
14737            if self.config.pretty {
14738                self.write_newline();
14739                self.write_indent();
14740            } else {
14741                self.write_space();
14742            }
14743            self.write_keyword("BEGIN CATCH");
14744            self.generate_tsql_block_statements(catch_body)?;
14745            self.write_keyword("END CATCH");
14746        }
14747
14748        Ok(())
14749    }
14750
14751    fn generate_tsql_block_statements(&mut self, statements: &[Expression]) -> Result<()> {
14752        if statements.is_empty() {
14753            self.write_space();
14754            return Ok(());
14755        }
14756
14757        if self.config.pretty {
14758            self.indent_level += 1;
14759            for stmt in statements {
14760                self.write_newline();
14761                self.write_indent();
14762                self.generate_expression(stmt)?;
14763                self.write(";");
14764            }
14765            self.indent_level -= 1;
14766            self.write_newline();
14767            self.write_indent();
14768        } else {
14769            self.write_space();
14770            for (i, stmt) in statements.iter().enumerate() {
14771                if i > 0 {
14772                    self.write_space();
14773                }
14774                self.generate_expression(stmt)?;
14775                self.write(";");
14776            }
14777            self.write_space();
14778        }
14779
14780        Ok(())
14781    }
14782
14783    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
14784        self.write_keyword("DROP TYPE");
14785
14786        if dt.if_exists {
14787            self.write_space();
14788            self.write_keyword("IF EXISTS");
14789        }
14790
14791        self.write_space();
14792        self.generate_table(&dt.name)?;
14793
14794        if dt.cascade {
14795            self.write_space();
14796            self.write_keyword("CASCADE");
14797        }
14798
14799        Ok(())
14800    }
14801
14802    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
14803        // Athena: DESCRIBE uses Hive engine (backticks)
14804        let saved_athena_hive_context = self.athena_hive_context;
14805        if matches!(
14806            self.config.dialect,
14807            Some(crate::dialects::DialectType::Athena)
14808        ) {
14809            self.athena_hive_context = true;
14810        }
14811
14812        // Output leading comments before DESCRIBE
14813        for comment in &d.leading_comments {
14814            self.write_formatted_comment(comment);
14815            self.write(" ");
14816        }
14817
14818        self.write_keyword("DESCRIBE");
14819
14820        if d.extended {
14821            self.write_space();
14822            self.write_keyword("EXTENDED");
14823        } else if d.formatted {
14824            self.write_space();
14825            self.write_keyword("FORMATTED");
14826        }
14827
14828        // Output style like ANALYZE, HISTORY
14829        if let Some(ref style) = d.style {
14830            self.write_space();
14831            self.write_keyword(style);
14832        }
14833
14834        // Handle object kind (TABLE, VIEW) based on dialect
14835        let should_output_kind = match self.config.dialect {
14836            // Spark doesn't use TABLE/VIEW after DESCRIBE
14837            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
14838                false
14839            }
14840            // Snowflake always includes TABLE
14841            Some(DialectType::Snowflake) => true,
14842            _ => d.kind.is_some(),
14843        };
14844        if should_output_kind {
14845            if let Some(ref kind) = d.kind {
14846                self.write_space();
14847                self.write_keyword(kind);
14848            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
14849                self.write_space();
14850                self.write_keyword("TABLE");
14851            }
14852        }
14853
14854        self.write_space();
14855        self.generate_expression(&d.target)?;
14856
14857        // Output parenthesized parameter types for PROCEDURE/FUNCTION
14858        if !d.params.is_empty() {
14859            self.write("(");
14860            for (i, param) in d.params.iter().enumerate() {
14861                if i > 0 {
14862                    self.write(", ");
14863                }
14864                self.write(param);
14865            }
14866            self.write(")");
14867        }
14868
14869        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
14870        if let Some(ref partition) = d.partition {
14871            self.write_space();
14872            self.generate_expression(partition)?;
14873        }
14874
14875        // Databricks: AS JSON
14876        if d.as_json {
14877            self.write_space();
14878            self.write_keyword("AS JSON");
14879        }
14880
14881        // Output properties like type=stage
14882        for (name, value) in &d.properties {
14883            self.write_space();
14884            self.write(name);
14885            self.write("=");
14886            self.write(value);
14887        }
14888
14889        // Restore Athena Hive context
14890        self.athena_hive_context = saved_athena_hive_context;
14891
14892        Ok(())
14893    }
14894
14895    /// Generate SHOW statement (Snowflake, MySQL, etc.)
14896    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
14897    fn generate_show(&mut self, s: &Show) -> Result<()> {
14898        self.write_keyword("SHOW");
14899        self.write_space();
14900
14901        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
14902        // where TERSE is syntactically valid but has no effect on output
14903        let show_terse = s.terse
14904            && !matches!(
14905                s.this.as_str(),
14906                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
14907            );
14908        if show_terse {
14909            self.write_keyword("TERSE");
14910            self.write_space();
14911        }
14912
14913        // Object type (USERS, TABLES, DATABASES, etc.)
14914        self.write_keyword(&s.this);
14915
14916        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
14917        if let Some(ref target_expr) = s.target {
14918            self.write_space();
14919            self.generate_expression(target_expr)?;
14920        }
14921
14922        // HISTORY keyword
14923        if s.history {
14924            self.write_space();
14925            self.write_keyword("HISTORY");
14926        }
14927
14928        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
14929        if let Some(ref for_target) = s.for_target {
14930            self.write_space();
14931            self.write_keyword("FOR");
14932            self.write_space();
14933            self.generate_expression(for_target)?;
14934        }
14935
14936        // Determine ordering based on dialect:
14937        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
14938        // - MySQL: IN, FROM, LIKE (when FROM is present)
14939        use crate::dialects::DialectType;
14940        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
14941        let is_mysql = matches!(self.config.dialect, Some(DialectType::MySQL));
14942        let mysql_tables_scope_as_from = is_mysql
14943            && matches!(s.this.as_str(), "TABLES" | "FULL TABLES")
14944            && s.scope_kind.as_deref() == Some("SCHEMA")
14945            && s.scope.is_some()
14946            && s.from.is_none();
14947
14948        if !is_snowflake && s.from.is_some() {
14949            // MySQL ordering: IN, FROM, LIKE
14950
14951            // IN scope_kind [scope]
14952            if let Some(ref scope_kind) = s.scope_kind {
14953                self.write_space();
14954                self.write_keyword("IN");
14955                self.write_space();
14956                self.write_keyword(scope_kind);
14957                if let Some(ref scope) = s.scope {
14958                    self.write_space();
14959                    self.generate_expression(scope)?;
14960                }
14961            } else if let Some(ref scope) = s.scope {
14962                self.write_space();
14963                self.write_keyword("IN");
14964                self.write_space();
14965                self.generate_expression(scope)?;
14966            }
14967
14968            // FROM clause
14969            if let Some(ref from) = s.from {
14970                self.write_space();
14971                self.write_keyword("FROM");
14972                self.write_space();
14973                self.generate_expression(from)?;
14974            }
14975
14976            // Second FROM clause (db name)
14977            if let Some(ref db) = s.db {
14978                self.write_space();
14979                self.write_keyword("FROM");
14980                self.write_space();
14981                self.generate_expression(db)?;
14982            }
14983
14984            // LIKE pattern
14985            if let Some(ref like) = s.like {
14986                self.write_space();
14987                self.write_keyword("LIKE");
14988                self.write_space();
14989                self.generate_expression(like)?;
14990            }
14991        } else {
14992            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
14993
14994            // LIKE pattern
14995            if let Some(ref like) = s.like {
14996                self.write_space();
14997                self.write_keyword("LIKE");
14998                self.write_space();
14999                self.generate_expression(like)?;
15000            }
15001
15002            // IN scope_kind [scope]
15003            if mysql_tables_scope_as_from {
15004                self.write_space();
15005                self.write_keyword("FROM");
15006                self.write_space();
15007                self.generate_expression(s.scope.as_ref().unwrap())?;
15008            } else if let Some(ref scope_kind) = s.scope_kind {
15009                self.write_space();
15010                self.write_keyword("IN");
15011                self.write_space();
15012                self.write_keyword(scope_kind);
15013                if let Some(ref scope) = s.scope {
15014                    self.write_space();
15015                    self.generate_expression(scope)?;
15016                }
15017            } else if let Some(ref scope) = s.scope {
15018                self.write_space();
15019                self.write_keyword("IN");
15020                self.write_space();
15021                self.generate_expression(scope)?;
15022            }
15023        }
15024
15025        // STARTS WITH pattern
15026        if let Some(ref starts_with) = s.starts_with {
15027            self.write_space();
15028            self.write_keyword("STARTS WITH");
15029            self.write_space();
15030            self.generate_expression(starts_with)?;
15031        }
15032
15033        // LIMIT clause
15034        if let Some(ref limit) = s.limit {
15035            self.write_space();
15036            self.generate_limit(limit)?;
15037        }
15038
15039        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
15040        if is_snowflake {
15041            if let Some(ref from) = s.from {
15042                self.write_space();
15043                self.write_keyword("FROM");
15044                self.write_space();
15045                self.generate_expression(from)?;
15046            }
15047        }
15048
15049        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
15050        if let Some(ref where_clause) = s.where_clause {
15051            self.write_space();
15052            self.write_keyword("WHERE");
15053            self.write_space();
15054            self.generate_expression(where_clause)?;
15055        }
15056
15057        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
15058        if let Some(is_mutex) = s.mutex {
15059            self.write_space();
15060            if is_mutex {
15061                self.write_keyword("MUTEX");
15062            } else {
15063                self.write_keyword("STATUS");
15064            }
15065        }
15066
15067        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
15068        if !s.privileges.is_empty() {
15069            self.write_space();
15070            self.write_keyword("WITH PRIVILEGES");
15071            self.write_space();
15072            for (i, priv_name) in s.privileges.iter().enumerate() {
15073                if i > 0 {
15074                    self.write(", ");
15075                }
15076                self.write_keyword(priv_name);
15077            }
15078        }
15079
15080        Ok(())
15081    }
15082
15083    // ==================== End DDL Generation ====================
15084
15085    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
15086        use crate::dialects::DialectType;
15087        match lit {
15088            Literal::String(s) => {
15089                self.generate_string_literal(s)?;
15090            }
15091            Literal::Number(n) => {
15092                if matches!(self.config.dialect, Some(DialectType::MySQL))
15093                    && n.len() > 2
15094                    && (n.starts_with("0x") || n.starts_with("0X"))
15095                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
15096                {
15097                    return self.generate_identifier(&Identifier {
15098                        name: n.clone(),
15099                        quoted: true,
15100                        trailing_comments: Vec::new(),
15101                        span: None,
15102                    });
15103                }
15104                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
15105                // for dialects that don't support them (MySQL interprets as identifier).
15106                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
15107                let n = if n.contains('_')
15108                    && !matches!(
15109                        self.config.dialect,
15110                        Some(DialectType::ClickHouse)
15111                            | Some(DialectType::DuckDB)
15112                            | Some(DialectType::PostgreSQL)
15113                            | Some(DialectType::Hive)
15114                            | Some(DialectType::Spark)
15115                            | Some(DialectType::Databricks)
15116                    ) {
15117                    std::borrow::Cow::Owned(n.replace('_', ""))
15118                } else {
15119                    std::borrow::Cow::Borrowed(n.as_str())
15120                };
15121                // Normalize numbers starting with decimal point to have leading zero
15122                // e.g., .25 -> 0.25 (matches sqlglot behavior)
15123                if n.starts_with('.') {
15124                    self.write("0");
15125                    self.write(&n);
15126                } else if n.starts_with("-.") {
15127                    // Handle negative numbers like -.25 -> -0.25
15128                    self.write("-0");
15129                    self.write(&n[1..]);
15130                } else {
15131                    self.write(&n);
15132                }
15133            }
15134            Literal::HexString(h) => {
15135                // Most dialects use lowercase x'...' for hex literals.
15136                match self.config.dialect {
15137                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
15138                        self.write("0x");
15139                        self.write(h);
15140                        return Ok(());
15141                    }
15142                    Some(DialectType::Spark)
15143                    | Some(DialectType::Databricks)
15144                    | Some(DialectType::Teradata) => self.write("X'"),
15145                    _ => self.write("x'"),
15146                }
15147                self.write(h);
15148                self.write("'");
15149            }
15150            Literal::HexNumber(h) => {
15151                // Hex number (0xA) - integer in hex notation (from BigQuery)
15152                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
15153                // For other dialects, convert to decimal integer
15154                match self.config.dialect {
15155                    Some(DialectType::BigQuery)
15156                    | Some(DialectType::ClickHouse)
15157                    | Some(DialectType::TSQL)
15158                    | Some(DialectType::Fabric) => {
15159                        self.write("0x");
15160                        self.write(h);
15161                    }
15162                    _ => {
15163                        // Convert hex to decimal
15164                        if let Ok(val) = u64::from_str_radix(h, 16) {
15165                            self.write(&val.to_string());
15166                        } else {
15167                            // Fallback: keep as 0x notation
15168                            self.write("0x");
15169                            self.write(h);
15170                        }
15171                    }
15172                }
15173            }
15174            Literal::BitString(b) => {
15175                // Bit string B'0101...'
15176                self.write("B'");
15177                self.write(b);
15178                self.write("'");
15179            }
15180            Literal::ByteString(b) => {
15181                // Byte string b'...' (BigQuery style)
15182                self.write("b'");
15183                // Escape special characters for output
15184                self.write_escaped_byte_string(b);
15185                self.write("'");
15186            }
15187            Literal::NationalString(s) => {
15188                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
15189                // Other dialects strip the N prefix and output as regular string
15190                let keep_n_prefix = matches!(
15191                    self.config.dialect,
15192                    Some(DialectType::TSQL)
15193                        | Some(DialectType::Oracle)
15194                        | Some(DialectType::MySQL)
15195                        | None
15196                );
15197                if keep_n_prefix {
15198                    self.write("N'");
15199                } else {
15200                    self.write("'");
15201                }
15202                self.write(s);
15203                self.write("'");
15204            }
15205            Literal::Date(d) => {
15206                self.generate_date_literal(d)?;
15207            }
15208            Literal::Time(t) => {
15209                self.generate_time_literal(t)?;
15210            }
15211            Literal::Timestamp(ts) => {
15212                self.generate_timestamp_literal(ts)?;
15213            }
15214            Literal::Datetime(dt) => {
15215                self.generate_datetime_literal(dt)?;
15216            }
15217            Literal::TripleQuotedString(s, _quote_char) => {
15218                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
15219                if matches!(
15220                    self.config.dialect,
15221                    Some(crate::dialects::DialectType::BigQuery)
15222                        | Some(crate::dialects::DialectType::DuckDB)
15223                        | Some(crate::dialects::DialectType::Snowflake)
15224                        | Some(crate::dialects::DialectType::Spark)
15225                        | Some(crate::dialects::DialectType::Hive)
15226                        | Some(crate::dialects::DialectType::Presto)
15227                        | Some(crate::dialects::DialectType::Trino)
15228                        | Some(crate::dialects::DialectType::PostgreSQL)
15229                        | Some(crate::dialects::DialectType::MySQL)
15230                        | Some(crate::dialects::DialectType::Redshift)
15231                        | Some(crate::dialects::DialectType::TSQL)
15232                        | Some(crate::dialects::DialectType::Oracle)
15233                        | Some(crate::dialects::DialectType::ClickHouse)
15234                        | Some(crate::dialects::DialectType::Databricks)
15235                        | Some(crate::dialects::DialectType::SQLite)
15236                ) {
15237                    self.generate_string_literal(s)?;
15238                } else {
15239                    // Preserve triple-quoted string syntax for generic/unknown dialects
15240                    let quotes = format!("{0}{0}{0}", _quote_char);
15241                    self.write(&quotes);
15242                    self.write(s);
15243                    self.write(&quotes);
15244                }
15245            }
15246            Literal::EscapeString(s) => {
15247                // PostgreSQL escape string: e'...' or E'...'
15248                // Token text format is "e:content" or "E:content"
15249                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
15250                use crate::dialects::DialectType;
15251                let content = if let Some(c) = s.strip_prefix("e:") {
15252                    c
15253                } else if let Some(c) = s.strip_prefix("E:") {
15254                    c
15255                } else {
15256                    s.as_str()
15257                };
15258
15259                // MySQL strips the PostgreSQL E prefix but still emits a string literal.
15260                if matches!(
15261                    self.config.dialect,
15262                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
15263                ) {
15264                    self.write("'");
15265                    self.write(&content.replace('\'', "''"));
15266                    self.write("'");
15267                } else {
15268                    // Some dialects use lowercase e' prefix
15269                    let prefix = if matches!(
15270                        self.config.dialect,
15271                        Some(DialectType::SingleStore)
15272                            | Some(DialectType::DuckDB)
15273                            | Some(DialectType::PostgreSQL)
15274                            | Some(DialectType::CockroachDB)
15275                            | Some(DialectType::Materialize)
15276                            | Some(DialectType::RisingWave)
15277                    ) {
15278                        "e'"
15279                    } else {
15280                        "E'"
15281                    };
15282
15283                    // Normalize \' to '' for output
15284                    let normalized = content.replace("\\'", "''");
15285                    self.write(prefix);
15286                    self.write(&normalized);
15287                    self.write("'");
15288                }
15289            }
15290            Literal::DollarString(s) => {
15291                // Convert dollar-quoted strings to single-quoted strings
15292                // (like Python sqlglot's rawstring_sql)
15293                use crate::dialects::DialectType;
15294                // Extract content from tag\x00content format
15295                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
15296                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
15297                let escape_backslash = matches!(
15298                    self.config.dialect,
15299                    Some(DialectType::ClickHouse) | Some(DialectType::Snowflake)
15300                );
15301                // Step 2: Determine quote escaping style
15302                // Snowflake: ' -> \' (backslash escape)
15303                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
15304                let use_backslash_quote =
15305                    matches!(self.config.dialect, Some(DialectType::Snowflake));
15306
15307                let mut escaped = String::with_capacity(content.len() + 4);
15308                for ch in content.chars() {
15309                    if escape_backslash && ch == '\\' {
15310                        // Escape backslash first (before quote escaping)
15311                        escaped.push('\\');
15312                        escaped.push('\\');
15313                    } else if ch == '\'' {
15314                        if use_backslash_quote {
15315                            escaped.push('\\');
15316                            escaped.push('\'');
15317                        } else {
15318                            escaped.push('\'');
15319                            escaped.push('\'');
15320                        }
15321                    } else {
15322                        escaped.push(ch);
15323                    }
15324                }
15325                self.write("'");
15326                self.write(&escaped);
15327                self.write("'");
15328            }
15329            Literal::RawString(s) => {
15330                // Raw strings (r"..." or r'...') contain literal backslashes.
15331                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
15332                // 1. If \\ is in STRING_ESCAPES, double all backslashes
15333                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
15334                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
15335                use crate::dialects::DialectType;
15336
15337                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
15338                let escape_backslash = matches!(
15339                    self.config.dialect,
15340                    Some(DialectType::BigQuery)
15341                        | Some(DialectType::MySQL)
15342                        | Some(DialectType::SingleStore)
15343                        | Some(DialectType::TiDB)
15344                        | Some(DialectType::Hive)
15345                        | Some(DialectType::Spark)
15346                        | Some(DialectType::Databricks)
15347                        | Some(DialectType::Drill)
15348                        | Some(DialectType::Snowflake)
15349                        | Some(DialectType::Redshift)
15350                        | Some(DialectType::ClickHouse)
15351                );
15352
15353                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
15354                // These escape quotes as \' instead of ''
15355                let backslash_escapes_quote = matches!(
15356                    self.config.dialect,
15357                    Some(DialectType::BigQuery)
15358                        | Some(DialectType::Hive)
15359                        | Some(DialectType::Spark)
15360                        | Some(DialectType::Databricks)
15361                        | Some(DialectType::Drill)
15362                        | Some(DialectType::Snowflake)
15363                        | Some(DialectType::Redshift)
15364                );
15365
15366                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
15367                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
15368                let supports_escape_sequences = escape_backslash;
15369
15370                let mut escaped = String::with_capacity(s.len() + 4);
15371                for ch in s.chars() {
15372                    if escape_backslash && ch == '\\' {
15373                        // Double the backslash for the target dialect
15374                        escaped.push('\\');
15375                        escaped.push('\\');
15376                    } else if ch == '\'' {
15377                        if backslash_escapes_quote {
15378                            // Use backslash to escape the quote: \'
15379                            escaped.push('\\');
15380                            escaped.push('\'');
15381                        } else {
15382                            // Use SQL standard quote doubling: ''
15383                            escaped.push('\'');
15384                            escaped.push('\'');
15385                        }
15386                    } else if supports_escape_sequences {
15387                        // Apply ESCAPED_SEQUENCES mapping for special chars
15388                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
15389                        match ch {
15390                            '\n' => {
15391                                escaped.push('\\');
15392                                escaped.push('n');
15393                            }
15394                            '\r' => {
15395                                escaped.push('\\');
15396                                escaped.push('r');
15397                            }
15398                            '\t' => {
15399                                escaped.push('\\');
15400                                escaped.push('t');
15401                            }
15402                            '\x07' => {
15403                                escaped.push('\\');
15404                                escaped.push('a');
15405                            }
15406                            '\x08' => {
15407                                escaped.push('\\');
15408                                escaped.push('b');
15409                            }
15410                            '\x0C' => {
15411                                escaped.push('\\');
15412                                escaped.push('f');
15413                            }
15414                            '\x0B' => {
15415                                escaped.push('\\');
15416                                escaped.push('v');
15417                            }
15418                            _ => escaped.push(ch),
15419                        }
15420                    } else {
15421                        escaped.push(ch);
15422                    }
15423                }
15424                self.write("'");
15425                self.write(&escaped);
15426                self.write("'");
15427            }
15428        }
15429        Ok(())
15430    }
15431
15432    /// Generate a DATE literal with dialect-specific formatting
15433    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
15434        use crate::dialects::DialectType;
15435
15436        match self.config.dialect {
15437            // SQL Server / Fabric use CONVERT or CAST
15438            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
15439                self.write("CAST('");
15440                self.write(d);
15441                self.write("' AS DATE)");
15442            }
15443            // BigQuery uses CAST syntax for type literals
15444            // DATE 'value' -> CAST('value' AS DATE)
15445            Some(DialectType::BigQuery) => {
15446                self.write("CAST('");
15447                self.write(d);
15448                self.write("' AS DATE)");
15449            }
15450            // Exasol uses CAST syntax for DATE literals
15451            // DATE 'value' -> CAST('value' AS DATE)
15452            Some(DialectType::Exasol) => {
15453                self.write("CAST('");
15454                self.write(d);
15455                self.write("' AS DATE)");
15456            }
15457            // Snowflake uses CAST syntax for DATE literals
15458            // DATE 'value' -> CAST('value' AS DATE)
15459            Some(DialectType::Snowflake) => {
15460                self.write("CAST('");
15461                self.write(d);
15462                self.write("' AS DATE)");
15463            }
15464            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
15465            Some(DialectType::PostgreSQL)
15466            | Some(DialectType::MySQL)
15467            | Some(DialectType::SingleStore)
15468            | Some(DialectType::TiDB)
15469            | Some(DialectType::Redshift) => {
15470                self.write("CAST('");
15471                self.write(d);
15472                self.write("' AS DATE)");
15473            }
15474            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
15475            Some(DialectType::DuckDB)
15476            | Some(DialectType::Presto)
15477            | Some(DialectType::Trino)
15478            | Some(DialectType::Athena)
15479            | Some(DialectType::Spark)
15480            | Some(DialectType::Databricks)
15481            | Some(DialectType::Hive) => {
15482                self.write("CAST('");
15483                self.write(d);
15484                self.write("' AS DATE)");
15485            }
15486            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
15487            Some(DialectType::Oracle) => {
15488                self.write("TO_DATE('");
15489                self.write(d);
15490                self.write("', 'YYYY-MM-DD')");
15491            }
15492            // Standard SQL: DATE '...'
15493            _ => {
15494                self.write_keyword("DATE");
15495                self.write(" '");
15496                self.write(d);
15497                self.write("'");
15498            }
15499        }
15500        Ok(())
15501    }
15502
15503    /// Generate a TIME literal with dialect-specific formatting
15504    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
15505        use crate::dialects::DialectType;
15506
15507        match self.config.dialect {
15508            // SQL Server uses CONVERT or CAST
15509            Some(DialectType::TSQL) => {
15510                self.write("CAST('");
15511                self.write(t);
15512                self.write("' AS TIME)");
15513            }
15514            // Standard SQL: TIME '...'
15515            _ => {
15516                self.write_keyword("TIME");
15517                self.write(" '");
15518                self.write(t);
15519                self.write("'");
15520            }
15521        }
15522        Ok(())
15523    }
15524
15525    /// Generate a date expression for Dremio, converting DATE literals to CAST
15526    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
15527        use crate::expressions::Literal;
15528
15529        match expr {
15530            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
15531                let Literal::Date(d) = lit.as_ref() else {
15532                    unreachable!()
15533                };
15534                // DATE 'value' -> CAST('value' AS DATE)
15535                self.write("CAST('");
15536                self.write(d);
15537                self.write("' AS DATE)");
15538            }
15539            _ => {
15540                // For all other expressions, generate normally
15541                self.generate_expression(expr)?;
15542            }
15543        }
15544        Ok(())
15545    }
15546
15547    /// Generate a TIMESTAMP literal with dialect-specific formatting
15548    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
15549        use crate::dialects::DialectType;
15550
15551        match self.config.dialect {
15552            // SQL Server uses CONVERT or CAST
15553            Some(DialectType::TSQL) => {
15554                self.write("CAST('");
15555                self.write(ts);
15556                self.write("' AS DATETIME2)");
15557            }
15558            // BigQuery uses CAST syntax for type literals
15559            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
15560            Some(DialectType::BigQuery) => {
15561                self.write("CAST('");
15562                self.write(ts);
15563                self.write("' AS TIMESTAMP)");
15564            }
15565            // Snowflake uses CAST syntax for TIMESTAMP literals
15566            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
15567            Some(DialectType::Snowflake) => {
15568                self.write("CAST('");
15569                self.write(ts);
15570                self.write("' AS TIMESTAMP)");
15571            }
15572            // Dremio uses CAST syntax for TIMESTAMP literals
15573            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
15574            Some(DialectType::Dremio) => {
15575                self.write("CAST('");
15576                self.write(ts);
15577                self.write("' AS TIMESTAMP)");
15578            }
15579            // Exasol uses CAST syntax for TIMESTAMP literals
15580            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
15581            Some(DialectType::Exasol) => {
15582                self.write("CAST('");
15583                self.write(ts);
15584                self.write("' AS TIMESTAMP)");
15585            }
15586            // Oracle prefers TO_TIMESTAMP function call
15587            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
15588            Some(DialectType::Oracle) => {
15589                self.write("TO_TIMESTAMP('");
15590                self.write(ts);
15591                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
15592            }
15593            // Presto/Trino: always use CAST for TIMESTAMP literals
15594            Some(DialectType::Presto) | Some(DialectType::Trino) => {
15595                if Self::timestamp_has_timezone(ts) {
15596                    self.write("CAST('");
15597                    self.write(ts);
15598                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
15599                } else {
15600                    self.write("CAST('");
15601                    self.write(ts);
15602                    self.write("' AS TIMESTAMP)");
15603                }
15604            }
15605            // ClickHouse: CAST('...' AS Nullable(DateTime))
15606            Some(DialectType::ClickHouse) => {
15607                self.write("CAST('");
15608                self.write(ts);
15609                self.write("' AS Nullable(DateTime))");
15610            }
15611            // Spark: CAST('...' AS TIMESTAMP)
15612            Some(DialectType::Spark) => {
15613                self.write("CAST('");
15614                self.write(ts);
15615                self.write("' AS TIMESTAMP)");
15616            }
15617            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
15618            // but TIMESTAMP '...' for special values like 'epoch'
15619            Some(DialectType::Redshift) => {
15620                if ts == "epoch" {
15621                    self.write_keyword("TIMESTAMP");
15622                    self.write(" '");
15623                    self.write(ts);
15624                    self.write("'");
15625                } else {
15626                    self.write("CAST('");
15627                    self.write(ts);
15628                    self.write("' AS TIMESTAMP)");
15629                }
15630            }
15631            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
15632            Some(DialectType::PostgreSQL)
15633            | Some(DialectType::Hive)
15634            | Some(DialectType::SQLite)
15635            | Some(DialectType::DuckDB)
15636            | Some(DialectType::Athena)
15637            | Some(DialectType::Drill)
15638            | Some(DialectType::Teradata) => {
15639                self.write("CAST('");
15640                self.write(ts);
15641                self.write("' AS TIMESTAMP)");
15642            }
15643            // MySQL/StarRocks: CAST('...' AS DATETIME)
15644            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
15645                self.write("CAST('");
15646                self.write(ts);
15647                self.write("' AS DATETIME)");
15648            }
15649            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
15650            Some(DialectType::Databricks) => {
15651                self.write("CAST('");
15652                self.write(ts);
15653                self.write("' AS TIMESTAMP_NTZ)");
15654            }
15655            // Standard SQL: TIMESTAMP '...'
15656            _ => {
15657                self.write_keyword("TIMESTAMP");
15658                self.write(" '");
15659                self.write(ts);
15660                self.write("'");
15661            }
15662        }
15663        Ok(())
15664    }
15665
15666    /// Check if a timestamp string contains a timezone identifier
15667    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
15668    fn timestamp_has_timezone(ts: &str) -> bool {
15669        // Check for common IANA timezone patterns: Continent/City format
15670        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
15671        // Also handles: UTC, GMT, Etc/GMT+0, etc.
15672        let ts_lower = ts.to_ascii_lowercase();
15673
15674        // Check for Continent/City pattern (most common)
15675        let continent_prefixes = [
15676            "africa/",
15677            "america/",
15678            "antarctica/",
15679            "arctic/",
15680            "asia/",
15681            "atlantic/",
15682            "australia/",
15683            "europe/",
15684            "indian/",
15685            "pacific/",
15686            "etc/",
15687            "brazil/",
15688            "canada/",
15689            "chile/",
15690            "mexico/",
15691            "us/",
15692        ];
15693
15694        for prefix in &continent_prefixes {
15695            if ts_lower.contains(prefix) {
15696                return true;
15697            }
15698        }
15699
15700        // Check for standalone timezone abbreviations at the end
15701        // These typically appear after the time portion
15702        let tz_abbrevs = [
15703            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
15704            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
15705            " sgt", " aest", " aedt", " acst", " acdt", " awst",
15706        ];
15707
15708        for abbrev in &tz_abbrevs {
15709            if ts_lower.ends_with(abbrev) {
15710                return true;
15711            }
15712        }
15713
15714        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
15715        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
15716        // Look for pattern: space followed by + or - and digits (optionally with :)
15717        let trimmed = ts.trim();
15718        if let Some(last_space) = trimmed.rfind(' ') {
15719            let suffix = &trimmed[last_space + 1..];
15720            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
15721                // Check if rest is numeric (possibly with : for hh:mm format)
15722                let rest = &suffix[1..];
15723                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
15724                    return true;
15725                }
15726            }
15727        }
15728
15729        false
15730    }
15731
15732    /// Generate a DATETIME literal with dialect-specific formatting
15733    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
15734        use crate::dialects::DialectType;
15735
15736        match self.config.dialect {
15737            // BigQuery uses CAST syntax for type literals
15738            // DATETIME 'value' -> CAST('value' AS DATETIME)
15739            Some(DialectType::BigQuery) => {
15740                self.write("CAST('");
15741                self.write(dt);
15742                self.write("' AS DATETIME)");
15743            }
15744            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
15745            Some(DialectType::DuckDB) => {
15746                self.write("CAST('");
15747                self.write(dt);
15748                self.write("' AS TIMESTAMP)");
15749            }
15750            // DATETIME is primarily a BigQuery type
15751            // Output as DATETIME '...' for dialects that support it
15752            _ => {
15753                self.write_keyword("DATETIME");
15754                self.write(" '");
15755                self.write(dt);
15756                self.write("'");
15757            }
15758        }
15759        Ok(())
15760    }
15761
15762    /// Generate a string literal with dialect-specific escaping
15763    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
15764        use crate::dialects::DialectType;
15765
15766        match self.config.dialect {
15767            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
15768            // and backslash escaping for special characters like newlines
15769            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
15770            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
15771                // Hive/Spark use backslash escaping for quotes (\') and special chars
15772                self.write("'");
15773                for c in s.chars() {
15774                    match c {
15775                        '\'' => self.write("\\'"),
15776                        '\\' => self.write("\\\\"),
15777                        '\n' => self.write("\\n"),
15778                        '\r' => self.write("\\r"),
15779                        '\t' => self.write("\\t"),
15780                        '\0' => self.write("\\0"),
15781                        _ => self.output.push(c),
15782                    }
15783                }
15784                self.write("'");
15785            }
15786            Some(DialectType::Drill) => {
15787                // Drill uses SQL-standard quote doubling ('') for quotes,
15788                // but backslash escaping for special characters
15789                self.write("'");
15790                for c in s.chars() {
15791                    match c {
15792                        '\'' => self.write("''"),
15793                        '\\' => self.write("\\\\"),
15794                        '\n' => self.write("\\n"),
15795                        '\r' => self.write("\\r"),
15796                        '\t' => self.write("\\t"),
15797                        '\0' => self.write("\\0"),
15798                        _ => self.output.push(c),
15799                    }
15800                }
15801                self.write("'");
15802            }
15803            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
15804                self.write("'");
15805                for c in s.chars() {
15806                    match c {
15807                        // MySQL uses SQL standard quote doubling
15808                        '\'' => self.write("''"),
15809                        '\\' => self.write("\\\\"),
15810                        '\n' => self.write("\\n"),
15811                        '\r' => self.write("\\r"),
15812                        '\t' => self.write("\\t"),
15813                        // sqlglot writes a literal NUL for this case
15814                        '\0' => self.output.push('\0'),
15815                        _ => self.output.push(c),
15816                    }
15817                }
15818                self.write("'");
15819            }
15820            // BigQuery: Uses backslash escaping
15821            Some(DialectType::BigQuery) => {
15822                self.write("'");
15823                for c in s.chars() {
15824                    match c {
15825                        '\'' => self.write("\\'"),
15826                        '\\' => self.write("\\\\"),
15827                        '\n' => self.write("\\n"),
15828                        '\r' => self.write("\\r"),
15829                        '\t' => self.write("\\t"),
15830                        '\0' => self.write("\\0"),
15831                        '\x07' => self.write("\\a"),
15832                        '\x08' => self.write("\\b"),
15833                        '\x0C' => self.write("\\f"),
15834                        '\x0B' => self.write("\\v"),
15835                        _ => self.output.push(c),
15836                    }
15837                }
15838                self.write("'");
15839            }
15840            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
15841            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
15842            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
15843            Some(DialectType::Athena) => {
15844                if self.athena_hive_context {
15845                    // Hive-style: backslash escaping
15846                    self.write("'");
15847                    for c in s.chars() {
15848                        match c {
15849                            '\'' => self.write("\\'"),
15850                            '\\' => self.write("\\\\"),
15851                            '\n' => self.write("\\n"),
15852                            '\r' => self.write("\\r"),
15853                            '\t' => self.write("\\t"),
15854                            '\0' => self.write("\\0"),
15855                            _ => self.output.push(c),
15856                        }
15857                    }
15858                    self.write("'");
15859                } else {
15860                    // Trino-style: SQL-standard escaping, preserve backslashes
15861                    self.write("'");
15862                    for c in s.chars() {
15863                        match c {
15864                            '\'' => self.write("''"),
15865                            // Preserve backslashes literally (no re-escaping)
15866                            _ => self.output.push(c),
15867                        }
15868                    }
15869                    self.write("'");
15870                }
15871            }
15872            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
15873            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
15874            // becomes string value '\\'), so we should NOT re-escape backslashes.
15875            // We only need to escape single quotes.
15876            Some(DialectType::Snowflake) => {
15877                self.write("'");
15878                for c in s.chars() {
15879                    match c {
15880                        '\'' => self.write("\\'"),
15881                        // Backslashes are already escaped in the tokenized string, don't re-escape
15882                        // Only escape special characters that might not have been escaped
15883                        '\n' => self.write("\\n"),
15884                        '\r' => self.write("\\r"),
15885                        '\t' => self.write("\\t"),
15886                        _ => self.output.push(c),
15887                    }
15888                }
15889                self.write("'");
15890            }
15891            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
15892            Some(DialectType::PostgreSQL) => {
15893                self.write("'");
15894                for c in s.chars() {
15895                    match c {
15896                        '\'' => self.write("''"),
15897                        _ => self.output.push(c),
15898                    }
15899                }
15900                self.write("'");
15901            }
15902            // Redshift: Uses backslash escaping for single quotes
15903            Some(DialectType::Redshift) => {
15904                self.write("'");
15905                for c in s.chars() {
15906                    match c {
15907                        '\'' => self.write("\\'"),
15908                        _ => self.output.push(c),
15909                    }
15910                }
15911                self.write("'");
15912            }
15913            // Oracle: Uses standard double single-quote escaping
15914            Some(DialectType::Oracle) => {
15915                self.write("'");
15916                for ch in s.chars() {
15917                    if ch == '\'' {
15918                        self.output.push_str("''");
15919                    } else {
15920                        self.output.push(ch);
15921                    }
15922                }
15923                self.write("'");
15924            }
15925            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
15926            // backslash escaping for backslashes and special characters
15927            Some(DialectType::ClickHouse) => {
15928                self.write("'");
15929                for c in s.chars() {
15930                    match c {
15931                        '\'' => self.write("''"),
15932                        '\\' => self.write("\\\\"),
15933                        '\n' => self.write("\\n"),
15934                        '\r' => self.write("\\r"),
15935                        '\t' => self.write("\\t"),
15936                        '\0' => self.write("\\0"),
15937                        '\x07' => self.write("\\a"),
15938                        '\x08' => self.write("\\b"),
15939                        '\x0C' => self.write("\\f"),
15940                        '\x0B' => self.write("\\v"),
15941                        // Non-printable characters: emit as \xNN hex escapes
15942                        c if c.is_control() || (c as u32) < 0x20 => {
15943                            let byte = c as u32;
15944                            if byte < 256 {
15945                                self.write(&format!("\\x{:02X}", byte));
15946                            } else {
15947                                self.output.push(c);
15948                            }
15949                        }
15950                        _ => self.output.push(c),
15951                    }
15952                }
15953                self.write("'");
15954            }
15955            // Default: SQL standard double single quotes (works for most dialects)
15956            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
15957            _ => {
15958                self.write("'");
15959                for ch in s.chars() {
15960                    if ch == '\'' {
15961                        self.output.push_str("''");
15962                    } else {
15963                        self.output.push(ch);
15964                    }
15965                }
15966                self.write("'");
15967            }
15968        }
15969        Ok(())
15970    }
15971
15972    /// Write a byte string with proper escaping for BigQuery-style byte literals
15973    /// Escapes characters as \xNN hex escapes where needed
15974    fn write_escaped_byte_string(&mut self, s: &str) {
15975        for c in s.chars() {
15976            match c {
15977                // Escape single quotes
15978                '\'' => self.write("\\'"),
15979                // Escape backslashes
15980                '\\' => self.write("\\\\"),
15981                // Keep all printable characters (including non-ASCII) as-is
15982                _ if !c.is_control() => self.output.push(c),
15983                // Escape control characters as hex
15984                _ => {
15985                    let byte = c as u32;
15986                    if byte < 256 {
15987                        self.write(&format!("\\x{:02x}", byte));
15988                    } else {
15989                        // For unicode characters, write each UTF-8 byte
15990                        for b in c.to_string().as_bytes() {
15991                            self.write(&format!("\\x{:02x}", b));
15992                        }
15993                    }
15994                }
15995            }
15996        }
15997    }
15998
15999    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
16000        use crate::dialects::DialectType;
16001
16002        // Different dialects have different boolean literal formats
16003        match self.config.dialect {
16004            // SQL Server typically uses 1/0 for boolean literals in many contexts
16005            // However, TRUE/FALSE also works in modern versions
16006            Some(DialectType::TSQL) => {
16007                self.write(if b.value { "1" } else { "0" });
16008            }
16009            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
16010            Some(DialectType::Oracle) => {
16011                self.write(if b.value { "1" } else { "0" });
16012            }
16013            // MySQL accepts TRUE/FALSE as aliases for 1/0
16014            Some(DialectType::MySQL) => {
16015                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
16016            }
16017            // Most other dialects support TRUE/FALSE
16018            _ => {
16019                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
16020            }
16021        }
16022        Ok(())
16023    }
16024
16025    /// Generate an identifier that's used as an alias name
16026    /// This quotes reserved keywords in addition to already-quoted identifiers
16027    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
16028        let name = &id.name;
16029        let quote_style = &self.config.identifier_quote_style;
16030
16031        // For aliases, quote if:
16032        // 1. The identifier was explicitly quoted in the source
16033        // 2. The identifier is a reserved keyword for the current dialect
16034        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
16035
16036        // Normalize identifier if configured
16037        let output_name = if self.config.normalize_identifiers && !id.quoted {
16038            name.to_ascii_lowercase()
16039        } else {
16040            name.to_string()
16041        };
16042
16043        if needs_quoting {
16044            let quote_style = if matches!(self.config.dialect, Some(DialectType::ClickHouse))
16045                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
16046                && quote_style.start == '"'
16047                && output_name.contains('"')
16048            {
16049                &IdentifierQuoteStyle::BACKTICK
16050            } else {
16051                quote_style
16052            };
16053            // Escape any quote characters within the identifier
16054            let escaped_name = if quote_style.start == quote_style.end {
16055                output_name.replace(
16056                    quote_style.end,
16057                    &format!("{}{}", quote_style.end, quote_style.end),
16058                )
16059            } else {
16060                output_name.replace(
16061                    quote_style.end,
16062                    &format!("{}{}", quote_style.end, quote_style.end),
16063                )
16064            };
16065            self.write(&format!(
16066                "{}{}{}",
16067                quote_style.start, escaped_name, quote_style.end
16068            ));
16069        } else {
16070            self.write(&output_name);
16071        }
16072
16073        // Output trailing comments
16074        for comment in &id.trailing_comments {
16075            self.write(" ");
16076            self.write_formatted_comment(comment);
16077        }
16078        Ok(())
16079    }
16080
16081    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
16082        use crate::dialects::DialectType;
16083
16084        let name = &id.name;
16085
16086        // For Athena, use backticks in Hive context, double quotes in Trino context
16087        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
16088            && self.athena_hive_context
16089        {
16090            &IdentifierQuoteStyle::BACKTICK
16091        } else {
16092            &self.config.identifier_quote_style
16093        };
16094
16095        // Quote if:
16096        // 1. The identifier was explicitly quoted in the source
16097        // 2. The identifier is a reserved keyword for the current dialect
16098        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
16099        // This matches Python sqlglot's identifier_sql behavior
16100        // Also quote identifiers starting with digits if the target dialect doesn't support them
16101        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
16102        let needs_digit_quoting = starts_with_digit
16103            && !self.config.identifiers_can_start_with_digit
16104            && self.config.dialect.is_some();
16105        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
16106            && name.len() > 2
16107            && (name.starts_with("0x") || name.starts_with("0X"))
16108            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
16109        let clickhouse_unsafe_identifier =
16110            matches!(self.config.dialect, Some(DialectType::ClickHouse))
16111                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
16112                && !name.starts_with('{')
16113                && !name.contains('(')
16114                && !name.contains(')')
16115                && name != "?"
16116                && name
16117                    .chars()
16118                    .any(|c| !(c.is_ascii_alphanumeric() || c == '_'));
16119        let needs_quoting = id.quoted
16120            || self.is_reserved_keyword(name)
16121            || self.config.always_quote_identifiers
16122            || needs_digit_quoting
16123            || mysql_invalid_hex_identifier
16124            || clickhouse_unsafe_identifier;
16125
16126        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
16127        // When quoted, we need to output `name`(16) not `name(16)`
16128        let (base_name, suffix) = if needs_quoting {
16129            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
16130            if let Some(paren_pos) = name.find('(') {
16131                let base = &name[..paren_pos];
16132                let rest = &name[paren_pos..];
16133                // Verify it looks like (digits) or (digits) ASC/DESC
16134                if rest.starts_with('(')
16135                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
16136                {
16137                    // Check if content between parens is all digits
16138                    let close_paren = rest.find(')').unwrap_or(rest.len());
16139                    let inside = &rest[1..close_paren];
16140                    if inside.chars().all(|c| c.is_ascii_digit()) {
16141                        (base.to_string(), rest.to_string())
16142                    } else {
16143                        (name.to_string(), String::new())
16144                    }
16145                } else {
16146                    (name.to_string(), String::new())
16147                }
16148            } else if name.ends_with(" ASC") {
16149                let base = &name[..name.len() - 4];
16150                (base.to_string(), " ASC".to_string())
16151            } else if name.ends_with(" DESC") {
16152                let base = &name[..name.len() - 5];
16153                (base.to_string(), " DESC".to_string())
16154            } else {
16155                (name.to_string(), String::new())
16156            }
16157        } else {
16158            (name.to_string(), String::new())
16159        };
16160
16161        // Normalize identifier if configured, with special handling for Exasol
16162        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
16163        // should be uppercased when not already quoted (to match Python sqlglot behavior)
16164        let output_name = if self.config.normalize_identifiers && !id.quoted {
16165            base_name.to_ascii_lowercase()
16166        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
16167            && !id.quoted
16168            && self.is_reserved_keyword(name)
16169        {
16170            // Exasol: uppercase reserved keywords when quoting them
16171            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
16172            base_name.to_ascii_uppercase()
16173        } else {
16174            base_name
16175        };
16176
16177        if needs_quoting {
16178            // Escape any quote characters within the identifier
16179            let escaped_name = if quote_style.start == quote_style.end {
16180                // Same start/end char (e.g., " or `) - double the quote char
16181                output_name.replace(
16182                    quote_style.end,
16183                    &format!("{}{}", quote_style.end, quote_style.end),
16184                )
16185            } else {
16186                // Different start/end (e.g., [ and ]) - escape only the end char
16187                output_name.replace(
16188                    quote_style.end,
16189                    &format!("{}{}", quote_style.end, quote_style.end),
16190                )
16191            };
16192            self.write(&format!(
16193                "{}{}{}{}",
16194                quote_style.start, escaped_name, quote_style.end, suffix
16195            ));
16196        } else {
16197            self.write(&output_name);
16198        }
16199
16200        // Output trailing comments
16201        for comment in &id.trailing_comments {
16202            self.write(" ");
16203            self.write_formatted_comment(comment);
16204        }
16205        Ok(())
16206    }
16207
16208    fn generate_column(&mut self, col: &Column) -> Result<()> {
16209        use crate::dialects::DialectType;
16210
16211        if let Some(table) = &col.table {
16212            // Exasol special case: LOCAL as column table prefix should NOT be quoted
16213            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
16214            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
16215            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
16216                && !table.quoted
16217                && table.name.eq_ignore_ascii_case("LOCAL");
16218
16219            if is_exasol_local_prefix {
16220                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
16221                self.write("LOCAL");
16222            } else {
16223                self.generate_identifier(table)?;
16224            }
16225            self.write(".");
16226        }
16227        self.generate_identifier(&col.name)?;
16228        // Oracle-style join marker (+)
16229        // Only output if dialect supports it (Oracle, Exasol)
16230        if col.join_mark && self.config.supports_column_join_marks {
16231            self.write(" (+)");
16232        }
16233        // Output trailing comments
16234        for comment in &col.trailing_comments {
16235            self.write_space();
16236            self.write_formatted_comment(comment);
16237        }
16238        Ok(())
16239    }
16240
16241    fn generate_prepare(&mut self, prepare: &PrepareStatement) -> Result<()> {
16242        self.write_keyword("PREPARE");
16243        self.write_space();
16244        self.generate_identifier(&prepare.name)?;
16245
16246        if !prepare.parameter_types.is_empty() {
16247            self.write(" (");
16248            for (i, data_type) in prepare.parameter_types.iter().enumerate() {
16249                if i > 0 {
16250                    self.write(", ");
16251                }
16252                self.generate_data_type(data_type)?;
16253            }
16254            self.write(")");
16255        }
16256
16257        self.write_space();
16258        self.write_keyword("AS");
16259        self.write_space();
16260        self.generate_expression(&prepare.statement)
16261    }
16262
16263    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
16264    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
16265    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
16266        use crate::dialects::DialectType;
16267        use crate::expressions::PseudocolumnType;
16268
16269        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
16270        if pc.kind == PseudocolumnType::Sysdate
16271            && !matches!(
16272                self.config.dialect,
16273                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
16274            )
16275        {
16276            self.write_keyword("CURRENT_TIMESTAMP");
16277            // Add () for dialects that expect it
16278            if matches!(
16279                self.config.dialect,
16280                Some(DialectType::MySQL)
16281                    | Some(DialectType::ClickHouse)
16282                    | Some(DialectType::Spark)
16283                    | Some(DialectType::Databricks)
16284                    | Some(DialectType::Hive)
16285            ) {
16286                self.write("()");
16287            }
16288        } else {
16289            self.write(pc.kind.as_str());
16290        }
16291        Ok(())
16292    }
16293
16294    /// Generate CONNECT BY clause (Oracle hierarchical queries)
16295    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
16296        use crate::dialects::DialectType;
16297
16298        // Generate native CONNECT BY for Oracle and Snowflake
16299        // For other dialects, add a comment noting manual conversion needed
16300        let supports_connect_by = matches!(
16301            self.config.dialect,
16302            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
16303        );
16304
16305        if !supports_connect_by && self.config.dialect.is_some() {
16306            // Add comment for unsupported dialects
16307            if self.config.pretty {
16308                self.write_newline();
16309            } else {
16310                self.write_space();
16311            }
16312            self.write_unsupported_comment(
16313                "CONNECT BY requires manual conversion to recursive CTE",
16314            )?;
16315        }
16316
16317        // Generate START WITH if present (before CONNECT BY)
16318        if let Some(start) = &connect.start {
16319            if self.config.pretty {
16320                self.write_newline();
16321            } else {
16322                self.write_space();
16323            }
16324            self.write_keyword("START WITH");
16325            self.write_space();
16326            self.generate_expression(start)?;
16327        }
16328
16329        // Generate CONNECT BY
16330        if self.config.pretty {
16331            self.write_newline();
16332        } else {
16333            self.write_space();
16334        }
16335        self.write_keyword("CONNECT BY");
16336        if connect.nocycle {
16337            self.write_space();
16338            self.write_keyword("NOCYCLE");
16339        }
16340        self.write_space();
16341        self.generate_expression(&connect.connect)?;
16342
16343        Ok(())
16344    }
16345
16346    /// Generate Connect expression (for Expression::Connect variant)
16347    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
16348        self.generate_connect(connect)
16349    }
16350
16351    /// Generate PRIOR expression
16352    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
16353        self.write_keyword("PRIOR");
16354        self.write_space();
16355        self.generate_expression(&prior.this)?;
16356        Ok(())
16357    }
16358
16359    /// Generate CONNECT_BY_ROOT function
16360    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
16361    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
16362        self.write_keyword("CONNECT_BY_ROOT");
16363        self.write_space();
16364        self.generate_expression(&cbr.this)?;
16365        Ok(())
16366    }
16367
16368    /// Generate MATCH_RECOGNIZE clause
16369    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
16370        use crate::dialects::DialectType;
16371
16372        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
16373        let supports_match_recognize = matches!(
16374            self.config.dialect,
16375            Some(DialectType::Oracle)
16376                | Some(DialectType::Snowflake)
16377                | Some(DialectType::Presto)
16378                | Some(DialectType::Trino)
16379        );
16380
16381        // Generate the source table first
16382        if let Some(source) = &mr.this {
16383            self.generate_expression(source)?;
16384        }
16385
16386        if !supports_match_recognize {
16387            self.write_unsupported_comment("MATCH_RECOGNIZE not supported in this dialect")?;
16388            return Ok(());
16389        }
16390
16391        // In pretty mode, MATCH_RECOGNIZE should be on a new line
16392        if self.config.pretty {
16393            self.write_newline();
16394        } else {
16395            self.write_space();
16396        }
16397
16398        self.write_keyword("MATCH_RECOGNIZE");
16399        self.write(" (");
16400
16401        if self.config.pretty {
16402            self.indent_level += 1;
16403        }
16404
16405        let mut needs_separator = false;
16406
16407        // PARTITION BY
16408        if let Some(partition_by) = &mr.partition_by {
16409            if !partition_by.is_empty() {
16410                if self.config.pretty {
16411                    self.write_newline();
16412                    self.write_indent();
16413                }
16414                self.write_keyword("PARTITION BY");
16415                self.write_space();
16416                for (i, expr) in partition_by.iter().enumerate() {
16417                    if i > 0 {
16418                        self.write(", ");
16419                    }
16420                    self.generate_expression(expr)?;
16421                }
16422                needs_separator = true;
16423            }
16424        }
16425
16426        // ORDER BY
16427        if let Some(order_by) = &mr.order_by {
16428            if !order_by.is_empty() {
16429                if needs_separator {
16430                    if self.config.pretty {
16431                        self.write_newline();
16432                        self.write_indent();
16433                    } else {
16434                        self.write_space();
16435                    }
16436                } else if self.config.pretty {
16437                    self.write_newline();
16438                    self.write_indent();
16439                }
16440                self.write_keyword("ORDER BY");
16441                // In pretty mode, put each ORDER BY column on a new indented line
16442                if self.config.pretty {
16443                    self.indent_level += 1;
16444                    for (i, ordered) in order_by.iter().enumerate() {
16445                        if i > 0 {
16446                            self.write(",");
16447                        }
16448                        self.write_newline();
16449                        self.write_indent();
16450                        self.generate_ordered(ordered)?;
16451                    }
16452                    self.indent_level -= 1;
16453                } else {
16454                    self.write_space();
16455                    for (i, ordered) in order_by.iter().enumerate() {
16456                        if i > 0 {
16457                            self.write(", ");
16458                        }
16459                        self.generate_ordered(ordered)?;
16460                    }
16461                }
16462                needs_separator = true;
16463            }
16464        }
16465
16466        // MEASURES
16467        if let Some(measures) = &mr.measures {
16468            if !measures.is_empty() {
16469                if needs_separator {
16470                    if self.config.pretty {
16471                        self.write_newline();
16472                        self.write_indent();
16473                    } else {
16474                        self.write_space();
16475                    }
16476                } else if self.config.pretty {
16477                    self.write_newline();
16478                    self.write_indent();
16479                }
16480                self.write_keyword("MEASURES");
16481                // In pretty mode, put each MEASURE on a new indented line
16482                if self.config.pretty {
16483                    self.indent_level += 1;
16484                    for (i, measure) in measures.iter().enumerate() {
16485                        if i > 0 {
16486                            self.write(",");
16487                        }
16488                        self.write_newline();
16489                        self.write_indent();
16490                        // Handle RUNNING/FINAL prefix
16491                        if let Some(semantics) = &measure.window_frame {
16492                            match semantics {
16493                                MatchRecognizeSemantics::Running => {
16494                                    self.write_keyword("RUNNING");
16495                                    self.write_space();
16496                                }
16497                                MatchRecognizeSemantics::Final => {
16498                                    self.write_keyword("FINAL");
16499                                    self.write_space();
16500                                }
16501                            }
16502                        }
16503                        self.generate_expression(&measure.this)?;
16504                    }
16505                    self.indent_level -= 1;
16506                } else {
16507                    self.write_space();
16508                    for (i, measure) in measures.iter().enumerate() {
16509                        if i > 0 {
16510                            self.write(", ");
16511                        }
16512                        // Handle RUNNING/FINAL prefix
16513                        if let Some(semantics) = &measure.window_frame {
16514                            match semantics {
16515                                MatchRecognizeSemantics::Running => {
16516                                    self.write_keyword("RUNNING");
16517                                    self.write_space();
16518                                }
16519                                MatchRecognizeSemantics::Final => {
16520                                    self.write_keyword("FINAL");
16521                                    self.write_space();
16522                                }
16523                            }
16524                        }
16525                        self.generate_expression(&measure.this)?;
16526                    }
16527                }
16528                needs_separator = true;
16529            }
16530        }
16531
16532        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
16533        if let Some(rows) = &mr.rows {
16534            if needs_separator {
16535                if self.config.pretty {
16536                    self.write_newline();
16537                    self.write_indent();
16538                } else {
16539                    self.write_space();
16540                }
16541            } else if self.config.pretty {
16542                self.write_newline();
16543                self.write_indent();
16544            }
16545            match rows {
16546                MatchRecognizeRows::OneRowPerMatch => {
16547                    self.write_keyword("ONE ROW PER MATCH");
16548                }
16549                MatchRecognizeRows::AllRowsPerMatch => {
16550                    self.write_keyword("ALL ROWS PER MATCH");
16551                }
16552                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
16553                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
16554                }
16555                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
16556                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
16557                }
16558                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
16559                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
16560                }
16561            }
16562            needs_separator = true;
16563        }
16564
16565        // AFTER MATCH SKIP
16566        if let Some(after) = &mr.after {
16567            if needs_separator {
16568                if self.config.pretty {
16569                    self.write_newline();
16570                    self.write_indent();
16571                } else {
16572                    self.write_space();
16573                }
16574            } else if self.config.pretty {
16575                self.write_newline();
16576                self.write_indent();
16577            }
16578            match after {
16579                MatchRecognizeAfter::PastLastRow => {
16580                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
16581                }
16582                MatchRecognizeAfter::ToNextRow => {
16583                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
16584                }
16585                MatchRecognizeAfter::ToFirst(ident) => {
16586                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
16587                    self.write_space();
16588                    self.generate_identifier(ident)?;
16589                }
16590                MatchRecognizeAfter::ToLast(ident) => {
16591                    self.write_keyword("AFTER MATCH SKIP TO LAST");
16592                    self.write_space();
16593                    self.generate_identifier(ident)?;
16594                }
16595            }
16596            needs_separator = true;
16597        }
16598
16599        // PATTERN
16600        if let Some(pattern) = &mr.pattern {
16601            if needs_separator {
16602                if self.config.pretty {
16603                    self.write_newline();
16604                    self.write_indent();
16605                } else {
16606                    self.write_space();
16607                }
16608            } else if self.config.pretty {
16609                self.write_newline();
16610                self.write_indent();
16611            }
16612            self.write_keyword("PATTERN");
16613            self.write_space();
16614            self.write("(");
16615            self.write(pattern);
16616            self.write(")");
16617            needs_separator = true;
16618        }
16619
16620        // DEFINE
16621        if let Some(define) = &mr.define {
16622            if !define.is_empty() {
16623                if needs_separator {
16624                    if self.config.pretty {
16625                        self.write_newline();
16626                        self.write_indent();
16627                    } else {
16628                        self.write_space();
16629                    }
16630                } else if self.config.pretty {
16631                    self.write_newline();
16632                    self.write_indent();
16633                }
16634                self.write_keyword("DEFINE");
16635                // In pretty mode, put each DEFINE on a new indented line
16636                if self.config.pretty {
16637                    self.indent_level += 1;
16638                    for (i, (name, expr)) in define.iter().enumerate() {
16639                        if i > 0 {
16640                            self.write(",");
16641                        }
16642                        self.write_newline();
16643                        self.write_indent();
16644                        self.generate_identifier(name)?;
16645                        self.write(" AS ");
16646                        self.generate_expression(expr)?;
16647                    }
16648                    self.indent_level -= 1;
16649                } else {
16650                    self.write_space();
16651                    for (i, (name, expr)) in define.iter().enumerate() {
16652                        if i > 0 {
16653                            self.write(", ");
16654                        }
16655                        self.generate_identifier(name)?;
16656                        self.write(" AS ");
16657                        self.generate_expression(expr)?;
16658                    }
16659                }
16660            }
16661        }
16662
16663        if self.config.pretty {
16664            self.indent_level -= 1;
16665            self.write_newline();
16666        }
16667        self.write(")");
16668
16669        // Alias - only include AS if it was explicitly present in the input
16670        if let Some(alias) = &mr.alias {
16671            self.write(" ");
16672            if mr.alias_explicit_as {
16673                self.write_keyword("AS");
16674                self.write(" ");
16675            }
16676            self.generate_identifier(alias)?;
16677        }
16678
16679        Ok(())
16680    }
16681
16682    /// Generate a query hint /*+ ... */
16683    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
16684        use crate::dialects::DialectType;
16685
16686        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
16687        let supports_hints = matches!(
16688            self.config.dialect,
16689            None |  // No dialect = preserve everything
16690            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
16691            Some(DialectType::Spark) | Some(DialectType::Hive) |
16692            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
16693        );
16694
16695        if !supports_hints || hint.expressions.is_empty() {
16696            return Ok(());
16697        }
16698
16699        // First, expand raw hint text into individual hint strings
16700        // This handles the case where the parser stored multiple hints as a single raw string
16701        let mut hint_strings: Vec<String> = Vec::new();
16702        for expr in &hint.expressions {
16703            match expr {
16704                HintExpression::Raw(text) => {
16705                    // Parse raw hint text into individual hint function calls
16706                    let parsed = self.parse_raw_hint_text(text);
16707                    hint_strings.extend(parsed);
16708                }
16709                _ => {
16710                    hint_strings.push(self.hint_expression_to_string(expr)?);
16711                }
16712            }
16713        }
16714
16715        // In pretty mode with multiple hints, always use multiline format
16716        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
16717        // always joins with newlines in pretty mode
16718        let use_multiline = self.config.pretty && hint_strings.len() > 1;
16719
16720        if use_multiline {
16721            // Pretty print with each hint on its own line
16722            self.write(" /*+ ");
16723            for (i, hint_str) in hint_strings.iter().enumerate() {
16724                if i > 0 {
16725                    self.write_newline();
16726                    self.write("  "); // 2-space indent within hint block
16727                }
16728                self.write(hint_str);
16729            }
16730            self.write(" */");
16731        } else {
16732            // Single line format
16733            self.write(" /*+ ");
16734            let sep = match self.config.dialect {
16735                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
16736                _ => " ",
16737            };
16738            for (i, hint_str) in hint_strings.iter().enumerate() {
16739                if i > 0 {
16740                    self.write(sep);
16741                }
16742                self.write(hint_str);
16743            }
16744            self.write(" */");
16745        }
16746
16747        Ok(())
16748    }
16749
16750    /// Parse raw hint text into individual hint function calls
16751    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
16752    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
16753    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
16754        let mut results = Vec::new();
16755        let mut chars = text.chars().peekable();
16756        let mut current = String::new();
16757        let mut paren_depth = 0;
16758        let mut has_unparseable_content = false;
16759        let mut position_after_last_function = 0;
16760        let mut char_position = 0;
16761
16762        while let Some(c) = chars.next() {
16763            char_position += c.len_utf8();
16764            match c {
16765                '(' => {
16766                    paren_depth += 1;
16767                    current.push(c);
16768                }
16769                ')' => {
16770                    paren_depth -= 1;
16771                    current.push(c);
16772                    // When we close the outer parenthesis, we've completed a hint function
16773                    if paren_depth == 0 {
16774                        let trimmed = current.trim().to_string();
16775                        if !trimmed.is_empty() {
16776                            // Format this hint for pretty printing if needed
16777                            let formatted = self.format_hint_function(&trimmed);
16778                            results.push(formatted);
16779                        }
16780                        current.clear();
16781                        position_after_last_function = char_position;
16782                    }
16783                }
16784                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
16785                    // Space/comma/whitespace outside parentheses - skip
16786                }
16787                _ if paren_depth == 0 => {
16788                    // Character outside parentheses - accumulate for potential hint name
16789                    current.push(c);
16790                }
16791                _ => {
16792                    current.push(c);
16793                }
16794            }
16795        }
16796
16797        // Check if there's remaining text after the last function call
16798        let remaining_text = text[position_after_last_function..].trim();
16799        if !remaining_text.is_empty() {
16800            // Check if it looks like valid hint function names
16801            // Valid hint identifiers typically are uppercase alphanumeric with underscores
16802            // If we see multiple words without parens, it's likely unparseable
16803            let words: Vec<&str> = remaining_text.split_whitespace().collect();
16804            let looks_like_hint_functions = words.iter().all(|word| {
16805                // A valid hint name followed by opening paren, or a standalone uppercase identifier
16806                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
16807            });
16808
16809            if !looks_like_hint_functions && words.len() > 1 {
16810                has_unparseable_content = true;
16811            }
16812        }
16813
16814        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
16815        if has_unparseable_content {
16816            return vec![text.trim().to_string()];
16817        }
16818
16819        // If we couldn't parse anything, return the original text as a single hint
16820        if results.is_empty() {
16821            results.push(text.trim().to_string());
16822        }
16823
16824        results
16825    }
16826
16827    /// Format a hint function for pretty printing
16828    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
16829    fn format_hint_function(&self, hint: &str) -> String {
16830        if !self.config.pretty {
16831            return hint.to_string();
16832        }
16833
16834        // Try to parse NAME(args) pattern
16835        if let Some(paren_pos) = hint.find('(') {
16836            if hint.ends_with(')') {
16837                let name = &hint[..paren_pos];
16838                let args_str = &hint[paren_pos + 1..hint.len() - 1];
16839
16840                // Parse arguments (space-separated for Oracle hints)
16841                let args: Vec<&str> = args_str.split_whitespace().collect();
16842
16843                // Calculate total width of arguments
16844                let total_args_width: usize =
16845                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
16846
16847                // If too wide, format on multiple lines
16848                if total_args_width > self.config.max_text_width && !args.is_empty() {
16849                    let mut result = format!("{}(\n", name);
16850                    for arg in &args {
16851                        result.push_str("    "); // 4-space indent for args
16852                        result.push_str(arg);
16853                        result.push('\n');
16854                    }
16855                    result.push_str("  )"); // 2-space indent for closing paren
16856                    return result;
16857                }
16858            }
16859        }
16860
16861        hint.to_string()
16862    }
16863
16864    /// Convert a hint expression to a string, handling multiline formatting for long arguments
16865    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
16866        match expr {
16867            HintExpression::Function { name, args } => {
16868                // Generate each argument to a string
16869                let arg_strings: Vec<String> = args
16870                    .iter()
16871                    .map(|arg| {
16872                        let mut gen = Generator::with_arc_config(self.config.clone());
16873                        gen.generate_expression(arg)?;
16874                        Ok(gen.output)
16875                    })
16876                    .collect::<Result<Vec<_>>>()?;
16877
16878                // Oracle hints use space-separated arguments, not comma-separated
16879                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
16880                    + arg_strings.len().saturating_sub(1); // spaces between args
16881
16882                // Check if function args need multiline formatting
16883                // Use too_wide check for argument formatting
16884                let args_multiline =
16885                    self.config.pretty && total_args_width > self.config.max_text_width;
16886
16887                if args_multiline && !arg_strings.is_empty() {
16888                    // Multiline format for long argument lists
16889                    let mut result = format!("{}(\n", name);
16890                    for arg_str in &arg_strings {
16891                        result.push_str("    "); // 4-space indent for args
16892                        result.push_str(arg_str);
16893                        result.push('\n');
16894                    }
16895                    result.push_str("  )"); // 2-space indent for closing paren
16896                    Ok(result)
16897                } else {
16898                    // Single line format with space-separated args (Oracle style)
16899                    let args_str = arg_strings.join(" ");
16900                    Ok(format!("{}({})", name, args_str))
16901                }
16902            }
16903            HintExpression::Identifier(name) => Ok(name.clone()),
16904            HintExpression::Raw(text) => {
16905                // For pretty printing, try to format the raw text
16906                if self.config.pretty {
16907                    Ok(self.format_hint_function(text))
16908                } else {
16909                    Ok(text.clone())
16910                }
16911            }
16912        }
16913    }
16914
16915    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
16916        // PostgreSQL ONLY modifier: prevents scanning child tables
16917        if table.only {
16918            self.write_keyword("ONLY");
16919            self.write_space();
16920        }
16921
16922        // Check for IDENTIFIER() (Snowflake) or OPENDATASOURCE(...).db.schema.table (TSQL)
16923        if let Some(ref identifier_func) = table.identifier_func {
16924            self.generate_expression(identifier_func)?;
16925            // If table name parts are present, emit .catalog.schema.name after the function
16926            if !table.name.name.is_empty() {
16927                if let Some(catalog) = &table.catalog {
16928                    self.write(".");
16929                    self.generate_identifier(catalog)?;
16930                }
16931                if let Some(schema) = &table.schema {
16932                    self.write(".");
16933                    self.generate_identifier(schema)?;
16934                }
16935                self.write(".");
16936                self.generate_identifier(&table.name)?;
16937            }
16938        } else {
16939            if let Some(catalog) = &table.catalog {
16940                self.generate_identifier(catalog)?;
16941                self.write(".");
16942            }
16943            if let Some(schema) = &table.schema {
16944                self.generate_identifier(schema)?;
16945                self.write(".");
16946            }
16947            self.generate_identifier(&table.name)?;
16948        }
16949
16950        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
16951        if let Some(changes) = &table.changes {
16952            self.write(" ");
16953            self.generate_changes(changes)?;
16954        }
16955
16956        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
16957        if !table.partitions.is_empty() {
16958            self.write_space();
16959            self.write_keyword("PARTITION");
16960            self.write("(");
16961            for (i, partition) in table.partitions.iter().enumerate() {
16962                if i > 0 {
16963                    self.write(", ");
16964                }
16965                self.generate_identifier(partition)?;
16966            }
16967            self.write(")");
16968        }
16969
16970        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
16971        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
16972        if table.changes.is_none() {
16973            if let Some(when) = &table.when {
16974                self.write_space();
16975                self.generate_historical_data(when)?;
16976            }
16977        }
16978
16979        // Output TSQL FOR SYSTEM_TIME temporal clause (before alias, except BigQuery)
16980        let system_time_post_alias = matches!(self.config.dialect, Some(DialectType::BigQuery));
16981        if !system_time_post_alias {
16982            if let Some(ref system_time) = table.system_time {
16983                self.write_space();
16984                self.write(system_time);
16985            }
16986        }
16987
16988        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
16989        if let Some(ref version) = table.version {
16990            self.write_space();
16991            self.generate_version(version)?;
16992        }
16993
16994        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
16995        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
16996        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
16997        let alias_post_tablesample = self.config.alias_post_tablesample;
16998
16999        if alias_post_tablesample {
17000            // TABLESAMPLE before alias (Oracle, Hive, Spark)
17001            self.generate_table_sample_clause(table)?;
17002        }
17003
17004        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
17005        // For SQLite, INDEXED BY hints come after the alias, so skip here
17006        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
17007            && table.hints.iter().any(|h| {
17008                if let Expression::Identifier(id) = h {
17009                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
17010                } else {
17011                    false
17012                }
17013            });
17014        if !table.hints.is_empty() && !is_sqlite_hint {
17015            for hint in &table.hints {
17016                self.write_space();
17017                self.generate_expression(hint)?;
17018            }
17019        }
17020
17021        if let Some(alias) = &table.alias {
17022            self.write_space();
17023            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
17024            // Generic mode and most dialects always use AS for table aliases
17025            let always_use_as = self.config.dialect.is_none()
17026                || matches!(
17027                    self.config.dialect,
17028                    Some(DialectType::Generic)
17029                        | Some(DialectType::PostgreSQL)
17030                        | Some(DialectType::Redshift)
17031                        | Some(DialectType::Snowflake)
17032                        | Some(DialectType::BigQuery)
17033                        | Some(DialectType::DuckDB)
17034                        | Some(DialectType::Presto)
17035                        | Some(DialectType::Trino)
17036                        | Some(DialectType::TSQL)
17037                        | Some(DialectType::Fabric)
17038                        | Some(DialectType::MySQL)
17039                        | Some(DialectType::Spark)
17040                        | Some(DialectType::Hive)
17041                        | Some(DialectType::SQLite)
17042                        | Some(DialectType::Drill)
17043                );
17044            let is_stage_ref = table.name.name.starts_with('@');
17045            // Oracle never uses AS for table aliases
17046            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
17047            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
17048                self.write_keyword("AS");
17049                self.write_space();
17050            }
17051            self.generate_identifier(alias)?;
17052
17053            // Output column aliases if present: AS t(c1, c2)
17054            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
17055            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
17056                self.write("(");
17057                for (i, col_alias) in table.column_aliases.iter().enumerate() {
17058                    if i > 0 {
17059                        self.write(", ");
17060                    }
17061                    self.generate_identifier(col_alias)?;
17062                }
17063                self.write(")");
17064            }
17065        }
17066
17067        // BigQuery: FOR SYSTEM_TIME AS OF after alias
17068        if system_time_post_alias {
17069            if let Some(ref system_time) = table.system_time {
17070                self.write_space();
17071                self.write(system_time);
17072            }
17073        }
17074
17075        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
17076        if !alias_post_tablesample {
17077            self.generate_table_sample_clause(table)?;
17078        }
17079
17080        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
17081        if is_sqlite_hint {
17082            for hint in &table.hints {
17083                self.write_space();
17084                self.generate_expression(hint)?;
17085            }
17086        }
17087
17088        // ClickHouse FINAL modifier
17089        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
17090            self.write_space();
17091            self.write_keyword("FINAL");
17092        }
17093
17094        // Output trailing comments
17095        for comment in &table.trailing_comments {
17096            self.write_space();
17097            self.write_formatted_comment(comment);
17098        }
17099        // Note: leading_comments (from before table in FROM clause) are intentionally NOT
17100        // output here - they are output by the FROM/PIVOT generator after the full expression
17101
17102        Ok(())
17103    }
17104
17105    /// Helper to output TABLESAMPLE clause for a table reference
17106    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
17107        if let Some(ref ts) = table.table_sample {
17108            self.write_space();
17109            if ts.is_using_sample {
17110                self.write_keyword("USING SAMPLE");
17111            } else {
17112                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
17113                self.write_keyword(self.config.tablesample_keywords);
17114            }
17115            self.generate_sample_body(ts)?;
17116            // Seed for table-level sample - use dialect's configured keyword
17117            if let Some(ref seed) = ts.seed {
17118                self.write_space();
17119                self.write_keyword(self.config.tablesample_seed_keyword);
17120                self.write(" (");
17121                self.generate_expression(seed)?;
17122                self.write(")");
17123            }
17124        }
17125        Ok(())
17126    }
17127
17128    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
17129        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
17130        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
17131
17132        if sr.quoted {
17133            self.write("'");
17134        }
17135
17136        self.write(&sr.name);
17137        if let Some(path) = &sr.path {
17138            self.write(path);
17139        }
17140
17141        if sr.quoted {
17142            self.write("'");
17143        }
17144
17145        // Output FILE_FORMAT and PATTERN if present
17146        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
17147        if has_options {
17148            self.write(" (");
17149            let mut first = true;
17150
17151            if let Some(file_format) = &sr.file_format {
17152                if !first {
17153                    self.write(", ");
17154                }
17155                self.write_keyword("FILE_FORMAT");
17156                self.write(" => ");
17157                self.generate_expression(file_format)?;
17158                first = false;
17159            }
17160
17161            if let Some(pattern) = &sr.pattern {
17162                if !first {
17163                    self.write(", ");
17164                }
17165                self.write_keyword("PATTERN");
17166                self.write(" => '");
17167                self.write(pattern);
17168                self.write("'");
17169            }
17170
17171            self.write(")");
17172        }
17173        Ok(())
17174    }
17175
17176    fn generate_star(&mut self, star: &Star) -> Result<()> {
17177        use crate::dialects::DialectType;
17178
17179        if let Some(table) = &star.table {
17180            self.generate_identifier(table)?;
17181            self.write(".");
17182        }
17183        self.write("*");
17184
17185        // Generate EXCLUDE/EXCEPT clause based on dialect
17186        if let Some(except) = &star.except {
17187            if !except.is_empty() {
17188                self.write_space();
17189                // Use dialect-appropriate keyword
17190                match self.config.dialect {
17191                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
17192                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
17193                        self.write_keyword("EXCLUDE")
17194                    }
17195                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
17196                }
17197                self.write(" (");
17198                for (i, col) in except.iter().enumerate() {
17199                    if i > 0 {
17200                        self.write(", ");
17201                    }
17202                    self.generate_identifier(col)?;
17203                }
17204                self.write(")");
17205            }
17206        }
17207
17208        // Generate REPLACE clause
17209        if let Some(replace) = &star.replace {
17210            if !replace.is_empty() {
17211                self.write_space();
17212                self.write_keyword("REPLACE");
17213                self.write(" (");
17214                for (i, alias) in replace.iter().enumerate() {
17215                    if i > 0 {
17216                        self.write(", ");
17217                    }
17218                    self.generate_expression(&alias.this)?;
17219                    self.write_space();
17220                    self.write_keyword("AS");
17221                    self.write_space();
17222                    self.generate_identifier(&alias.alias)?;
17223                }
17224                self.write(")");
17225            }
17226        }
17227
17228        // Generate RENAME clause (Snowflake specific)
17229        if let Some(rename) = &star.rename {
17230            if !rename.is_empty() {
17231                self.write_space();
17232                self.write_keyword("RENAME");
17233                self.write(" (");
17234                for (i, (old_name, new_name)) in rename.iter().enumerate() {
17235                    if i > 0 {
17236                        self.write(", ");
17237                    }
17238                    self.generate_identifier(old_name)?;
17239                    self.write_space();
17240                    self.write_keyword("AS");
17241                    self.write_space();
17242                    self.generate_identifier(new_name)?;
17243                }
17244                self.write(")");
17245            }
17246        }
17247
17248        // Output trailing comments
17249        for comment in &star.trailing_comments {
17250            self.write_space();
17251            self.write_formatted_comment(comment);
17252        }
17253
17254        Ok(())
17255    }
17256
17257    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
17258    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
17259        self.write("{");
17260        match expr {
17261            Expression::Star(star) => {
17262                // Generate the star (table.* or just * with optional EXCLUDE)
17263                self.generate_star(star)?;
17264            }
17265            Expression::ILike(ilike) => {
17266                // {* ILIKE 'pattern'} syntax
17267                self.generate_expression(&ilike.left)?;
17268                self.write_space();
17269                self.write_keyword("ILIKE");
17270                self.write_space();
17271                self.generate_expression(&ilike.right)?;
17272            }
17273            _ => {
17274                self.generate_expression(expr)?;
17275            }
17276        }
17277        self.write("}");
17278        Ok(())
17279    }
17280
17281    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
17282        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
17283        // to avoid duplication (comments are captured as both Column.trailing_comments
17284        // and Alias.pre_alias_comments during parsing)
17285        match &alias.this {
17286            Expression::Column(col) => {
17287                // Generate column without trailing comments - they're in pre_alias_comments
17288                if let Some(table) = &col.table {
17289                    self.generate_identifier(table)?;
17290                    self.write(".");
17291                }
17292                self.generate_identifier(&col.name)?;
17293            }
17294            _ => {
17295                self.generate_expression(&alias.this)?;
17296            }
17297        }
17298
17299        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
17300        // moves pre-alias comments to after the alias. When there are also trailing_comments,
17301        // keep pre-alias comments in their original position (between expression and AS).
17302        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
17303            for comment in &alias.pre_alias_comments {
17304                self.write_space();
17305                self.write_formatted_comment(comment);
17306            }
17307        }
17308
17309        use crate::dialects::DialectType;
17310
17311        // Determine if we should skip AS keyword for table-valued function aliases
17312        // Oracle and some other dialects don't use AS for table aliases
17313        // Note: We specifically use TableFromRows here, NOT Function, because Function
17314        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
17315        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
17316        let is_table_source = matches!(
17317            &alias.this,
17318            Expression::JSONTable(_)
17319                | Expression::XMLTable(_)
17320                | Expression::TableFromRows(_)
17321                | Expression::Unnest(_)
17322                | Expression::MatchRecognize(_)
17323                | Expression::Select(_)
17324                | Expression::Subquery(_)
17325                | Expression::Paren(_)
17326        );
17327        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
17328        let skip_as = is_table_source && dialect_skips_table_alias_as;
17329
17330        self.write_space();
17331        if !skip_as {
17332            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
17333                if let Some(ref alias_keyword) = alias.alias_keyword {
17334                    self.write(alias_keyword);
17335                } else {
17336                    self.write_keyword("AS");
17337                }
17338            } else {
17339                self.write_keyword("AS");
17340            }
17341            self.write_space();
17342        }
17343
17344        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
17345        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
17346
17347        // Check if we have column aliases only (no table alias name)
17348        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
17349            // Generate AS (col1, col2, ...)
17350            self.write("(");
17351            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
17352                if i > 0 {
17353                    self.write(", ");
17354                }
17355                self.generate_alias_identifier(col_alias)?;
17356            }
17357            self.write(")");
17358        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
17359            // Generate AS alias(col1, col2, ...)
17360            self.generate_alias_identifier(&alias.alias)?;
17361            self.write("(");
17362            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
17363                if i > 0 {
17364                    self.write(", ");
17365                }
17366                self.generate_alias_identifier(col_alias)?;
17367            }
17368            self.write(")");
17369        } else {
17370            // Simple alias (or BigQuery without column aliases)
17371            self.generate_alias_identifier(&alias.alias)?;
17372        }
17373
17374        // Output trailing comments (comments after the alias)
17375        for comment in &alias.trailing_comments {
17376            self.write_space();
17377            self.write_formatted_comment(comment);
17378        }
17379
17380        // Output pre-alias comments: when there are no trailing_comments, sqlglot
17381        // moves pre-alias comments to after the alias. When there are trailing_comments,
17382        // the pre-alias comments were already lost (consumed as column trailing comments
17383        // that were then used as pre_alias_comments). We always emit them after alias.
17384        if alias.trailing_comments.is_empty() {
17385            for comment in &alias.pre_alias_comments {
17386                self.write_space();
17387                self.write_formatted_comment(comment);
17388            }
17389        }
17390
17391        Ok(())
17392    }
17393
17394    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
17395        use crate::dialects::DialectType;
17396
17397        // SingleStore uses :> syntax
17398        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
17399            self.generate_expression(&cast.this)?;
17400            self.write(" :> ");
17401            self.generate_data_type(&cast.to)?;
17402            return Ok(());
17403        }
17404
17405        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
17406        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
17407            let is_unknown_type = matches!(cast.to, DataType::Unknown)
17408                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
17409            if is_unknown_type {
17410                if let Some(format) = &cast.format {
17411                    self.write_keyword("CAST");
17412                    self.write("(");
17413                    self.generate_expression(&cast.this)?;
17414                    self.write_space();
17415                    self.write_keyword("AS");
17416                    self.write_space();
17417                    self.write_keyword("FORMAT");
17418                    self.write_space();
17419                    self.generate_expression(format)?;
17420                    self.write(")");
17421                    return Ok(());
17422                }
17423            }
17424        }
17425
17426        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
17427        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
17428        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
17429            if let Some(format) = &cast.format {
17430                // Check if target type is DATE or TIMESTAMP
17431                let is_date = matches!(cast.to, DataType::Date);
17432                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
17433
17434                if is_date || is_timestamp {
17435                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
17436                    self.write_keyword(func_name);
17437                    self.write("(");
17438                    self.generate_expression(&cast.this)?;
17439                    self.write(", ");
17440
17441                    // Normalize format string for Oracle (HH -> HH12)
17442                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
17443                    if let Expression::Literal(lit) = format.as_ref() {
17444                        if let Literal::String(fmt_str) = lit.as_ref() {
17445                            let normalized = self.normalize_oracle_format(fmt_str);
17446                            self.write("'");
17447                            self.write(&normalized);
17448                            self.write("'");
17449                        }
17450                    } else {
17451                        self.generate_expression(format)?;
17452                    }
17453
17454                    self.write(")");
17455                    return Ok(());
17456                }
17457            }
17458        }
17459
17460        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
17461        // This preserves sqlglot's typed inline array literal output.
17462        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
17463            if let Expression::Array(arr) = &cast.this {
17464                self.generate_data_type(&cast.to)?;
17465                // Output just the bracket content [values] without the ARRAY prefix
17466                self.write("[");
17467                for (i, expr) in arr.expressions.iter().enumerate() {
17468                    if i > 0 {
17469                        self.write(", ");
17470                    }
17471                    self.generate_expression(expr)?;
17472                }
17473                self.write("]");
17474                return Ok(());
17475            }
17476            if matches!(&cast.this, Expression::ArrayFunc(_)) {
17477                self.generate_data_type(&cast.to)?;
17478                self.generate_expression(&cast.this)?;
17479                return Ok(());
17480            }
17481        }
17482
17483        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
17484        // convert the inner Struct to ROW(values...) format
17485        if matches!(
17486            self.config.dialect,
17487            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
17488        ) {
17489            if let Expression::Struct(ref s) = cast.this {
17490                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
17491                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
17492                    self.write_keyword("CAST");
17493                    self.write("(");
17494                    self.generate_struct_as_row(s)?;
17495                    self.write_space();
17496                    self.write_keyword("AS");
17497                    self.write_space();
17498                    self.generate_data_type(&cast.to)?;
17499                    self.write(")");
17500                    return Ok(());
17501                }
17502            }
17503        }
17504
17505        // Determine if we should use :: syntax based on dialect
17506        // PostgreSQL prefers :: for identity, most others prefer CAST()
17507        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
17508
17509        if use_double_colon {
17510            // PostgreSQL :: syntax: expr::type
17511            self.generate_expression(&cast.this)?;
17512            self.write("::");
17513            self.generate_data_type(&cast.to)?;
17514        } else {
17515            // Standard CAST() syntax
17516            self.write_keyword("CAST");
17517            self.write("(");
17518            self.generate_expression(&cast.this)?;
17519            self.write_space();
17520            self.write_keyword("AS");
17521            self.write_space();
17522            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
17523            // This matches Python sqlglot's CAST_MAPPING behavior
17524            if matches!(
17525                self.config.dialect,
17526                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
17527            ) {
17528                match &cast.to {
17529                    DataType::Custom { ref name } => {
17530                        if name.eq_ignore_ascii_case("LONGTEXT")
17531                            || name.eq_ignore_ascii_case("MEDIUMTEXT")
17532                            || name.eq_ignore_ascii_case("TINYTEXT")
17533                            || name.eq_ignore_ascii_case("LONGBLOB")
17534                            || name.eq_ignore_ascii_case("MEDIUMBLOB")
17535                            || name.eq_ignore_ascii_case("TINYBLOB")
17536                        {
17537                            self.write_keyword("CHAR");
17538                        } else {
17539                            self.generate_data_type(&cast.to)?;
17540                        }
17541                    }
17542                    DataType::VarChar { length, .. } => {
17543                        // MySQL CAST: VARCHAR -> CHAR
17544                        self.write_keyword("CHAR");
17545                        if let Some(n) = length {
17546                            self.write(&format!("({})", n));
17547                        }
17548                    }
17549                    DataType::Text => {
17550                        // MySQL CAST: TEXT -> CHAR
17551                        self.write_keyword("CHAR");
17552                    }
17553                    DataType::Timestamp {
17554                        precision,
17555                        timezone: false,
17556                    } => {
17557                        // MySQL CAST: TIMESTAMP -> DATETIME
17558                        self.write_keyword("DATETIME");
17559                        if let Some(p) = precision {
17560                            self.write(&format!("({})", p));
17561                        }
17562                    }
17563                    _ => {
17564                        self.generate_data_type(&cast.to)?;
17565                    }
17566                }
17567            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
17568                // Snowflake CAST: STRING -> VARCHAR
17569                match &cast.to {
17570                    DataType::String { length } => {
17571                        self.write_keyword("VARCHAR");
17572                        if let Some(n) = length {
17573                            self.write(&format!("({})", n));
17574                        }
17575                    }
17576                    _ => {
17577                        self.generate_data_type(&cast.to)?;
17578                    }
17579                }
17580            } else {
17581                self.generate_data_type(&cast.to)?;
17582            }
17583
17584            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
17585            if let Some(default) = &cast.default {
17586                self.write_space();
17587                self.write_keyword("DEFAULT");
17588                self.write_space();
17589                self.generate_expression(default)?;
17590                self.write_space();
17591                self.write_keyword("ON");
17592                self.write_space();
17593                self.write_keyword("CONVERSION");
17594                self.write_space();
17595                self.write_keyword("ERROR");
17596            }
17597
17598            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
17599            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
17600            if let Some(format) = &cast.format {
17601                // Check if Oracle dialect - use comma syntax
17602                if matches!(
17603                    self.config.dialect,
17604                    Some(crate::dialects::DialectType::Oracle)
17605                ) {
17606                    self.write(", ");
17607                } else {
17608                    self.write_space();
17609                    self.write_keyword("FORMAT");
17610                    self.write_space();
17611                }
17612                self.generate_expression(format)?;
17613            }
17614
17615            self.write(")");
17616            // Output trailing comments
17617            for comment in &cast.trailing_comments {
17618                self.write_space();
17619                self.write_formatted_comment(comment);
17620            }
17621        }
17622        Ok(())
17623    }
17624
17625    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
17626    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
17627    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
17628        self.write_keyword("ROW");
17629        self.write("(");
17630        for (i, (_, expr)) in s.fields.iter().enumerate() {
17631            if i > 0 {
17632                self.write(", ");
17633            }
17634            // Recursively convert inner Struct to ROW format
17635            if let Expression::Struct(ref inner_s) = expr {
17636                self.generate_struct_as_row(inner_s)?;
17637            } else {
17638                self.generate_expression(expr)?;
17639            }
17640        }
17641        self.write(")");
17642        Ok(())
17643    }
17644
17645    /// Normalize Oracle date/time format strings
17646    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
17647    fn normalize_oracle_format(&self, format: &str) -> String {
17648        // Replace standalone HH with HH12 (but not HH12 or HH24)
17649        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
17650        let mut result = String::new();
17651        let chars: Vec<char> = format.chars().collect();
17652        let mut i = 0;
17653
17654        while i < chars.len() {
17655            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
17656                // Check what follows HH
17657                if i + 2 < chars.len() {
17658                    let next = chars[i + 2];
17659                    if next == '1' || next == '2' {
17660                        // This is HH12 or HH24, keep as is
17661                        result.push('H');
17662                        result.push('H');
17663                        i += 2;
17664                        continue;
17665                    }
17666                }
17667                // Standalone HH -> HH12
17668                result.push_str("HH12");
17669                i += 2;
17670            } else {
17671                result.push(chars[i]);
17672                i += 1;
17673            }
17674        }
17675
17676        result
17677    }
17678
17679    /// Check if the current dialect prefers :: cast syntax
17680    /// Preserve ClickHouse's native `::` shorthand when the parser saw it.
17681    fn dialect_prefers_double_colon(&self) -> bool {
17682        matches!(self.config.dialect, Some(DialectType::ClickHouse))
17683    }
17684
17685    /// Generate MOD function - uses % operator for dialects that prefer or require it.
17686    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
17687        use crate::dialects::DialectType;
17688
17689        // Several dialects prefer or require x % y instead of MOD(x, y).
17690        let use_percent_operator = matches!(
17691            self.config.dialect,
17692            Some(DialectType::Snowflake)
17693                | Some(DialectType::MySQL)
17694                | Some(DialectType::Presto)
17695                | Some(DialectType::Trino)
17696                | Some(DialectType::PostgreSQL)
17697                | Some(DialectType::DuckDB)
17698                | Some(DialectType::Hive)
17699                | Some(DialectType::Spark)
17700                | Some(DialectType::Databricks)
17701                | Some(DialectType::Athena)
17702                | Some(DialectType::TSQL)
17703                | Some(DialectType::Fabric)
17704        );
17705
17706        if use_percent_operator {
17707            // MOD(a, b) treats both arguments as grouped expressions. When
17708            // lowering to an infix operator, keep binary arguments grouped.
17709            let needs_paren = |e: &Expression| {
17710                matches!(
17711                    e,
17712                    Expression::Add(_)
17713                        | Expression::Sub(_)
17714                        | Expression::Mul(_)
17715                        | Expression::Div(_)
17716                        | Expression::Mod(_)
17717                        | Expression::ModFunc(_)
17718                )
17719            };
17720            if needs_paren(&f.this) {
17721                self.write("(");
17722                self.generate_expression(&f.this)?;
17723                self.write(")");
17724            } else {
17725                self.generate_expression(&f.this)?;
17726            }
17727            self.write(" % ");
17728            if needs_paren(&f.expression) {
17729                self.write("(");
17730                self.generate_expression(&f.expression)?;
17731                self.write(")");
17732            } else {
17733                self.generate_expression(&f.expression)?;
17734            }
17735            Ok(())
17736        } else {
17737            self.generate_binary_func("MOD", &f.this, &f.expression)
17738        }
17739    }
17740
17741    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
17742    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
17743        use crate::dialects::DialectType;
17744
17745        // Snowflake normalizes IFNULL to COALESCE
17746        let func_name = match self.config.dialect {
17747            Some(DialectType::Snowflake) => "COALESCE",
17748            _ => "IFNULL",
17749        };
17750
17751        self.generate_binary_func(func_name, &f.this, &f.expression)
17752    }
17753
17754    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
17755    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
17756        // Use original function name if preserved (for identity tests)
17757        if let Some(ref original_name) = f.original_name {
17758            return self.generate_binary_func(original_name, &f.this, &f.expression);
17759        }
17760
17761        // Otherwise, use dialect-specific function names
17762        use crate::dialects::DialectType;
17763        let func_name = match self.config.dialect {
17764            Some(DialectType::Snowflake)
17765            | Some(DialectType::ClickHouse)
17766            | Some(DialectType::PostgreSQL)
17767            | Some(DialectType::Presto)
17768            | Some(DialectType::Trino)
17769            | Some(DialectType::Athena)
17770            | Some(DialectType::DuckDB)
17771            | Some(DialectType::BigQuery)
17772            | Some(DialectType::Spark)
17773            | Some(DialectType::Databricks)
17774            | Some(DialectType::Hive) => "COALESCE",
17775            Some(DialectType::MySQL)
17776            | Some(DialectType::Doris)
17777            | Some(DialectType::StarRocks)
17778            | Some(DialectType::SingleStore)
17779            | Some(DialectType::TiDB) => "IFNULL",
17780            _ => "NVL",
17781        };
17782
17783        self.generate_binary_func(func_name, &f.this, &f.expression)
17784    }
17785
17786    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
17787    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
17788        use crate::dialects::DialectType;
17789
17790        // Snowflake normalizes STDDEV_SAMP to STDDEV
17791        let func_name = match self.config.dialect {
17792            Some(DialectType::Snowflake) => "STDDEV",
17793            _ => "STDDEV_SAMP",
17794        };
17795
17796        self.generate_agg_func(func_name, f)
17797    }
17798
17799    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
17800        self.generate_expression(&coll.this)?;
17801        self.write_space();
17802        self.write_keyword("COLLATE");
17803        self.write_space();
17804        if coll.quoted {
17805            // Single-quoted string: COLLATE 'de_DE'
17806            self.write("'");
17807            self.write(&coll.collation);
17808            self.write("'");
17809        } else if coll.double_quoted {
17810            // Double-quoted identifier: COLLATE "de_DE"
17811            self.write("\"");
17812            self.write(&coll.collation);
17813            self.write("\"");
17814        } else {
17815            // Unquoted identifier: COLLATE de_DE
17816            self.write(&coll.collation);
17817        }
17818        Ok(())
17819    }
17820
17821    fn generate_case(&mut self, case: &Case) -> Result<()> {
17822        // In pretty mode, decide whether to expand based on total text width
17823        let multiline_case = if self.config.pretty {
17824            // Build the flat representation to check width
17825            let mut statements: Vec<String> = Vec::new();
17826            let operand_str = if let Some(operand) = &case.operand {
17827                let s = self.generate_to_string(operand)?;
17828                statements.push(format!("CASE {}", s));
17829                s
17830            } else {
17831                statements.push("CASE".to_string());
17832                String::new()
17833            };
17834            let _ = operand_str;
17835            for (condition, result) in &case.whens {
17836                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
17837                statements.push(format!("THEN {}", self.generate_to_string(result)?));
17838            }
17839            if let Some(else_) = &case.else_ {
17840                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
17841            }
17842            statements.push("END".to_string());
17843            self.too_wide(&statements)
17844        } else {
17845            false
17846        };
17847
17848        self.write_keyword("CASE");
17849        if let Some(operand) = &case.operand {
17850            self.write_space();
17851            self.generate_expression(operand)?;
17852        }
17853        if multiline_case {
17854            self.indent_level += 1;
17855        }
17856        for (condition, result) in &case.whens {
17857            if multiline_case {
17858                self.write_newline();
17859                self.write_indent();
17860            } else {
17861                self.write_space();
17862            }
17863            self.write_keyword("WHEN");
17864            self.write_space();
17865            self.generate_expression(condition)?;
17866            if multiline_case {
17867                self.write_newline();
17868                self.write_indent();
17869            } else {
17870                self.write_space();
17871            }
17872            self.write_keyword("THEN");
17873            self.write_space();
17874            self.generate_expression(result)?;
17875        }
17876        if let Some(else_) = &case.else_ {
17877            if multiline_case {
17878                self.write_newline();
17879                self.write_indent();
17880            } else {
17881                self.write_space();
17882            }
17883            self.write_keyword("ELSE");
17884            self.write_space();
17885            self.generate_expression(else_)?;
17886        }
17887        if multiline_case {
17888            self.indent_level -= 1;
17889            self.write_newline();
17890            self.write_indent();
17891        } else {
17892            self.write_space();
17893        }
17894        self.write_keyword("END");
17895        // Emit any comments that were attached to the CASE keyword
17896        for comment in &case.comments {
17897            self.write(" ");
17898            self.write_formatted_comment(comment);
17899        }
17900        Ok(())
17901    }
17902
17903    fn generate_function(&mut self, func: &Function) -> Result<()> {
17904        // Normalize function name based on dialect settings
17905        let normalized_name = if matches!(self.config.dialect, Some(DialectType::Snowflake))
17906            && func.name.to_ascii_uppercase().starts_with("IDENTIFIER(")
17907        {
17908            Cow::Borrowed(func.name.as_str())
17909        } else {
17910            self.normalize_func_name(&func.name)
17911        };
17912
17913        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
17914        if matches!(self.config.dialect, Some(DialectType::DuckDB))
17915            && func.name.eq_ignore_ascii_case("ARRAY_CONSTRUCT_COMPACT")
17916        {
17917            self.write("LIST_FILTER(");
17918            self.write("[");
17919            for (i, arg) in func.args.iter().enumerate() {
17920                if i > 0 {
17921                    self.write(", ");
17922                }
17923                self.generate_expression(arg)?;
17924            }
17925            self.write("], _u -> NOT _u IS NULL)");
17926            return Ok(());
17927        }
17928
17929        // Snowflake fixtures expect TO_VARIANT applied to arrays to keep ARRAY_CONSTRUCT(...)
17930        // rather than bracket-array syntax.
17931        if matches!(self.config.dialect, Some(DialectType::Snowflake))
17932            && func.name.eq_ignore_ascii_case("TO_VARIANT")
17933            && func.args.len() == 1
17934        {
17935            let array_expressions = match &func.args[0] {
17936                Expression::ArrayFunc(arr) => Some(&arr.expressions),
17937                Expression::Array(arr) => Some(&arr.expressions),
17938                _ => None,
17939            };
17940            if let Some(expressions) = array_expressions {
17941                self.write_keyword("TO_VARIANT");
17942                self.write("(");
17943                self.write_keyword("ARRAY_CONSTRUCT");
17944                self.write("(");
17945                for (i, arg) in expressions.iter().enumerate() {
17946                    if i > 0 {
17947                        self.write(", ");
17948                    }
17949                    self.generate_expression(arg)?;
17950                }
17951                self.write(")");
17952                self.write(")");
17953                return Ok(());
17954            }
17955        }
17956
17957        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
17958        if func.name.eq_ignore_ascii_case("STRUCT")
17959            && !matches!(
17960                self.config.dialect,
17961                Some(DialectType::BigQuery)
17962                    | Some(DialectType::Spark)
17963                    | Some(DialectType::Databricks)
17964                    | Some(DialectType::Hive)
17965                    | None
17966            )
17967        {
17968            return self.generate_struct_function_cross_dialect(func);
17969        }
17970
17971        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
17972        // This is an internal marker function for ::? JSON path syntax
17973        if func.name.eq_ignore_ascii_case("__SS_JSON_PATH_QMARK__") && func.args.len() == 2 {
17974            self.generate_expression(&func.args[0])?;
17975            self.write("::?");
17976            // Extract the key from the string literal
17977            if let Expression::Literal(lit) = &func.args[1] {
17978                if let crate::expressions::Literal::String(key) = lit.as_ref() {
17979                    self.write(key);
17980                }
17981            } else {
17982                self.generate_expression(&func.args[1])?;
17983            }
17984            return Ok(());
17985        }
17986
17987        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
17988        if func.name.eq_ignore_ascii_case("__PG_BITWISE_XOR__") && func.args.len() == 2 {
17989            self.generate_expression(&func.args[0])?;
17990            self.write(" # ");
17991            self.generate_expression(&func.args[1])?;
17992            return Ok(());
17993        }
17994
17995        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
17996        if matches!(
17997            self.config.dialect,
17998            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
17999        ) && func.name.eq_ignore_ascii_case("TRY")
18000            && func.args.len() == 1
18001        {
18002            self.generate_expression(&func.args[0])?;
18003            return Ok(());
18004        }
18005
18006        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
18007        if self.config.dialect == Some(DialectType::ClickHouse)
18008            && func.name.eq_ignore_ascii_case("TOSTARTOFDAY")
18009            && func.args.len() == 1
18010        {
18011            self.write("dateTrunc('DAY', ");
18012            self.generate_expression(&func.args[0])?;
18013            self.write(")");
18014            return Ok(());
18015        }
18016
18017        // ClickHouse uses dateTrunc casing.
18018        if self.config.dialect == Some(DialectType::ClickHouse)
18019            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
18020            && func.args.len() == 2
18021        {
18022            self.write("dateTrunc(");
18023            self.generate_expression(&func.args[0])?;
18024            self.write(", ");
18025            self.generate_expression(&func.args[1])?;
18026            self.write(")");
18027            return Ok(());
18028        }
18029
18030        // Presto-family dialects spell SUBSTRING as SUBSTR in SQLGlot outputs.
18031        if matches!(
18032            self.config.dialect,
18033            Some(DialectType::Presto | DialectType::Trino | DialectType::Athena)
18034        ) && func.name.eq_ignore_ascii_case("SUBSTRING")
18035        {
18036            self.write_keyword("SUBSTR");
18037            self.write("(");
18038            for (i, arg) in func.args.iter().enumerate() {
18039                if i > 0 {
18040                    self.write(", ");
18041                }
18042                self.generate_expression(arg)?;
18043            }
18044            self.write(")");
18045            return Ok(());
18046        }
18047
18048        if self.config.dialect == Some(DialectType::Snowflake)
18049            && func.name.eq_ignore_ascii_case("LIST_DISTINCT")
18050            && func.args.len() == 1
18051        {
18052            self.write_keyword("ARRAY_DISTINCT");
18053            self.write("(");
18054            self.write_keyword("ARRAY_COMPACT");
18055            self.write("(");
18056            self.generate_expression(&func.args[0])?;
18057            self.write("))");
18058            return Ok(());
18059        }
18060
18061        if self.config.dialect == Some(DialectType::Snowflake)
18062            && func.name.eq_ignore_ascii_case("LIST")
18063            && func.args.len() == 1
18064            && !matches!(func.args.first(), Some(Expression::Select(_)))
18065        {
18066            self.write_keyword("ARRAY_AGG");
18067            self.write("(");
18068            self.generate_expression(&func.args[0])?;
18069            self.write(")");
18070            return Ok(());
18071        }
18072
18073        // Redshift: CONCAT(a, b, ...) -> a || b || ...
18074        if self.config.dialect == Some(DialectType::Redshift)
18075            && func.name.eq_ignore_ascii_case("CONCAT")
18076            && func.args.len() >= 2
18077        {
18078            for (i, arg) in func.args.iter().enumerate() {
18079                if i > 0 {
18080                    self.write(" || ");
18081                }
18082                self.generate_expression(arg)?;
18083            }
18084            return Ok(());
18085        }
18086
18087        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
18088        if self.config.dialect == Some(DialectType::Redshift)
18089            && func.name.eq_ignore_ascii_case("CONCAT_WS")
18090            && func.args.len() >= 2
18091        {
18092            let sep = &func.args[0];
18093            for (i, arg) in func.args.iter().skip(1).enumerate() {
18094                if i > 0 {
18095                    self.write(" || ");
18096                    self.generate_expression(sep)?;
18097                    self.write(" || ");
18098                }
18099                self.generate_expression(arg)?;
18100            }
18101            return Ok(());
18102        }
18103
18104        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
18105        // Unit should be unquoted uppercase identifier
18106        if self.config.dialect == Some(DialectType::Redshift)
18107            && (func.name.eq_ignore_ascii_case("DATEDIFF")
18108                || func.name.eq_ignore_ascii_case("DATE_DIFF"))
18109            && func.args.len() == 3
18110        {
18111            self.write_keyword("DATEDIFF");
18112            self.write("(");
18113            // First arg is unit - normalize to unquoted uppercase
18114            self.write_redshift_date_part(&func.args[0]);
18115            self.write(", ");
18116            self.generate_expression(&func.args[1])?;
18117            self.write(", ");
18118            self.generate_expression(&func.args[2])?;
18119            self.write(")");
18120            return Ok(());
18121        }
18122
18123        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
18124        // Unit should be unquoted uppercase identifier
18125        if self.config.dialect == Some(DialectType::Redshift)
18126            && (func.name.eq_ignore_ascii_case("DATEADD")
18127                || func.name.eq_ignore_ascii_case("DATE_ADD"))
18128            && func.args.len() == 3
18129        {
18130            self.write_keyword("DATEADD");
18131            self.write("(");
18132            // First arg is unit - normalize to unquoted uppercase
18133            self.write_redshift_date_part(&func.args[0]);
18134            self.write(", ");
18135            self.generate_expression(&func.args[1])?;
18136            self.write(", ");
18137            self.generate_expression(&func.args[2])?;
18138            self.write(")");
18139            return Ok(());
18140        }
18141
18142        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function.
18143        if func.name.eq_ignore_ascii_case("UUID_STRING")
18144            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
18145        {
18146            if matches!(
18147                self.config.dialect,
18148                Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
18149            ) {
18150                self.write_keyword("CAST");
18151                self.write("(");
18152                self.write_keyword("UUID");
18153                self.write("() ");
18154                self.write_keyword("AS");
18155                self.write(" ");
18156                self.write_keyword("STRING");
18157                self.write(")");
18158                return Ok(());
18159            }
18160
18161            if matches!(
18162                self.config.dialect,
18163                Some(DialectType::Presto | DialectType::Trino)
18164            ) {
18165                self.write_keyword("CAST");
18166                self.write("(");
18167                self.write_keyword("UUID");
18168                self.write("() ");
18169                self.write_keyword("AS");
18170                self.write(" ");
18171                self.write_keyword("VARCHAR");
18172                self.write(")");
18173                return Ok(());
18174            }
18175
18176            if self.config.dialect == Some(DialectType::DuckDB) && func.args.len() == 2 {
18177                self.write("(SELECT LOWER(SUBSTRING(h, 1, 8) || '-' || SUBSTRING(h, 9, 4) || '-' || '5' || SUBSTRING(h, 14, 3) || '-' || FORMAT('{:02x}', CAST('0x' || SUBSTRING(h, 17, 2) AS INT) & 63 | 128) || SUBSTRING(h, 19, 2) || '-' || SUBSTRING(h, 21, 12)) FROM (SELECT SUBSTRING(SHA1(UNHEX(REPLACE(");
18178                self.generate_expression(&func.args[0])?;
18179                self.write(", '-', '')) || ENCODE(");
18180                self.generate_expression(&func.args[1])?;
18181                self.write(")), 1, 32) AS h))");
18182                return Ok(());
18183            }
18184
18185            let func_name = match self.config.dialect {
18186                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
18187                Some(DialectType::BigQuery) => "GENERATE_UUID",
18188                _ => "UUID",
18189            };
18190            self.write_keyword(func_name);
18191            self.write("()");
18192            return Ok(());
18193        }
18194
18195        // Snowflake: GENERATOR(val) -> GENERATOR(ROWCOUNT => val)
18196        // GENERATOR(val1, val2) -> GENERATOR(ROWCOUNT => val1, TIMELIMIT => val2)
18197        // Positional args are mapped to named parameters.
18198        if matches!(self.config.dialect, Some(DialectType::Snowflake))
18199            && func.name.eq_ignore_ascii_case("GENERATOR")
18200        {
18201            let has_positional_args =
18202                !func.args.is_empty() && !matches!(&func.args[0], Expression::NamedArgument(_));
18203            if has_positional_args {
18204                let param_names = ["ROWCOUNT", "TIMELIMIT"];
18205                self.write_keyword("GENERATOR");
18206                self.write("(");
18207                for (i, arg) in func.args.iter().enumerate() {
18208                    if i > 0 {
18209                        self.write(", ");
18210                    }
18211                    if i < param_names.len() {
18212                        self.write_keyword(param_names[i]);
18213                        self.write(" => ");
18214                        self.generate_expression(arg)?;
18215                    } else {
18216                        self.generate_expression(arg)?;
18217                    }
18218                }
18219                self.write(")");
18220                return Ok(());
18221            }
18222        }
18223
18224        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
18225        // Unit should be quoted uppercase string
18226        if self.config.dialect == Some(DialectType::Redshift)
18227            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
18228            && func.args.len() == 2
18229        {
18230            self.write_keyword("DATE_TRUNC");
18231            self.write("(");
18232            // First arg is unit - normalize to quoted uppercase
18233            self.write_redshift_date_part_quoted(&func.args[0]);
18234            self.write(", ");
18235            self.generate_expression(&func.args[1])?;
18236            self.write(")");
18237            return Ok(());
18238        }
18239
18240        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
18241        if matches!(
18242            self.config.dialect,
18243            Some(DialectType::TSQL) | Some(DialectType::Fabric)
18244        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
18245            || func.name.eq_ignore_ascii_case("DATEPART"))
18246            && func.args.len() == 2
18247        {
18248            self.write_keyword("DATEPART");
18249            self.write("(");
18250            self.write_tsql_date_part(&func.args[0])?;
18251            self.write(", ");
18252            self.generate_expression(&func.args[1])?;
18253            self.write(")");
18254            return Ok(());
18255        }
18256
18257        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
18258        if matches!(
18259            self.config.dialect,
18260            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
18261        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
18262            || func.name.eq_ignore_ascii_case("DATEPART"))
18263            && func.args.len() == 2
18264        {
18265            self.write_keyword("EXTRACT");
18266            self.write("(");
18267            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
18268            match &func.args[0] {
18269                Expression::Literal(lit)
18270                    if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
18271                {
18272                    let crate::expressions::Literal::String(s) = lit.as_ref() else {
18273                        unreachable!()
18274                    };
18275                    self.write(&s.to_ascii_lowercase());
18276                }
18277                _ => self.generate_expression(&func.args[0])?,
18278            }
18279            self.write_space();
18280            self.write_keyword("FROM");
18281            self.write_space();
18282            self.generate_expression(&func.args[1])?;
18283            self.write(")");
18284            return Ok(());
18285        }
18286
18287        // PostgreSQL: DATE_ADD(date, INTERVAL '...') / DATE_SUB(...) -> infix interval arithmetic.
18288        if self.config.dialect == Some(DialectType::PostgreSQL)
18289            && matches!(
18290                func.name.to_ascii_uppercase().as_str(),
18291                "DATE_ADD" | "DATE_SUB"
18292            )
18293            && func.args.len() == 2
18294            && matches!(func.args[1], Expression::Interval(_))
18295        {
18296            self.generate_expression(&func.args[0])?;
18297            self.write_space();
18298            if func.name.eq_ignore_ascii_case("DATE_SUB") {
18299                self.write("-");
18300            } else {
18301                self.write("+");
18302            }
18303            self.write_space();
18304            self.generate_expression(&func.args[1])?;
18305            return Ok(());
18306        }
18307
18308        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
18309        // Also DATE literals in Dremio should be CAST(...AS DATE)
18310        if self.config.dialect == Some(DialectType::Dremio)
18311            && (func.name.eq_ignore_ascii_case("DATE_PART")
18312                || func.name.eq_ignore_ascii_case("DATEPART"))
18313            && func.args.len() == 2
18314        {
18315            self.write_keyword("EXTRACT");
18316            self.write("(");
18317            self.generate_expression(&func.args[0])?;
18318            self.write_space();
18319            self.write_keyword("FROM");
18320            self.write_space();
18321            // For Dremio, DATE literals should become CAST('value' AS DATE)
18322            self.generate_dremio_date_expression(&func.args[1])?;
18323            self.write(")");
18324            return Ok(());
18325        }
18326
18327        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
18328        if self.config.dialect == Some(DialectType::Dremio)
18329            && func.name.eq_ignore_ascii_case("CURRENT_DATE_UTC")
18330            && func.args.is_empty()
18331        {
18332            self.write_keyword("CURRENT_DATE_UTC");
18333            return Ok(());
18334        }
18335
18336        // Dremio: DATETYPE(year, month, day) transformation
18337        // - If all args are integer literals: DATE('YYYY-MM-DD')
18338        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
18339        if self.config.dialect == Some(DialectType::Dremio)
18340            && func.name.eq_ignore_ascii_case("DATETYPE")
18341            && func.args.len() == 3
18342        {
18343            // Helper function to extract integer from number literal
18344            fn get_int_literal(expr: &Expression) -> Option<i64> {
18345                if let Expression::Literal(lit) = expr {
18346                    if let crate::expressions::Literal::Number(s) = lit.as_ref() {
18347                        s.parse::<i64>().ok()
18348                    } else {
18349                        None
18350                    }
18351                } else {
18352                    None
18353                }
18354            }
18355
18356            // Check if all arguments are integer literals
18357            if let (Some(year), Some(month), Some(day)) = (
18358                get_int_literal(&func.args[0]),
18359                get_int_literal(&func.args[1]),
18360                get_int_literal(&func.args[2]),
18361            ) {
18362                // All are integer literals: DATE('YYYY-MM-DD')
18363                self.write_keyword("DATE");
18364                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
18365                return Ok(());
18366            }
18367
18368            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
18369            self.write_keyword("CAST");
18370            self.write("(");
18371            self.write_keyword("CONCAT");
18372            self.write("(");
18373            self.generate_expression(&func.args[0])?;
18374            self.write(", '-', ");
18375            self.generate_expression(&func.args[1])?;
18376            self.write(", '-', ");
18377            self.generate_expression(&func.args[2])?;
18378            self.write(")");
18379            self.write_space();
18380            self.write_keyword("AS");
18381            self.write_space();
18382            self.write_keyword("DATE");
18383            self.write(")");
18384            return Ok(());
18385        }
18386
18387        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
18388        // when it's not an integer literal
18389        let is_presto_like = matches!(
18390            self.config.dialect,
18391            Some(DialectType::Presto) | Some(DialectType::Trino)
18392        );
18393        if is_presto_like && func.name.eq_ignore_ascii_case("DATE_ADD") && func.args.len() == 3 {
18394            self.write_keyword("DATE_ADD");
18395            self.write("(");
18396            // First arg: unit (pass through as-is, e.g., 'DAY')
18397            self.generate_expression(&func.args[0])?;
18398            self.write(", ");
18399            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
18400            let interval = &func.args[1];
18401            let needs_cast = !self.returns_integer_type(interval);
18402            if needs_cast {
18403                self.write_keyword("CAST");
18404                self.write("(");
18405            }
18406            self.generate_expression(interval)?;
18407            if needs_cast {
18408                self.write_space();
18409                self.write_keyword("AS");
18410                self.write_space();
18411                self.write_keyword("BIGINT");
18412                self.write(")");
18413            }
18414            self.write(", ");
18415            // Third arg: date
18416            self.generate_expression(&func.args[2])?;
18417            self.write(")");
18418            return Ok(());
18419        }
18420
18421        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
18422        let use_brackets = func.use_bracket_syntax;
18423
18424        // Special case: functions WITH ORDINALITY need special output order
18425        // Input: FUNC(args) WITH ORDINALITY
18426        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
18427        // Output must be: FUNC(args) WITH ORDINALITY
18428        let has_ordinality = func.name.len() >= 16
18429            && func.name[func.name.len() - 16..].eq_ignore_ascii_case(" WITH ORDINALITY");
18430        let output_name = if has_ordinality {
18431            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
18432            self.normalize_func_name(base_name)
18433        } else {
18434            normalized_name.clone()
18435        };
18436
18437        // For qualified names (schema.function or object.method), preserve original case
18438        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
18439        let quote_source_clickhouse_function =
18440            matches!(self.config.dialect, Some(DialectType::ClickHouse))
18441                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
18442                && func.quoted;
18443
18444        if quote_source_clickhouse_function {
18445            self.generate_identifier(&Identifier {
18446                name: func.name.clone(),
18447                quoted: true,
18448                trailing_comments: Vec::new(),
18449                span: None,
18450            })?;
18451        } else if func.name.contains('.') && !has_ordinality {
18452            // Don't normalize qualified functions - preserve original case
18453            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
18454            if func.quoted {
18455                self.write("`");
18456                self.write(&func.name);
18457                self.write("`");
18458            } else {
18459                self.write(&func.name);
18460            }
18461        } else {
18462            self.write(&output_name);
18463        }
18464
18465        // If no_parens is true and there are no args, output just the function name
18466        // Unless the target dialect requires parens for this function
18467        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
18468            let needs_parens = if func.name.eq_ignore_ascii_case("CURRENT_USER")
18469                || func.name.eq_ignore_ascii_case("SESSION_USER")
18470                || func.name.eq_ignore_ascii_case("SYSTEM_USER")
18471            {
18472                matches!(
18473                    self.config.dialect,
18474                    Some(DialectType::Snowflake)
18475                        | Some(DialectType::Spark)
18476                        | Some(DialectType::Databricks)
18477                        | Some(DialectType::Hive)
18478                )
18479            } else {
18480                false
18481            };
18482            !needs_parens
18483        };
18484        if force_parens {
18485            // Output trailing comments
18486            for comment in &func.trailing_comments {
18487                self.write_space();
18488                self.write_formatted_comment(comment);
18489            }
18490            return Ok(());
18491        }
18492
18493        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
18494        if func.name.eq_ignore_ascii_case("CUBE")
18495            || func.name.eq_ignore_ascii_case("ROLLUP")
18496            || func.name.eq_ignore_ascii_case("GROUPING SETS")
18497        {
18498            self.write(" (");
18499        } else if use_brackets {
18500            self.write("[");
18501        } else {
18502            self.write("(");
18503        }
18504        if func.distinct {
18505            self.write_keyword("DISTINCT");
18506            self.write_space();
18507        }
18508
18509        // Check if arguments should be split onto multiple lines (pretty + too wide)
18510        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
18511            && (func.name.eq_ignore_ascii_case("TABLE")
18512                || func.name.eq_ignore_ascii_case("FLATTEN"));
18513        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
18514        let is_grouping_func = func.name.eq_ignore_ascii_case("GROUPING SETS")
18515            || func.name.eq_ignore_ascii_case("CUBE")
18516            || func.name.eq_ignore_ascii_case("ROLLUP");
18517        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
18518            if is_grouping_func {
18519                true
18520            } else {
18521                // Pre-render arguments to check total width
18522                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
18523                for arg in &func.args {
18524                    let mut temp_gen = Generator::with_arc_config(self.config.clone());
18525                    Arc::make_mut(&mut temp_gen.config).pretty = false; // Don't recurse into pretty
18526                    temp_gen.generate_expression(arg)?;
18527                    expr_strings.push(temp_gen.output);
18528                }
18529                self.too_wide(&expr_strings)
18530            }
18531        } else {
18532            false
18533        };
18534
18535        if should_split {
18536            // Split onto multiple lines
18537            self.write_newline();
18538            self.indent_level += 1;
18539            for (i, arg) in func.args.iter().enumerate() {
18540                self.write_indent();
18541                self.generate_expression(arg)?;
18542                if i + 1 < func.args.len() {
18543                    self.write(",");
18544                }
18545                self.write_newline();
18546            }
18547            self.indent_level -= 1;
18548            self.write_indent();
18549        } else {
18550            // All on one line
18551            for (i, arg) in func.args.iter().enumerate() {
18552                if i > 0 {
18553                    self.write(", ");
18554                }
18555                self.generate_expression(arg)?;
18556            }
18557        }
18558
18559        if use_brackets {
18560            self.write("]");
18561        } else {
18562            self.write(")");
18563        }
18564        // Append WITH ORDINALITY after closing paren for table-valued functions
18565        if has_ordinality {
18566            self.write_space();
18567            self.write_keyword("WITH ORDINALITY");
18568        }
18569        // Output trailing comments
18570        for comment in &func.trailing_comments {
18571            self.write_space();
18572            self.write_formatted_comment(comment);
18573        }
18574        Ok(())
18575    }
18576
18577    fn generate_function_emits(&mut self, fe: &FunctionEmits) -> Result<()> {
18578        self.generate_expression(&fe.this)?;
18579        self.write_keyword(" EMITS ");
18580        self.generate_expression(&fe.emits)?;
18581        Ok(())
18582    }
18583
18584    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
18585        // Normalize function name based on dialect settings
18586        let mut normalized_name = self.normalize_func_name(&func.name);
18587
18588        // Dialect-specific name mappings for aggregate functions
18589        if func.name.eq_ignore_ascii_case("MAX_BY") || func.name.eq_ignore_ascii_case("MIN_BY") {
18590            let is_max = func.name.eq_ignore_ascii_case("MAX_BY");
18591            match self.config.dialect {
18592                Some(DialectType::ClickHouse) => {
18593                    normalized_name = if is_max {
18594                        Cow::Borrowed("argMax")
18595                    } else {
18596                        Cow::Borrowed("argMin")
18597                    };
18598                }
18599                Some(DialectType::DuckDB) => {
18600                    normalized_name = if is_max {
18601                        Cow::Borrowed("ARG_MAX")
18602                    } else {
18603                        Cow::Borrowed("ARG_MIN")
18604                    };
18605                }
18606                _ => {}
18607            }
18608        }
18609        self.write(normalized_name.as_ref());
18610        self.write("(");
18611        if func.distinct {
18612            self.write_keyword("DISTINCT");
18613            self.write_space();
18614        }
18615
18616        // Check if we need to transform multi-arg COUNT DISTINCT
18617        // When dialect doesn't support multi_arg_distinct, transform:
18618        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
18619        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
18620        let needs_multi_arg_transform =
18621            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
18622
18623        if needs_multi_arg_transform {
18624            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
18625            self.write_keyword("CASE");
18626            for arg in &func.args {
18627                self.write_space();
18628                self.write_keyword("WHEN");
18629                self.write_space();
18630                self.generate_expression(arg)?;
18631                self.write_space();
18632                self.write_keyword("IS NULL THEN NULL");
18633            }
18634            self.write_space();
18635            self.write_keyword("ELSE");
18636            self.write(" (");
18637            for (i, arg) in func.args.iter().enumerate() {
18638                if i > 0 {
18639                    self.write(", ");
18640                }
18641                self.generate_expression(arg)?;
18642            }
18643            self.write(")");
18644            self.write_space();
18645            self.write_keyword("END");
18646        } else {
18647            for (i, arg) in func.args.iter().enumerate() {
18648                if i > 0 {
18649                    self.write(", ");
18650                }
18651                self.generate_expression(arg)?;
18652            }
18653        }
18654
18655        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
18656        let clickhouse_ignore_nulls_outside =
18657            matches!(self.config.dialect, Some(DialectType::ClickHouse));
18658        if self.config.ignore_nulls_in_func
18659            && !matches!(
18660                self.config.dialect,
18661                Some(DialectType::DuckDB) | Some(DialectType::ClickHouse)
18662            )
18663        {
18664            if let Some(ignore) = func.ignore_nulls {
18665                self.write_space();
18666                if ignore {
18667                    self.write_keyword("IGNORE NULLS");
18668                } else {
18669                    self.write_keyword("RESPECT NULLS");
18670                }
18671            }
18672        }
18673
18674        // ORDER BY inside aggregate
18675        if !func.order_by.is_empty() {
18676            self.write_space();
18677            self.write_keyword("ORDER BY");
18678            self.write_space();
18679            for (i, ord) in func.order_by.iter().enumerate() {
18680                if i > 0 {
18681                    self.write(", ");
18682                }
18683                self.generate_ordered(ord)?;
18684            }
18685        }
18686
18687        // LIMIT inside aggregate
18688        if let Some(limit) = &func.limit {
18689            self.write_space();
18690            self.write_keyword("LIMIT");
18691            self.write_space();
18692            // Check if this is a Tuple representing LIMIT offset, count
18693            if let Expression::Tuple(t) = limit.as_ref() {
18694                if t.expressions.len() == 2 {
18695                    self.generate_expression(&t.expressions[0])?;
18696                    self.write(", ");
18697                    self.generate_expression(&t.expressions[1])?;
18698                } else {
18699                    self.generate_expression(limit)?;
18700                }
18701            } else {
18702                self.generate_expression(limit)?;
18703            }
18704        }
18705
18706        self.write(")");
18707
18708        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
18709        if (!self.config.ignore_nulls_in_func || clickhouse_ignore_nulls_outside)
18710            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18711        {
18712            if let Some(ignore) = func.ignore_nulls {
18713                self.write_space();
18714                if ignore {
18715                    self.write_keyword("IGNORE NULLS");
18716                } else {
18717                    self.write_keyword("RESPECT NULLS");
18718                }
18719            }
18720        }
18721
18722        if let Some(filter) = &func.filter {
18723            self.write_space();
18724            self.write_keyword("FILTER");
18725            self.write("(");
18726            self.write_keyword("WHERE");
18727            self.write_space();
18728            self.generate_expression(filter)?;
18729            self.write(")");
18730        }
18731
18732        Ok(())
18733    }
18734
18735    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
18736        self.generate_expression(&wf.this)?;
18737
18738        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
18739        if let Some(keep) = &wf.keep {
18740            self.write_space();
18741            self.write_keyword("KEEP");
18742            self.write(" (");
18743            self.write_keyword("DENSE_RANK");
18744            self.write_space();
18745            if keep.first {
18746                self.write_keyword("FIRST");
18747            } else {
18748                self.write_keyword("LAST");
18749            }
18750            self.write_space();
18751            self.write_keyword("ORDER BY");
18752            self.write_space();
18753            for (i, ord) in keep.order_by.iter().enumerate() {
18754                if i > 0 {
18755                    self.write(", ");
18756                }
18757                self.generate_ordered(ord)?;
18758            }
18759            self.write(")");
18760        }
18761
18762        // Check if there's any OVER clause content
18763        let has_over = !wf.over.partition_by.is_empty()
18764            || !wf.over.order_by.is_empty()
18765            || wf.over.frame.is_some()
18766            || wf.over.window_name.is_some();
18767
18768        // Only output OVER if there's actual window specification (not just KEEP alone)
18769        if has_over {
18770            self.write_space();
18771            self.write_keyword("OVER");
18772
18773            // Check if this is just a bare named window reference (no parens needed)
18774            let has_specs = !wf.over.partition_by.is_empty()
18775                || !wf.over.order_by.is_empty()
18776                || wf.over.frame.is_some();
18777
18778            if wf.over.window_name.is_some() && !has_specs {
18779                // OVER window_name (without parentheses)
18780                self.write_space();
18781                self.write(&wf.over.window_name.as_ref().unwrap().name);
18782            } else {
18783                // OVER (...) or OVER (window_name ...)
18784                self.write(" (");
18785                self.generate_over(&wf.over)?;
18786                self.write(")");
18787            }
18788        } else if wf.keep.is_none() {
18789            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
18790            self.write_space();
18791            self.write_keyword("OVER");
18792            self.write(" ()");
18793        }
18794
18795        Ok(())
18796    }
18797
18798    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
18799    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
18800        self.generate_expression(&wg.this)?;
18801        self.write_space();
18802        self.write_keyword("WITHIN GROUP");
18803        self.write(" (");
18804        self.write_keyword("ORDER BY");
18805        self.write_space();
18806        for (i, ord) in wg.order_by.iter().enumerate() {
18807            if i > 0 {
18808                self.write(", ");
18809            }
18810            self.generate_ordered(ord)?;
18811        }
18812        self.write(")");
18813        Ok(())
18814    }
18815
18816    /// Generate the contents of an OVER clause (without parentheses)
18817    fn generate_over(&mut self, over: &Over) -> Result<()> {
18818        let mut has_content = false;
18819
18820        // Named window reference
18821        if let Some(name) = &over.window_name {
18822            self.write(&name.name);
18823            has_content = true;
18824        }
18825
18826        // PARTITION BY
18827        if !over.partition_by.is_empty() {
18828            if has_content {
18829                self.write_space();
18830            }
18831            self.write_keyword("PARTITION BY");
18832            self.write_space();
18833            for (i, expr) in over.partition_by.iter().enumerate() {
18834                if i > 0 {
18835                    self.write(", ");
18836                }
18837                self.generate_expression(expr)?;
18838            }
18839            has_content = true;
18840        }
18841
18842        // ORDER BY
18843        if !over.order_by.is_empty() {
18844            if has_content {
18845                self.write_space();
18846            }
18847            self.write_keyword("ORDER BY");
18848            self.write_space();
18849            for (i, ordered) in over.order_by.iter().enumerate() {
18850                if i > 0 {
18851                    self.write(", ");
18852                }
18853                self.generate_ordered(ordered)?;
18854            }
18855            has_content = true;
18856        }
18857
18858        // Window frame
18859        if let Some(frame) = &over.frame {
18860            if has_content {
18861                self.write_space();
18862            }
18863            self.generate_window_frame(frame)?;
18864        }
18865
18866        Ok(())
18867    }
18868
18869    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
18870        // Exasol uses lowercase for frame kind (rows/range/groups)
18871        let lowercase_frame = self.config.lowercase_window_frame_keywords;
18872
18873        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
18874        if !lowercase_frame {
18875            if let Some(kind_text) = &frame.kind_text {
18876                self.write(kind_text);
18877            } else {
18878                match frame.kind {
18879                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
18880                    WindowFrameKind::Range => self.write_keyword("RANGE"),
18881                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
18882                }
18883            }
18884        } else {
18885            match frame.kind {
18886                WindowFrameKind::Rows => self.write("rows"),
18887                WindowFrameKind::Range => self.write("range"),
18888                WindowFrameKind::Groups => self.write("groups"),
18889            }
18890        }
18891
18892        // Use BETWEEN format only when there's an explicit end bound,
18893        // or when normalize_window_frame_between is enabled and the start is a directional bound
18894        self.write_space();
18895        let should_normalize = self.config.normalize_window_frame_between
18896            && frame.end.is_none()
18897            && matches!(
18898                frame.start,
18899                WindowFrameBound::Preceding(_)
18900                    | WindowFrameBound::Following(_)
18901                    | WindowFrameBound::UnboundedPreceding
18902                    | WindowFrameBound::UnboundedFollowing
18903            );
18904
18905        if let Some(end) = &frame.end {
18906            // BETWEEN format: RANGE BETWEEN start AND end
18907            self.write_keyword("BETWEEN");
18908            self.write_space();
18909            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18910            self.write_space();
18911            self.write_keyword("AND");
18912            self.write_space();
18913            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
18914        } else if should_normalize {
18915            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
18916            self.write_keyword("BETWEEN");
18917            self.write_space();
18918            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18919            self.write_space();
18920            self.write_keyword("AND");
18921            self.write_space();
18922            self.write_keyword("CURRENT ROW");
18923        } else {
18924            // Single bound format: RANGE CURRENT ROW
18925            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18926        }
18927
18928        // EXCLUDE clause
18929        if let Some(exclude) = &frame.exclude {
18930            self.write_space();
18931            self.write_keyword("EXCLUDE");
18932            self.write_space();
18933            match exclude {
18934                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
18935                WindowFrameExclude::Group => self.write_keyword("GROUP"),
18936                WindowFrameExclude::Ties => self.write_keyword("TIES"),
18937                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
18938            }
18939        }
18940
18941        Ok(())
18942    }
18943
18944    fn generate_window_frame_bound(
18945        &mut self,
18946        bound: &WindowFrameBound,
18947        side_text: Option<&str>,
18948    ) -> Result<()> {
18949        // Exasol uses lowercase for preceding/following
18950        let lowercase_frame = self.config.lowercase_window_frame_keywords;
18951
18952        match bound {
18953            WindowFrameBound::CurrentRow => {
18954                self.write_keyword("CURRENT ROW");
18955            }
18956            WindowFrameBound::UnboundedPreceding => {
18957                self.write_keyword("UNBOUNDED");
18958                self.write_space();
18959                if lowercase_frame {
18960                    self.write("preceding");
18961                } else if let Some(text) = side_text {
18962                    self.write(text);
18963                } else {
18964                    self.write_keyword("PRECEDING");
18965                }
18966            }
18967            WindowFrameBound::UnboundedFollowing => {
18968                self.write_keyword("UNBOUNDED");
18969                self.write_space();
18970                if lowercase_frame {
18971                    self.write("following");
18972                } else if let Some(text) = side_text {
18973                    self.write(text);
18974                } else {
18975                    self.write_keyword("FOLLOWING");
18976                }
18977            }
18978            WindowFrameBound::Preceding(expr) => {
18979                self.generate_expression(expr)?;
18980                self.write_space();
18981                if lowercase_frame {
18982                    self.write("preceding");
18983                } else if let Some(text) = side_text {
18984                    self.write(text);
18985                } else {
18986                    self.write_keyword("PRECEDING");
18987                }
18988            }
18989            WindowFrameBound::Following(expr) => {
18990                self.generate_expression(expr)?;
18991                self.write_space();
18992                if lowercase_frame {
18993                    self.write("following");
18994                } else if let Some(text) = side_text {
18995                    self.write(text);
18996                } else {
18997                    self.write_keyword("FOLLOWING");
18998                }
18999            }
19000            WindowFrameBound::BarePreceding => {
19001                if lowercase_frame {
19002                    self.write("preceding");
19003                } else if let Some(text) = side_text {
19004                    self.write(text);
19005                } else {
19006                    self.write_keyword("PRECEDING");
19007                }
19008            }
19009            WindowFrameBound::BareFollowing => {
19010                if lowercase_frame {
19011                    self.write("following");
19012                } else if let Some(text) = side_text {
19013                    self.write(text);
19014                } else {
19015                    self.write_keyword("FOLLOWING");
19016                }
19017            }
19018            WindowFrameBound::Value(expr) => {
19019                // Bare numeric bound without PRECEDING/FOLLOWING
19020                self.generate_expression(expr)?;
19021            }
19022        }
19023        Ok(())
19024    }
19025
19026    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
19027        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
19028        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
19029        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
19030            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
19031            && !matches!(&interval.this, Some(Expression::Literal(_)));
19032
19033        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
19034        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
19035        if self.config.single_string_interval {
19036            if let (
19037                Some(Expression::Literal(lit)),
19038                Some(IntervalUnitSpec::Simple {
19039                    ref unit,
19040                    ref use_plural,
19041                }),
19042            ) = (&interval.this, &interval.unit)
19043            {
19044                if let Literal::String(ref val) = lit.as_ref() {
19045                    self.write_keyword("INTERVAL");
19046                    self.write_space();
19047                    let effective_plural = *use_plural && self.config.interval_allows_plural_form;
19048                    let unit_str = self.interval_unit_str(unit, effective_plural);
19049                    self.write("'");
19050                    self.write(val);
19051                    self.write(" ");
19052                    self.write(&unit_str);
19053                    self.write("'");
19054                    return Ok(());
19055                }
19056            }
19057        }
19058
19059        if !skip_interval_keyword {
19060            self.write_keyword("INTERVAL");
19061        }
19062
19063        // Generate value if present
19064        if let Some(ref value) = interval.this {
19065            if !skip_interval_keyword {
19066                self.write_space();
19067            }
19068            // If the value is a complex expression (not a literal/column/function call)
19069            // and there's a unit, wrap it in parentheses
19070            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
19071            let needs_parens = interval.unit.is_some()
19072                && matches!(
19073                    value,
19074                    Expression::Add(_)
19075                        | Expression::Sub(_)
19076                        | Expression::Mul(_)
19077                        | Expression::Div(_)
19078                        | Expression::Mod(_)
19079                        | Expression::BitwiseAnd(_)
19080                        | Expression::BitwiseOr(_)
19081                        | Expression::BitwiseXor(_)
19082                );
19083            if needs_parens {
19084                self.write("(");
19085            }
19086            self.generate_expression(value)?;
19087            if needs_parens {
19088                self.write(")");
19089            }
19090        }
19091
19092        // Generate unit if present
19093        if let Some(ref unit_spec) = interval.unit {
19094            self.write_space();
19095            self.write_interval_unit_spec(unit_spec)?;
19096        }
19097
19098        Ok(())
19099    }
19100
19101    /// Return the string representation of an interval unit
19102    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
19103        match (unit, use_plural) {
19104            (IntervalUnit::Year, false) => "YEAR",
19105            (IntervalUnit::Year, true) => "YEARS",
19106            (IntervalUnit::Quarter, false) => "QUARTER",
19107            (IntervalUnit::Quarter, true) => "QUARTERS",
19108            (IntervalUnit::Month, false) => "MONTH",
19109            (IntervalUnit::Month, true) => "MONTHS",
19110            (IntervalUnit::Week, false) => "WEEK",
19111            (IntervalUnit::Week, true) => "WEEKS",
19112            (IntervalUnit::Day, false) => "DAY",
19113            (IntervalUnit::Day, true) => "DAYS",
19114            (IntervalUnit::Hour, false) => "HOUR",
19115            (IntervalUnit::Hour, true) => "HOURS",
19116            (IntervalUnit::Minute, false) => "MINUTE",
19117            (IntervalUnit::Minute, true) => "MINUTES",
19118            (IntervalUnit::Second, false) => "SECOND",
19119            (IntervalUnit::Second, true) => "SECONDS",
19120            (IntervalUnit::Millisecond, false) => "MILLISECOND",
19121            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
19122            (IntervalUnit::Microsecond, false) => "MICROSECOND",
19123            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
19124            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
19125            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
19126        }
19127    }
19128
19129    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
19130        match unit_spec {
19131            IntervalUnitSpec::Simple { unit, use_plural } => {
19132                // If dialect doesn't allow plural forms, force singular
19133                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
19134                self.write_simple_interval_unit(unit, effective_plural);
19135            }
19136            IntervalUnitSpec::Span(span) => {
19137                self.write_simple_interval_unit(&span.this, false);
19138                self.write_space();
19139                self.write_keyword("TO");
19140                self.write_space();
19141                self.write_simple_interval_unit(&span.expression, false);
19142            }
19143            IntervalUnitSpec::ExprSpan(span) => {
19144                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
19145                self.generate_expression(&span.this)?;
19146                self.write_space();
19147                self.write_keyword("TO");
19148                self.write_space();
19149                self.generate_expression(&span.expression)?;
19150            }
19151            IntervalUnitSpec::Expr(expr) => {
19152                self.generate_expression(expr)?;
19153            }
19154        }
19155        Ok(())
19156    }
19157
19158    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
19159        // Output interval unit, respecting plural preference
19160        match (unit, use_plural) {
19161            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
19162            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
19163            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
19164            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
19165            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
19166            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
19167            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
19168            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
19169            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
19170            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
19171            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
19172            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
19173            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
19174            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
19175            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
19176            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
19177            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
19178            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
19179            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
19180            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
19181            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
19182            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
19183        }
19184    }
19185
19186    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
19187    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
19188    fn write_redshift_date_part(&mut self, expr: &Expression) {
19189        let part_str = self.extract_date_part_string(expr);
19190        if let Some(part) = part_str {
19191            let normalized = self.normalize_date_part(&part);
19192            self.write_keyword(&normalized);
19193        } else {
19194            // If we can't extract a date part string, fall back to generating the expression
19195            let _ = self.generate_expression(expr);
19196        }
19197    }
19198
19199    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
19200    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
19201    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
19202        let part_str = self.extract_date_part_string(expr);
19203        if let Some(part) = part_str {
19204            let normalized = self.normalize_date_part(&part);
19205            self.write("'");
19206            self.write(&normalized);
19207            self.write("'");
19208        } else {
19209            // If we can't extract a date part string, fall back to generating the expression
19210            let _ = self.generate_expression(expr);
19211        }
19212    }
19213
19214    fn write_tsql_date_part(&mut self, expr: &Expression) -> Result<()> {
19215        if let Some(part) = self.extract_date_part_string(expr) {
19216            let upper = part.to_ascii_uppercase();
19217            let unmapped = match upper.as_str() {
19218                "DAYOFWEEK" => "WEEKDAY",
19219                "WEEKISO" => "ISO_WEEK",
19220                "TIMEZONE_MINUTE" => "TZOFFSET",
19221                _ => part.as_str(),
19222            };
19223            self.write_keyword(unmapped);
19224        } else {
19225            self.generate_expression(expr)?;
19226        }
19227        Ok(())
19228    }
19229
19230    /// Extract date part string from expression (handles string literals and identifiers)
19231    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
19232        match expr {
19233            Expression::Literal(lit)
19234                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
19235            {
19236                let crate::expressions::Literal::String(s) = lit.as_ref() else {
19237                    unreachable!()
19238                };
19239                Some(s.clone())
19240            }
19241            Expression::Identifier(id) => Some(id.name.clone()),
19242            Expression::Var(v) => Some(v.this.clone()),
19243            Expression::Column(col) if col.table.is_none() => {
19244                // Simple column reference without table prefix, treat as identifier
19245                Some(col.name.name.clone())
19246            }
19247            _ => None,
19248        }
19249    }
19250
19251    /// Normalize date part to uppercase singular form
19252    /// days -> DAY, months -> MONTH, etc.
19253    fn normalize_date_part(&self, part: &str) -> String {
19254        let mut buf = [0u8; 64];
19255        let lower: &str = if part.len() <= 64 {
19256            for (i, b) in part.bytes().enumerate() {
19257                buf[i] = b.to_ascii_lowercase();
19258            }
19259            std::str::from_utf8(&buf[..part.len()]).unwrap_or(part)
19260        } else {
19261            return part.to_ascii_uppercase();
19262        };
19263        match lower {
19264            "day" | "days" | "d" => "DAY".to_string(),
19265            "month" | "months" | "mon" | "mons" | "mm" => "MONTH".to_string(),
19266            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
19267            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
19268            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
19269            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
19270            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
19271            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
19272            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
19273            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
19274            _ => part.to_ascii_uppercase(),
19275        }
19276    }
19277
19278    fn write_datetime_field(&mut self, field: &DateTimeField) {
19279        match field {
19280            DateTimeField::Year => self.write_keyword("YEAR"),
19281            DateTimeField::Month => self.write_keyword("MONTH"),
19282            DateTimeField::Day => self.write_keyword("DAY"),
19283            DateTimeField::Hour => self.write_keyword("HOUR"),
19284            DateTimeField::Minute => self.write_keyword("MINUTE"),
19285            DateTimeField::Second => self.write_keyword("SECOND"),
19286            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
19287            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
19288            DateTimeField::DayOfWeek => {
19289                let name = match self.config.dialect {
19290                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
19291                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => "WEEKDAY",
19292                    _ => "DOW",
19293                };
19294                self.write_keyword(name);
19295            }
19296            DateTimeField::DayOfYear => {
19297                let name = match self.config.dialect {
19298                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
19299                    _ => "DOY",
19300                };
19301                self.write_keyword(name);
19302            }
19303            DateTimeField::Week => self.write_keyword("WEEK"),
19304            DateTimeField::WeekWithModifier(modifier) => {
19305                self.write_keyword("WEEK");
19306                self.write("(");
19307                self.write(modifier);
19308                self.write(")");
19309            }
19310            DateTimeField::Quarter => self.write_keyword("QUARTER"),
19311            DateTimeField::Epoch => self.write_keyword("EPOCH"),
19312            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
19313            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
19314            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
19315            DateTimeField::Date => self.write_keyword("DATE"),
19316            DateTimeField::Time => self.write_keyword("TIME"),
19317            DateTimeField::Custom(name) => self.write(name),
19318        }
19319    }
19320
19321    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
19322    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
19323        match field {
19324            DateTimeField::Year => self.write("year"),
19325            DateTimeField::Month => self.write("month"),
19326            DateTimeField::Day => self.write("day"),
19327            DateTimeField::Hour => self.write("hour"),
19328            DateTimeField::Minute => self.write("minute"),
19329            DateTimeField::Second => self.write("second"),
19330            DateTimeField::Millisecond => self.write("millisecond"),
19331            DateTimeField::Microsecond => self.write("microsecond"),
19332            DateTimeField::DayOfWeek => self.write("dow"),
19333            DateTimeField::DayOfYear => self.write("doy"),
19334            DateTimeField::Week => self.write("week"),
19335            DateTimeField::WeekWithModifier(modifier) => {
19336                self.write("week(");
19337                self.write(modifier);
19338                self.write(")");
19339            }
19340            DateTimeField::Quarter => self.write("quarter"),
19341            DateTimeField::Epoch => self.write("epoch"),
19342            DateTimeField::Timezone => self.write("timezone"),
19343            DateTimeField::TimezoneHour => self.write("timezone_hour"),
19344            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
19345            DateTimeField::Date => self.write("date"),
19346            DateTimeField::Time => self.write("time"),
19347            DateTimeField::Custom(name) => self.write(name),
19348        }
19349    }
19350
19351    // Helper function generators
19352
19353    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
19354        self.write_keyword(name);
19355        self.write("(");
19356        self.generate_expression(arg)?;
19357        self.write(")");
19358        Ok(())
19359    }
19360
19361    /// Generate a unary function, using the original name if available for round-trip preservation
19362    fn generate_unary_func(
19363        &mut self,
19364        default_name: &str,
19365        f: &crate::expressions::UnaryFunc,
19366    ) -> Result<()> {
19367        let name = f.original_name.as_deref().unwrap_or(default_name);
19368        self.write_keyword(name);
19369        self.write("(");
19370        self.generate_expression(&f.this)?;
19371        self.write(")");
19372        Ok(())
19373    }
19374
19375    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
19376    fn generate_sqrt_cbrt(
19377        &mut self,
19378        f: &crate::expressions::UnaryFunc,
19379        func_name: &str,
19380        _op: &str,
19381    ) -> Result<()> {
19382        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
19383        // Always use function syntax for consistency
19384        self.write_keyword(func_name);
19385        self.write("(");
19386        self.generate_expression(&f.this)?;
19387        self.write(")");
19388        Ok(())
19389    }
19390
19391    fn generate_binary_func(
19392        &mut self,
19393        name: &str,
19394        arg1: &Expression,
19395        arg2: &Expression,
19396    ) -> Result<()> {
19397        self.write_keyword(name);
19398        self.write("(");
19399        self.generate_expression(arg1)?;
19400        self.write(", ");
19401        self.generate_expression(arg2)?;
19402        self.write(")");
19403        Ok(())
19404    }
19405
19406    /// Generate CHAR/CHR function with optional USING charset
19407    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
19408    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
19409    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
19410        // Use stored name if available, otherwise default to CHAR
19411        let func_name = f.name.as_deref().unwrap_or("CHAR");
19412        self.write_keyword(func_name);
19413        self.write("(");
19414        for (i, arg) in f.args.iter().enumerate() {
19415            if i > 0 {
19416                self.write(", ");
19417            }
19418            self.generate_expression(arg)?;
19419        }
19420        if let Some(ref charset) = f.charset {
19421            self.write(" ");
19422            self.write_keyword("USING");
19423            self.write(" ");
19424            self.write(charset);
19425        }
19426        self.write(")");
19427        Ok(())
19428    }
19429
19430    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
19431        use crate::dialects::DialectType;
19432
19433        match self.config.dialect {
19434            Some(DialectType::Teradata) => {
19435                // Teradata uses ** operator for exponentiation
19436                self.generate_expression(&f.this)?;
19437                self.write(" ** ");
19438                self.generate_expression(&f.expression)?;
19439                Ok(())
19440            }
19441            _ => {
19442                // Other dialects use POWER function
19443                self.generate_binary_func("POWER", &f.this, &f.expression)
19444            }
19445        }
19446    }
19447
19448    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
19449        self.write_func_name(name);
19450        self.write("(");
19451        for (i, arg) in args.iter().enumerate() {
19452            if i > 0 {
19453                self.write(", ");
19454            }
19455            self.generate_expression(arg)?;
19456        }
19457        self.write(")");
19458        Ok(())
19459    }
19460
19461    // String function generators
19462
19463    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
19464        self.write_keyword("CONCAT_WS");
19465        self.write("(");
19466        self.generate_expression(&f.separator)?;
19467        for expr in &f.expressions {
19468            self.write(", ");
19469            self.generate_expression(expr)?;
19470        }
19471        self.write(")");
19472        Ok(())
19473    }
19474
19475    fn collect_concat_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
19476        if let Expression::Concat(op) = expr {
19477            Self::collect_concat_operands(&op.left, out);
19478            Self::collect_concat_operands(&op.right, out);
19479        } else {
19480            out.push(expr);
19481        }
19482    }
19483
19484    fn generate_mysql_concat_from_concat(&mut self, op: &BinaryOp) -> Result<()> {
19485        let mut operands = Vec::new();
19486        Self::collect_concat_operands(&op.left, &mut operands);
19487        Self::collect_concat_operands(&op.right, &mut operands);
19488
19489        self.write_keyword("CONCAT");
19490        self.write("(");
19491        for (i, operand) in operands.iter().enumerate() {
19492            if i > 0 {
19493                self.write(", ");
19494            }
19495            self.generate_expression(operand)?;
19496        }
19497        self.write(")");
19498        Ok(())
19499    }
19500
19501    fn collect_dpipe_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
19502        if let Expression::DPipe(dpipe) = expr {
19503            Self::collect_dpipe_operands(&dpipe.this, out);
19504            Self::collect_dpipe_operands(&dpipe.expression, out);
19505        } else {
19506            out.push(expr);
19507        }
19508    }
19509
19510    fn generate_mysql_concat_from_dpipe(&mut self, e: &DPipe) -> Result<()> {
19511        let mut operands = Vec::new();
19512        Self::collect_dpipe_operands(&e.this, &mut operands);
19513        Self::collect_dpipe_operands(&e.expression, &mut operands);
19514
19515        self.write_keyword("CONCAT");
19516        self.write("(");
19517        for (i, operand) in operands.iter().enumerate() {
19518            if i > 0 {
19519                self.write(", ");
19520            }
19521            self.generate_expression(operand)?;
19522        }
19523        self.write(")");
19524        Ok(())
19525    }
19526
19527    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
19528        // Oracle and Presto-family dialects use SUBSTR; most others use SUBSTRING
19529        let use_substr = matches!(
19530            self.config.dialect,
19531            Some(
19532                DialectType::Oracle
19533                    | DialectType::Presto
19534                    | DialectType::Trino
19535                    | DialectType::Athena
19536            )
19537        );
19538        if use_substr {
19539            self.write_keyword("SUBSTR");
19540        } else {
19541            self.write_keyword("SUBSTRING");
19542        }
19543        self.write("(");
19544        self.generate_expression(&f.this)?;
19545        // PostgreSQL always uses FROM/FOR syntax
19546        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
19547        // Spark/Hive/TSQL/Fabric use comma syntax, not FROM/FOR syntax
19548        let use_comma_syntax = matches!(
19549            self.config.dialect,
19550            Some(DialectType::Spark)
19551                | Some(DialectType::Hive)
19552                | Some(DialectType::Databricks)
19553                | Some(DialectType::TSQL)
19554                | Some(DialectType::Fabric)
19555        );
19556        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
19557            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
19558            self.write_space();
19559            self.write_keyword("FROM");
19560            self.write_space();
19561            self.generate_expression(&f.start)?;
19562            if let Some(length) = &f.length {
19563                self.write_space();
19564                self.write_keyword("FOR");
19565                self.write_space();
19566                self.generate_expression(length)?;
19567            }
19568        } else {
19569            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
19570            self.write(", ");
19571            self.generate_expression(&f.start)?;
19572            if let Some(length) = &f.length {
19573                self.write(", ");
19574                self.generate_expression(length)?;
19575            }
19576        }
19577        self.write(")");
19578        Ok(())
19579    }
19580
19581    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
19582        self.write_keyword("OVERLAY");
19583        self.write("(");
19584        self.generate_expression(&f.this)?;
19585        self.write_space();
19586        self.write_keyword("PLACING");
19587        self.write_space();
19588        self.generate_expression(&f.replacement)?;
19589        self.write_space();
19590        self.write_keyword("FROM");
19591        self.write_space();
19592        self.generate_expression(&f.from)?;
19593        if let Some(length) = &f.length {
19594            self.write_space();
19595            self.write_keyword("FOR");
19596            self.write_space();
19597            self.generate_expression(length)?;
19598        }
19599        self.write(")");
19600        Ok(())
19601    }
19602
19603    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
19604        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
19605        // when no characters are specified (PostgreSQL style)
19606        if f.position_explicit && f.characters.is_none() {
19607            match f.position {
19608                TrimPosition::Leading => {
19609                    self.write_keyword("LTRIM");
19610                    self.write("(");
19611                    self.generate_expression(&f.this)?;
19612                    self.write(")");
19613                    return Ok(());
19614                }
19615                TrimPosition::Trailing => {
19616                    self.write_keyword("RTRIM");
19617                    self.write("(");
19618                    self.generate_expression(&f.this)?;
19619                    self.write(")");
19620                    return Ok(());
19621                }
19622                TrimPosition::Both => {
19623                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
19624                    // Fall through to standard TRIM handling
19625                }
19626            }
19627        }
19628
19629        self.write_keyword("TRIM");
19630        self.write("(");
19631        // When BOTH is specified without trim characters, simplify to just TRIM(str)
19632        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
19633        let force_standard = f.characters.is_some()
19634            && !f.sql_standard_syntax
19635            && matches!(
19636                self.config.dialect,
19637                Some(DialectType::Hive)
19638                    | Some(DialectType::Spark)
19639                    | Some(DialectType::Databricks)
19640                    | Some(DialectType::ClickHouse)
19641            );
19642        let use_standard = (f.sql_standard_syntax || force_standard)
19643            && !(f.position_explicit
19644                && f.characters.is_none()
19645                && matches!(f.position, TrimPosition::Both));
19646        if use_standard {
19647            // SQL standard syntax: TRIM(BOTH chars FROM str)
19648            // Only output position if it was explicitly specified
19649            if f.position_explicit {
19650                match f.position {
19651                    TrimPosition::Both => self.write_keyword("BOTH"),
19652                    TrimPosition::Leading => self.write_keyword("LEADING"),
19653                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
19654                }
19655                self.write_space();
19656            }
19657            if let Some(chars) = &f.characters {
19658                self.generate_expression(chars)?;
19659                self.write_space();
19660            }
19661            self.write_keyword("FROM");
19662            self.write_space();
19663            self.generate_expression(&f.this)?;
19664        } else {
19665            // Simple function syntax: TRIM(str) or TRIM(str, chars)
19666            self.generate_expression(&f.this)?;
19667            if let Some(chars) = &f.characters {
19668                self.write(", ");
19669                self.generate_expression(chars)?;
19670            }
19671        }
19672        self.write(")");
19673        Ok(())
19674    }
19675
19676    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
19677        self.write_keyword("REPLACE");
19678        self.write("(");
19679        self.generate_expression(&f.this)?;
19680        self.write(", ");
19681        self.generate_expression(&f.old)?;
19682        self.write(", ");
19683        self.generate_expression(&f.new)?;
19684        self.write(")");
19685        Ok(())
19686    }
19687
19688    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
19689        self.write_keyword(name);
19690        self.write("(");
19691        self.generate_expression(&f.this)?;
19692        self.write(", ");
19693        self.generate_expression(&f.length)?;
19694        self.write(")");
19695        Ok(())
19696    }
19697
19698    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
19699        self.write_keyword("REPEAT");
19700        self.write("(");
19701        self.generate_expression(&f.this)?;
19702        self.write(", ");
19703        self.generate_expression(&f.times)?;
19704        self.write(")");
19705        Ok(())
19706    }
19707
19708    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
19709        self.write_keyword(name);
19710        self.write("(");
19711        self.generate_expression(&f.this)?;
19712        self.write(", ");
19713        self.generate_expression(&f.length)?;
19714        if let Some(fill) = &f.fill {
19715            self.write(", ");
19716            self.generate_expression(fill)?;
19717        }
19718        self.write(")");
19719        Ok(())
19720    }
19721
19722    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
19723        self.write_keyword("SPLIT");
19724        self.write("(");
19725        self.generate_expression(&f.this)?;
19726        self.write(", ");
19727        self.generate_expression(&f.delimiter)?;
19728        self.write(")");
19729        Ok(())
19730    }
19731
19732    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
19733        use crate::dialects::DialectType;
19734        // PostgreSQL uses ~ operator for regex matching
19735        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
19736            self.generate_expression(&f.this)?;
19737            self.write(" ~ ");
19738            self.generate_expression(&f.pattern)?;
19739        } else if matches!(self.config.dialect, Some(DialectType::Exasol)) && f.flags.is_none() {
19740            // Exasol uses REGEXP_LIKE as infix binary operator
19741            self.generate_expression(&f.this)?;
19742            self.write_keyword(" REGEXP_LIKE ");
19743            self.generate_expression(&f.pattern)?;
19744        } else if matches!(
19745            self.config.dialect,
19746            Some(DialectType::SingleStore)
19747                | Some(DialectType::Spark)
19748                | Some(DialectType::Hive)
19749                | Some(DialectType::Databricks)
19750        ) && f.flags.is_none()
19751        {
19752            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
19753            self.generate_expression(&f.this)?;
19754            self.write_keyword(" RLIKE ");
19755            self.generate_expression(&f.pattern)?;
19756        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
19757            // StarRocks uses REGEXP function syntax
19758            self.write_keyword("REGEXP");
19759            self.write("(");
19760            self.generate_expression(&f.this)?;
19761            self.write(", ");
19762            self.generate_expression(&f.pattern)?;
19763            if let Some(flags) = &f.flags {
19764                self.write(", ");
19765                self.generate_expression(flags)?;
19766            }
19767            self.write(")");
19768        } else {
19769            self.write_keyword("REGEXP_LIKE");
19770            self.write("(");
19771            self.generate_expression(&f.this)?;
19772            self.write(", ");
19773            self.generate_expression(&f.pattern)?;
19774            if let Some(flags) = &f.flags {
19775                self.write(", ");
19776                self.generate_expression(flags)?;
19777            }
19778            self.write(")");
19779        }
19780        Ok(())
19781    }
19782
19783    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
19784        self.write_keyword("REGEXP_REPLACE");
19785        self.write("(");
19786        self.generate_expression(&f.this)?;
19787        self.write(", ");
19788        self.generate_expression(&f.pattern)?;
19789        self.write(", ");
19790        self.generate_expression(&f.replacement)?;
19791        if let Some(flags) = &f.flags {
19792            self.write(", ");
19793            self.generate_expression(flags)?;
19794        }
19795        self.write(")");
19796        Ok(())
19797    }
19798
19799    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
19800        self.write_keyword("REGEXP_EXTRACT");
19801        self.write("(");
19802        self.generate_expression(&f.this)?;
19803        self.write(", ");
19804        self.generate_expression(&f.pattern)?;
19805        if let Some(group) = &f.group {
19806            self.write(", ");
19807            self.generate_expression(group)?;
19808        }
19809        self.write(")");
19810        Ok(())
19811    }
19812
19813    // Math function generators
19814
19815    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
19816        self.write_keyword("ROUND");
19817        self.write("(");
19818        self.generate_expression(&f.this)?;
19819        if let Some(decimals) = &f.decimals {
19820            self.write(", ");
19821            self.generate_expression(decimals)?;
19822        }
19823        self.write(")");
19824        Ok(())
19825    }
19826
19827    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
19828        self.write_keyword("FLOOR");
19829        self.write("(");
19830        self.generate_expression(&f.this)?;
19831        // Handle Druid-style FLOOR(time TO unit) syntax
19832        if let Some(to) = &f.to {
19833            self.write(" ");
19834            self.write_keyword("TO");
19835            self.write(" ");
19836            self.generate_expression(to)?;
19837        } else if let Some(scale) = &f.scale {
19838            self.write(", ");
19839            self.generate_expression(scale)?;
19840        }
19841        self.write(")");
19842        Ok(())
19843    }
19844
19845    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
19846        self.write_keyword("CEIL");
19847        self.write("(");
19848        self.generate_expression(&f.this)?;
19849        // Handle Druid-style CEIL(time TO unit) syntax
19850        if let Some(to) = &f.to {
19851            self.write(" ");
19852            self.write_keyword("TO");
19853            self.write(" ");
19854            self.generate_expression(to)?;
19855        } else if let Some(decimals) = &f.decimals {
19856            self.write(", ");
19857            self.generate_expression(decimals)?;
19858        }
19859        self.write(")");
19860        Ok(())
19861    }
19862
19863    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
19864        use crate::expressions::Literal;
19865
19866        if let Some(base) = &f.base {
19867            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
19868            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
19869            if self.is_log_base_none() {
19870                if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "2"))
19871                {
19872                    self.write_func_name("LOG2");
19873                    self.write("(");
19874                    self.generate_expression(&f.this)?;
19875                    self.write(")");
19876                    return Ok(());
19877                } else if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "10"))
19878                {
19879                    self.write_func_name("LOG10");
19880                    self.write("(");
19881                    self.generate_expression(&f.this)?;
19882                    self.write(")");
19883                    return Ok(());
19884                }
19885                // Other bases: fall through to LOG(base, value) — best effort
19886            }
19887
19888            self.write_func_name("LOG");
19889            self.write("(");
19890            if self.is_log_value_first() {
19891                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
19892                self.generate_expression(&f.this)?;
19893                self.write(", ");
19894                self.generate_expression(base)?;
19895            } else {
19896                // Default (PostgreSQL, etc.): LOG(base, value)
19897                self.generate_expression(base)?;
19898                self.write(", ");
19899                self.generate_expression(&f.this)?;
19900            }
19901            self.write(")");
19902        } else {
19903            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
19904            self.write_func_name("LOG");
19905            self.write("(");
19906            self.generate_expression(&f.this)?;
19907            self.write(")");
19908        }
19909        Ok(())
19910    }
19911
19912    /// Whether the target dialect uses LOG(value, base) order (value first).
19913    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
19914    fn is_log_value_first(&self) -> bool {
19915        use crate::dialects::DialectType;
19916        matches!(
19917            self.config.dialect,
19918            Some(DialectType::BigQuery)
19919                | Some(DialectType::TSQL)
19920                | Some(DialectType::Tableau)
19921                | Some(DialectType::Fabric)
19922        )
19923    }
19924
19925    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
19926    /// Presto, Trino, ClickHouse, Athena.
19927    fn is_log_base_none(&self) -> bool {
19928        use crate::dialects::DialectType;
19929        matches!(
19930            self.config.dialect,
19931            Some(DialectType::Presto)
19932                | Some(DialectType::Trino)
19933                | Some(DialectType::ClickHouse)
19934                | Some(DialectType::Athena)
19935        )
19936    }
19937
19938    // Date/time function generators
19939
19940    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
19941        self.write_keyword("CURRENT_TIME");
19942        if let Some(precision) = f.precision {
19943            self.write(&format!("({})", precision));
19944        } else if matches!(
19945            self.config.dialect,
19946            Some(crate::dialects::DialectType::MySQL)
19947                | Some(crate::dialects::DialectType::SingleStore)
19948                | Some(crate::dialects::DialectType::TiDB)
19949        ) {
19950            self.write("()");
19951        }
19952        Ok(())
19953    }
19954
19955    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
19956        use crate::dialects::DialectType;
19957
19958        // Oracle/Redshift SYSDATE handling
19959        if f.sysdate {
19960            match self.config.dialect {
19961                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
19962                    self.write_keyword("SYSDATE");
19963                    return Ok(());
19964                }
19965                Some(DialectType::Snowflake) => {
19966                    // Snowflake uses SYSDATE() function
19967                    self.write_keyword("SYSDATE");
19968                    self.write("()");
19969                    return Ok(());
19970                }
19971                _ => {
19972                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
19973                }
19974            }
19975        }
19976
19977        self.write_keyword("CURRENT_TIMESTAMP");
19978        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
19979        if let Some(precision) = f.precision {
19980            self.write(&format!("({})", precision));
19981        } else if matches!(
19982            self.config.dialect,
19983            Some(crate::dialects::DialectType::MySQL)
19984                | Some(crate::dialects::DialectType::SingleStore)
19985                | Some(crate::dialects::DialectType::TiDB)
19986                | Some(crate::dialects::DialectType::Spark)
19987                | Some(crate::dialects::DialectType::Hive)
19988                | Some(crate::dialects::DialectType::Databricks)
19989                | Some(crate::dialects::DialectType::ClickHouse)
19990                | Some(crate::dialects::DialectType::BigQuery)
19991                | Some(crate::dialects::DialectType::Snowflake)
19992                | Some(crate::dialects::DialectType::Exasol)
19993        ) {
19994            self.write("()");
19995        }
19996        Ok(())
19997    }
19998
19999    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
20000        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
20001        if self.config.dialect == Some(DialectType::Exasol) {
20002            self.write_keyword("CONVERT_TZ");
20003            self.write("(");
20004            self.generate_expression(&f.this)?;
20005            self.write(", 'UTC', ");
20006            self.generate_expression(&f.zone)?;
20007            self.write(")");
20008            return Ok(());
20009        }
20010
20011        self.generate_expression(&f.this)?;
20012        self.write_space();
20013        self.write_keyword("AT TIME ZONE");
20014        self.write_space();
20015        self.generate_expression(&f.zone)?;
20016        Ok(())
20017    }
20018
20019    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
20020        use crate::dialects::DialectType;
20021
20022        // Presto/Trino use DATE_ADD('unit', interval, date) format
20023        // with the interval cast to BIGINT when needed
20024        let is_presto_like = matches!(
20025            self.config.dialect,
20026            Some(DialectType::Presto) | Some(DialectType::Trino)
20027        );
20028
20029        if is_presto_like {
20030            self.write_keyword(name);
20031            self.write("(");
20032            // Unit as string literal
20033            self.write("'");
20034            self.write_simple_interval_unit(&f.unit, false);
20035            self.write("'");
20036            self.write(", ");
20037            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
20038            let needs_cast = !self.returns_integer_type(&f.interval);
20039            if needs_cast {
20040                self.write_keyword("CAST");
20041                self.write("(");
20042            }
20043            self.generate_expression(&f.interval)?;
20044            if needs_cast {
20045                self.write_space();
20046                self.write_keyword("AS");
20047                self.write_space();
20048                self.write_keyword("BIGINT");
20049                self.write(")");
20050            }
20051            self.write(", ");
20052            self.generate_expression(&f.this)?;
20053            self.write(")");
20054        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
20055            self.generate_expression(&f.this)?;
20056            self.write_space();
20057            if name.eq_ignore_ascii_case("DATE_SUB") {
20058                self.write("-");
20059            } else {
20060                self.write("+");
20061            }
20062            self.write_space();
20063            self.write_keyword("INTERVAL");
20064            self.write_space();
20065            self.write("'");
20066            let mut interval_gen = Generator::with_arc_config(self.config.clone());
20067            let interval_sql = interval_gen.generate(&f.interval)?;
20068            self.write(&interval_sql);
20069            self.write(" ");
20070            self.write_simple_interval_unit(&f.unit, false);
20071            self.write("'");
20072        } else {
20073            self.write_keyword(name);
20074            self.write("(");
20075            self.generate_expression(&f.this)?;
20076            self.write(", ");
20077            self.write_keyword("INTERVAL");
20078            self.write_space();
20079            self.generate_expression(&f.interval)?;
20080            self.write_space();
20081            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
20082            self.write(")");
20083        }
20084        Ok(())
20085    }
20086
20087    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
20088    /// This is a heuristic to avoid full type inference
20089    fn returns_integer_type(&self, expr: &Expression) -> bool {
20090        use crate::expressions::{DataType, Literal};
20091        match expr {
20092            // Integer literals (no decimal point)
20093            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
20094                let Literal::Number(n) = lit.as_ref() else {
20095                    unreachable!()
20096                };
20097                !n.contains('.')
20098            }
20099
20100            // FLOOR(x) returns integer if x is integer
20101            Expression::Floor(f) => self.returns_integer_type(&f.this),
20102
20103            // ROUND(x) returns integer if x is integer
20104            Expression::Round(f) => {
20105                // Only if no decimals arg or it's returning an integer
20106                f.decimals.is_none() && self.returns_integer_type(&f.this)
20107            }
20108
20109            // SIGN returns integer if input is integer
20110            Expression::Sign(f) => self.returns_integer_type(&f.this),
20111
20112            // ABS returns the same type as input
20113            Expression::Abs(f) => self.returns_integer_type(&f.this),
20114
20115            // Arithmetic operations on integers return integers
20116            Expression::Mul(op) => {
20117                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
20118            }
20119            Expression::Add(op) => {
20120                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
20121            }
20122            Expression::Sub(op) => {
20123                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
20124            }
20125            Expression::Mod(op) => self.returns_integer_type(&op.left),
20126
20127            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
20128            Expression::Cast(c) => matches!(
20129                &c.to,
20130                DataType::BigInt { .. }
20131                    | DataType::Int { .. }
20132                    | DataType::SmallInt { .. }
20133                    | DataType::TinyInt { .. }
20134            ),
20135
20136            // Negation: -x returns integer if x is integer
20137            Expression::Neg(op) => self.returns_integer_type(&op.this),
20138
20139            // Parenthesized expression
20140            Expression::Paren(p) => self.returns_integer_type(&p.this),
20141
20142            // Column references and most expressions are assumed to need casting
20143            // since we don't have full type information
20144            _ => false,
20145        }
20146    }
20147
20148    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
20149        self.write_keyword("DATEDIFF");
20150        self.write("(");
20151        if let Some(unit) = &f.unit {
20152            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
20153            self.write(", ");
20154        }
20155        if self.config.dialect == Some(DialectType::Snowflake) {
20156            self.generate_expression(&f.expression)?;
20157            self.write(", ");
20158            self.generate_expression(&f.this)?;
20159        } else {
20160            self.generate_expression(&f.this)?;
20161            self.write(", ");
20162            self.generate_expression(&f.expression)?;
20163        }
20164        self.write(")");
20165        Ok(())
20166    }
20167
20168    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
20169        if self.config.dialect == Some(DialectType::ClickHouse) {
20170            self.write("dateTrunc");
20171        } else {
20172            self.write_keyword("DATE_TRUNC");
20173        }
20174        self.write("('");
20175        self.write_datetime_field(&f.unit);
20176        self.write("', ");
20177        self.generate_expression(&f.this)?;
20178        self.write(")");
20179        Ok(())
20180    }
20181
20182    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
20183        use crate::dialects::DialectType;
20184        use crate::expressions::DateTimeField;
20185
20186        self.write_keyword("LAST_DAY");
20187        self.write("(");
20188        self.generate_expression(&f.this)?;
20189        if let Some(unit) = &f.unit {
20190            self.write(", ");
20191            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
20192            // WEEK(SUNDAY) -> WEEK
20193            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
20194                if let DateTimeField::WeekWithModifier(_) = unit {
20195                    self.write_keyword("WEEK");
20196                } else {
20197                    self.write_datetime_field(unit);
20198                }
20199            } else {
20200                self.write_datetime_field(unit);
20201            }
20202        }
20203        self.write(")");
20204        Ok(())
20205    }
20206
20207    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
20208        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
20209        if matches!(
20210            self.config.dialect,
20211            Some(DialectType::TSQL) | Some(DialectType::Fabric)
20212        ) {
20213            self.write_keyword("DATEPART");
20214            self.write("(");
20215            self.write_datetime_field(&f.field);
20216            self.write(", ");
20217            self.generate_expression(&f.this)?;
20218            self.write(")");
20219            return Ok(());
20220        }
20221        self.write_keyword("EXTRACT");
20222        self.write("(");
20223        // Hive/Spark use lowercase datetime fields in EXTRACT
20224        if matches!(
20225            self.config.dialect,
20226            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
20227        ) {
20228            self.write_datetime_field_lower(&f.field);
20229        } else {
20230            self.write_datetime_field(&f.field);
20231        }
20232        self.write_space();
20233        self.write_keyword("FROM");
20234        self.write_space();
20235        self.generate_expression(&f.this)?;
20236        self.write(")");
20237        Ok(())
20238    }
20239
20240    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
20241        self.write_keyword("TO_DATE");
20242        self.write("(");
20243        self.generate_expression(&f.this)?;
20244        if let Some(format) = &f.format {
20245            self.write(", ");
20246            self.generate_expression(format)?;
20247        }
20248        self.write(")");
20249        Ok(())
20250    }
20251
20252    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
20253        self.write_keyword("TO_TIMESTAMP");
20254        self.write("(");
20255        self.generate_expression(&f.this)?;
20256        if let Some(format) = &f.format {
20257            self.write(", ");
20258            self.generate_expression(format)?;
20259        }
20260        self.write(")");
20261        Ok(())
20262    }
20263
20264    // Control flow function generators
20265
20266    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
20267        use crate::dialects::DialectType;
20268
20269        // Generic mode: normalize IF to CASE WHEN
20270        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
20271            self.write_keyword("CASE WHEN");
20272            self.write_space();
20273            self.generate_expression(&f.condition)?;
20274            self.write_space();
20275            self.write_keyword("THEN");
20276            self.write_space();
20277            self.generate_expression(&f.true_value)?;
20278            if let Some(false_val) = &f.false_value {
20279                self.write_space();
20280                self.write_keyword("ELSE");
20281                self.write_space();
20282                self.generate_expression(false_val)?;
20283            }
20284            self.write_space();
20285            self.write_keyword("END");
20286            return Ok(());
20287        }
20288
20289        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
20290        if self.config.dialect == Some(DialectType::Exasol) {
20291            self.write_keyword("IF");
20292            self.write_space();
20293            self.generate_expression(&f.condition)?;
20294            self.write_space();
20295            self.write_keyword("THEN");
20296            self.write_space();
20297            self.generate_expression(&f.true_value)?;
20298            if let Some(false_val) = &f.false_value {
20299                self.write_space();
20300                self.write_keyword("ELSE");
20301                self.write_space();
20302                self.generate_expression(false_val)?;
20303            }
20304            self.write_space();
20305            self.write_keyword("ENDIF");
20306            return Ok(());
20307        }
20308
20309        // Choose function name based on target dialect
20310        let func_name = match self.config.dialect {
20311            Some(DialectType::ClickHouse) => f.original_name.as_deref().unwrap_or("IF"),
20312            Some(DialectType::Snowflake) => "IFF",
20313            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
20314            Some(DialectType::Drill) => "`IF`",
20315            _ => "IF",
20316        };
20317        self.write(func_name);
20318        self.write("(");
20319        self.generate_expression(&f.condition)?;
20320        self.write(", ");
20321        self.generate_expression(&f.true_value)?;
20322        if let Some(false_val) = &f.false_value {
20323            self.write(", ");
20324            self.generate_expression(false_val)?;
20325        }
20326        self.write(")");
20327        Ok(())
20328    }
20329
20330    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
20331        self.write_keyword("NVL2");
20332        self.write("(");
20333        self.generate_expression(&f.this)?;
20334        self.write(", ");
20335        self.generate_expression(&f.true_value)?;
20336        self.write(", ");
20337        self.generate_expression(&f.false_value)?;
20338        self.write(")");
20339        Ok(())
20340    }
20341
20342    // Typed aggregate function generators
20343
20344    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
20345        // Use normalize_functions for COUNT to respect ClickHouse case preservation
20346        let count_name = match self.config.normalize_functions {
20347            NormalizeFunctions::Upper => "COUNT".to_string(),
20348            NormalizeFunctions::Lower => "count".to_string(),
20349            NormalizeFunctions::None => f
20350                .original_name
20351                .clone()
20352                .unwrap_or_else(|| "COUNT".to_string()),
20353        };
20354        self.write(&count_name);
20355        self.write("(");
20356        if f.distinct {
20357            self.write_keyword("DISTINCT");
20358            self.write_space();
20359        }
20360        if f.star {
20361            self.write("*");
20362        } else if let Some(ref expr) = f.this {
20363            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
20364            if let Expression::Tuple(tuple) = expr {
20365                // Check if we need to transform multi-arg COUNT DISTINCT
20366                // When dialect doesn't support multi_arg_distinct, transform:
20367                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
20368                let needs_transform =
20369                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
20370
20371                if needs_transform {
20372                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
20373                    self.write_keyword("CASE");
20374                    for e in &tuple.expressions {
20375                        self.write_space();
20376                        self.write_keyword("WHEN");
20377                        self.write_space();
20378                        self.generate_expression(e)?;
20379                        self.write_space();
20380                        self.write_keyword("IS NULL THEN NULL");
20381                    }
20382                    self.write_space();
20383                    self.write_keyword("ELSE");
20384                    self.write(" (");
20385                    for (i, e) in tuple.expressions.iter().enumerate() {
20386                        if i > 0 {
20387                            self.write(", ");
20388                        }
20389                        self.generate_expression(e)?;
20390                    }
20391                    self.write(")");
20392                    self.write_space();
20393                    self.write_keyword("END");
20394                } else {
20395                    for (i, e) in tuple.expressions.iter().enumerate() {
20396                        if i > 0 {
20397                            self.write(", ");
20398                        }
20399                        self.generate_expression(e)?;
20400                    }
20401                }
20402            } else {
20403                self.generate_expression(expr)?;
20404            }
20405        }
20406        let clickhouse_ignore_nulls_outside =
20407            matches!(self.config.dialect, Some(DialectType::ClickHouse));
20408        if let Some(ignore) = f.ignore_nulls.filter(|_| !clickhouse_ignore_nulls_outside) {
20409            self.write_space();
20410            if ignore {
20411                self.write_keyword("IGNORE NULLS");
20412            } else {
20413                self.write_keyword("RESPECT NULLS");
20414            }
20415        }
20416        self.write(")");
20417        if let Some(ignore) = f.ignore_nulls.filter(|_| clickhouse_ignore_nulls_outside) {
20418            self.write_space();
20419            if ignore {
20420                self.write_keyword("IGNORE NULLS");
20421            } else {
20422                self.write_keyword("RESPECT NULLS");
20423            }
20424        }
20425        if let Some(ref filter) = f.filter {
20426            self.write_space();
20427            self.write_keyword("FILTER");
20428            self.write("(");
20429            self.write_keyword("WHERE");
20430            self.write_space();
20431            self.generate_expression(filter)?;
20432            self.write(")");
20433        }
20434        Ok(())
20435    }
20436
20437    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
20438        // Apply function name normalization based on config
20439        let func_name: Cow<'_, str> = match self.config.normalize_functions {
20440            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
20441            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
20442            NormalizeFunctions::None => {
20443                // Use the original function name from parsing if available,
20444                // otherwise fall back to lowercase of the hardcoded constant
20445                if let Some(ref original) = f.name {
20446                    Cow::Owned(original.clone())
20447                } else {
20448                    Cow::Owned(name.to_ascii_lowercase())
20449                }
20450            }
20451        };
20452        self.write(func_name.as_ref());
20453        self.write("(");
20454        if f.distinct {
20455            self.write_keyword("DISTINCT");
20456            self.write_space();
20457        }
20458        // MODE() uses a NULL placeholder internally for its zero-arg ordered-set form.
20459        // Other aggregates may legitimately receive NULL as an explicit argument.
20460        let is_zero_arg_mode =
20461            name.eq_ignore_ascii_case("MODE") && matches!(f.this, Expression::Null(_));
20462        if !is_zero_arg_mode {
20463            self.generate_expression(&f.this)?;
20464        }
20465        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
20466        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
20467        if self.config.ignore_nulls_in_func
20468            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
20469        {
20470            match f.ignore_nulls {
20471                Some(true) => {
20472                    self.write_space();
20473                    self.write_keyword("IGNORE NULLS");
20474                }
20475                Some(false) => {
20476                    self.write_space();
20477                    self.write_keyword("RESPECT NULLS");
20478                }
20479                None => {}
20480            }
20481        }
20482        // Generate HAVING MAX/MIN if present (BigQuery syntax)
20483        // e.g., ANY_VALUE(fruit HAVING MAX sold)
20484        if let Some((ref expr, is_max)) = f.having_max {
20485            self.write_space();
20486            self.write_keyword("HAVING");
20487            self.write_space();
20488            if is_max {
20489                self.write_keyword("MAX");
20490            } else {
20491                self.write_keyword("MIN");
20492            }
20493            self.write_space();
20494            self.generate_expression(expr)?;
20495        }
20496        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
20497        if !f.order_by.is_empty() {
20498            self.write_space();
20499            self.write_keyword("ORDER BY");
20500            self.write_space();
20501            for (i, ord) in f.order_by.iter().enumerate() {
20502                if i > 0 {
20503                    self.write(", ");
20504                }
20505                self.generate_ordered(ord)?;
20506            }
20507        }
20508        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
20509        if let Some(ref limit) = f.limit {
20510            self.write_space();
20511            self.write_keyword("LIMIT");
20512            self.write_space();
20513            // Check if this is a Tuple representing LIMIT offset, count
20514            if let Expression::Tuple(t) = limit.as_ref() {
20515                if t.expressions.len() == 2 {
20516                    self.generate_expression(&t.expressions[0])?;
20517                    self.write(", ");
20518                    self.generate_expression(&t.expressions[1])?;
20519                } else {
20520                    self.generate_expression(limit)?;
20521                }
20522            } else {
20523                self.generate_expression(limit)?;
20524            }
20525        }
20526        self.write(")");
20527        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
20528        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
20529        if !self.config.ignore_nulls_in_func
20530            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
20531        {
20532            match f.ignore_nulls {
20533                Some(true) => {
20534                    self.write_space();
20535                    self.write_keyword("IGNORE NULLS");
20536                }
20537                Some(false) => {
20538                    self.write_space();
20539                    self.write_keyword("RESPECT NULLS");
20540                }
20541                None => {}
20542            }
20543        }
20544        if let Some(ref filter) = f.filter {
20545            self.write_space();
20546            self.write_keyword("FILTER");
20547            self.write("(");
20548            self.write_keyword("WHERE");
20549            self.write_space();
20550            self.generate_expression(filter)?;
20551            self.write(")");
20552        }
20553        Ok(())
20554    }
20555
20556    /// Generate FIRST/LAST aggregate functions with Hive/Spark2-style boolean argument
20557    /// for IGNORE NULLS. In Hive/Spark2, `FIRST(col) IGNORE NULLS` is written as `FIRST(col, TRUE)`.
20558    fn generate_agg_func_with_ignore_nulls_bool(&mut self, name: &str, f: &AggFunc) -> Result<()> {
20559        // For Hive/Spark2 dialects, convert IGNORE NULLS to boolean TRUE argument
20560        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
20561            // Create a modified copy without ignore_nulls, add TRUE as part of the output
20562            let func_name: Cow<'_, str> = match self.config.normalize_functions {
20563                NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
20564                NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
20565                NormalizeFunctions::None => {
20566                    if let Some(ref original) = f.name {
20567                        Cow::Owned(original.clone())
20568                    } else {
20569                        Cow::Owned(name.to_ascii_lowercase())
20570                    }
20571                }
20572            };
20573            self.write(func_name.as_ref());
20574            self.write("(");
20575            if f.distinct {
20576                self.write_keyword("DISTINCT");
20577                self.write_space();
20578            }
20579            if !matches!(f.this, Expression::Null(_)) {
20580                self.generate_expression(&f.this)?;
20581            }
20582            self.write(", ");
20583            self.write_keyword("TRUE");
20584            self.write(")");
20585            return Ok(());
20586        }
20587        self.generate_agg_func(name, f)
20588    }
20589
20590    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
20591        self.write_keyword("GROUP_CONCAT");
20592        self.write("(");
20593        if f.distinct {
20594            self.write_keyword("DISTINCT");
20595            self.write_space();
20596        }
20597        self.generate_expression(&f.this)?;
20598        if let Some(ref order_by) = f.order_by {
20599            self.write_space();
20600            self.write_keyword("ORDER BY");
20601            self.write_space();
20602            for (i, ord) in order_by.iter().enumerate() {
20603                if i > 0 {
20604                    self.write(", ");
20605                }
20606                self.generate_ordered(ord)?;
20607            }
20608        }
20609        if let Some(ref sep) = f.separator {
20610            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
20611            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
20612            if matches!(
20613                self.config.dialect,
20614                Some(crate::dialects::DialectType::SQLite)
20615            ) {
20616                self.write(", ");
20617                self.generate_expression(sep)?;
20618            } else {
20619                self.write_space();
20620                self.write_keyword("SEPARATOR");
20621                self.write_space();
20622                self.generate_expression(sep)?;
20623            }
20624        }
20625        if let Some(ref limit) = f.limit {
20626            self.write_space();
20627            self.write_keyword("LIMIT");
20628            self.write_space();
20629            self.generate_expression(limit)?;
20630        }
20631        self.write(")");
20632        if let Some(ref filter) = f.filter {
20633            self.write_space();
20634            self.write_keyword("FILTER");
20635            self.write("(");
20636            self.write_keyword("WHERE");
20637            self.write_space();
20638            self.generate_expression(filter)?;
20639            self.write(")");
20640        }
20641        Ok(())
20642    }
20643
20644    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
20645        let uses_within_group_order = matches!(
20646            self.config.dialect,
20647            Some(crate::dialects::DialectType::TSQL | crate::dialects::DialectType::Fabric)
20648        );
20649        self.write_keyword("STRING_AGG");
20650        self.write("(");
20651        if f.distinct {
20652            self.write_keyword("DISTINCT");
20653            self.write_space();
20654        }
20655        self.generate_expression(&f.this)?;
20656        if let Some(ref separator) = f.separator {
20657            self.write(", ");
20658            self.generate_expression(separator)?;
20659        }
20660        // TSQL/Fabric put aggregate ORDER BY in WITHIN GROUP after the closing paren.
20661        if !uses_within_group_order {
20662            if let Some(ref order_by) = f.order_by {
20663                self.write_space();
20664                self.write_keyword("ORDER BY");
20665                self.write_space();
20666                for (i, ord) in order_by.iter().enumerate() {
20667                    if i > 0 {
20668                        self.write(", ");
20669                    }
20670                    self.generate_ordered(ord)?;
20671                }
20672            }
20673        }
20674        if let Some(ref limit) = f.limit {
20675            self.write_space();
20676            self.write_keyword("LIMIT");
20677            self.write_space();
20678            self.generate_expression(limit)?;
20679        }
20680        self.write(")");
20681        if uses_within_group_order {
20682            if let Some(ref order_by) = f.order_by {
20683                self.write_space();
20684                self.write_keyword("WITHIN GROUP");
20685                self.write(" (");
20686                self.write_keyword("ORDER BY");
20687                self.write_space();
20688                for (i, ord) in order_by.iter().enumerate() {
20689                    if i > 0 {
20690                        self.write(", ");
20691                    }
20692                    self.generate_ordered(ord)?;
20693                }
20694                self.write(")");
20695            }
20696        }
20697        if let Some(ref filter) = f.filter {
20698            self.write_space();
20699            self.write_keyword("FILTER");
20700            self.write("(");
20701            self.write_keyword("WHERE");
20702            self.write_space();
20703            self.generate_expression(filter)?;
20704            self.write(")");
20705        }
20706        Ok(())
20707    }
20708
20709    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
20710        use crate::dialects::DialectType;
20711        let order_inside_args = matches!(self.config.dialect, Some(DialectType::DuckDB));
20712        self.write_keyword("LISTAGG");
20713        self.write("(");
20714        if f.distinct {
20715            self.write_keyword("DISTINCT");
20716            self.write_space();
20717        }
20718        self.generate_expression(&f.this)?;
20719        if let Some(ref sep) = f.separator {
20720            self.write(", ");
20721            self.generate_expression(sep)?;
20722        } else if matches!(
20723            self.config.dialect,
20724            Some(DialectType::Trino) | Some(DialectType::Presto)
20725        ) {
20726            // Trino/Presto require explicit separator; default to ','
20727            self.write(", ','");
20728        }
20729        if let Some(ref overflow) = f.on_overflow {
20730            self.write_space();
20731            self.write_keyword("ON OVERFLOW");
20732            self.write_space();
20733            match overflow {
20734                ListAggOverflow::Error => self.write_keyword("ERROR"),
20735                ListAggOverflow::Truncate { filler, with_count } => {
20736                    self.write_keyword("TRUNCATE");
20737                    if let Some(ref fill) = filler {
20738                        self.write_space();
20739                        self.generate_expression(fill)?;
20740                    }
20741                    if *with_count {
20742                        self.write_space();
20743                        self.write_keyword("WITH COUNT");
20744                    } else {
20745                        self.write_space();
20746                        self.write_keyword("WITHOUT COUNT");
20747                    }
20748                }
20749            }
20750        }
20751        if order_inside_args {
20752            if let Some(ref order_by) = f.order_by {
20753                self.write_space();
20754                self.write_keyword("ORDER BY");
20755                self.write_space();
20756                for (i, ord) in order_by.iter().enumerate() {
20757                    if i > 0 {
20758                        self.write(", ");
20759                    }
20760                    self.generate_ordered(ord)?;
20761                }
20762            }
20763        }
20764        self.write(")");
20765        if !order_inside_args {
20766            if let Some(ref order_by) = f.order_by {
20767                self.write_space();
20768                self.write_keyword("WITHIN GROUP");
20769                self.write(" (");
20770                self.write_keyword("ORDER BY");
20771                self.write_space();
20772                for (i, ord) in order_by.iter().enumerate() {
20773                    if i > 0 {
20774                        self.write(", ");
20775                    }
20776                    self.generate_ordered(ord)?;
20777                }
20778                self.write(")");
20779            }
20780        }
20781        if let Some(ref filter) = f.filter {
20782            self.write_space();
20783            self.write_keyword("FILTER");
20784            self.write("(");
20785            self.write_keyword("WHERE");
20786            self.write_space();
20787            self.generate_expression(filter)?;
20788            self.write(")");
20789        }
20790        Ok(())
20791    }
20792
20793    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
20794        self.write_keyword("SUM_IF");
20795        self.write("(");
20796        self.generate_expression(&f.this)?;
20797        self.write(", ");
20798        self.generate_expression(&f.condition)?;
20799        self.write(")");
20800        if let Some(ref filter) = f.filter {
20801            self.write_space();
20802            self.write_keyword("FILTER");
20803            self.write("(");
20804            self.write_keyword("WHERE");
20805            self.write_space();
20806            self.generate_expression(filter)?;
20807            self.write(")");
20808        }
20809        Ok(())
20810    }
20811
20812    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
20813        self.write_keyword("APPROX_PERCENTILE");
20814        self.write("(");
20815        self.generate_expression(&f.this)?;
20816        self.write(", ");
20817        self.generate_expression(&f.percentile)?;
20818        if let Some(ref acc) = f.accuracy {
20819            self.write(", ");
20820            self.generate_expression(acc)?;
20821        }
20822        self.write(")");
20823        if let Some(ref filter) = f.filter {
20824            self.write_space();
20825            self.write_keyword("FILTER");
20826            self.write("(");
20827            self.write_keyword("WHERE");
20828            self.write_space();
20829            self.generate_expression(filter)?;
20830            self.write(")");
20831        }
20832        Ok(())
20833    }
20834
20835    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
20836        self.write_keyword(name);
20837        self.write("(");
20838        self.generate_expression(&f.percentile)?;
20839        self.write(")");
20840        if let Some(ref order_by) = f.order_by {
20841            self.write_space();
20842            self.write_keyword("WITHIN GROUP");
20843            self.write(" (");
20844            self.write_keyword("ORDER BY");
20845            self.write_space();
20846            self.generate_expression(&f.this)?;
20847            for ord in order_by.iter() {
20848                if ord.desc {
20849                    self.write_space();
20850                    self.write_keyword("DESC");
20851                }
20852            }
20853            self.write(")");
20854        }
20855        if let Some(ref filter) = f.filter {
20856            self.write_space();
20857            self.write_keyword("FILTER");
20858            self.write("(");
20859            self.write_keyword("WHERE");
20860            self.write_space();
20861            self.generate_expression(filter)?;
20862            self.write(")");
20863        }
20864        Ok(())
20865    }
20866
20867    // Window function generators
20868
20869    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
20870        self.write_keyword("NTILE");
20871        self.write("(");
20872        if let Some(num_buckets) = &f.num_buckets {
20873            self.generate_expression(num_buckets)?;
20874        }
20875        if let Some(order_by) = &f.order_by {
20876            self.write_keyword(" ORDER BY ");
20877            for (i, ob) in order_by.iter().enumerate() {
20878                if i > 0 {
20879                    self.write(", ");
20880                }
20881                self.generate_ordered(ob)?;
20882            }
20883        }
20884        self.write(")");
20885        Ok(())
20886    }
20887
20888    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
20889        self.write_keyword(name);
20890        self.write("(");
20891        self.generate_expression(&f.this)?;
20892        if let Some(ref offset) = f.offset {
20893            self.write(", ");
20894            self.generate_expression(offset)?;
20895            if let Some(ref default) = f.default {
20896                self.write(", ");
20897                self.generate_expression(default)?;
20898            }
20899        }
20900        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
20901        if self.config.ignore_nulls_in_func {
20902            match f.ignore_nulls {
20903                Some(true) => {
20904                    self.write_space();
20905                    self.write_keyword("IGNORE NULLS");
20906                }
20907                Some(false) => {
20908                    self.write_space();
20909                    self.write_keyword("RESPECT NULLS");
20910                }
20911                None => {}
20912            }
20913        }
20914        self.write(")");
20915        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20916        if !self.config.ignore_nulls_in_func {
20917            match f.ignore_nulls {
20918                Some(true) => {
20919                    self.write_space();
20920                    self.write_keyword("IGNORE NULLS");
20921                }
20922                Some(false) => {
20923                    self.write_space();
20924                    self.write_keyword("RESPECT NULLS");
20925                }
20926                None => {}
20927            }
20928        }
20929        Ok(())
20930    }
20931
20932    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
20933        self.write_keyword(name);
20934        self.write("(");
20935        self.generate_expression(&f.this)?;
20936        // ORDER BY inside parens (e.g., DuckDB: LAST_VALUE(x ORDER BY x))
20937        if !f.order_by.is_empty() {
20938            self.write_space();
20939            self.write_keyword("ORDER BY");
20940            self.write_space();
20941            for (i, ordered) in f.order_by.iter().enumerate() {
20942                if i > 0 {
20943                    self.write(", ");
20944                }
20945                self.generate_ordered(ordered)?;
20946            }
20947        }
20948        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
20949        if self.config.ignore_nulls_in_func {
20950            match f.ignore_nulls {
20951                Some(true) => {
20952                    self.write_space();
20953                    self.write_keyword("IGNORE NULLS");
20954                }
20955                Some(false) => {
20956                    self.write_space();
20957                    self.write_keyword("RESPECT NULLS");
20958                }
20959                None => {}
20960            }
20961        }
20962        self.write(")");
20963        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20964        if !self.config.ignore_nulls_in_func {
20965            match f.ignore_nulls {
20966                Some(true) => {
20967                    self.write_space();
20968                    self.write_keyword("IGNORE NULLS");
20969                }
20970                Some(false) => {
20971                    self.write_space();
20972                    self.write_keyword("RESPECT NULLS");
20973                }
20974                None => {}
20975            }
20976        }
20977        Ok(())
20978    }
20979
20980    /// Generate FIRST_VALUE/LAST_VALUE with Hive/Spark2-style boolean argument for IGNORE NULLS.
20981    /// In Hive/Spark2, `FIRST_VALUE(col) IGNORE NULLS` is written as `FIRST_VALUE(col, TRUE)`.
20982    fn generate_value_func_with_ignore_nulls_bool(
20983        &mut self,
20984        name: &str,
20985        f: &ValueFunc,
20986    ) -> Result<()> {
20987        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
20988            self.write_keyword(name);
20989            self.write("(");
20990            self.generate_expression(&f.this)?;
20991            self.write(", ");
20992            self.write_keyword("TRUE");
20993            self.write(")");
20994            return Ok(());
20995        }
20996        self.generate_value_func(name, f)
20997    }
20998
20999    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
21000        self.write_keyword("NTH_VALUE");
21001        self.write("(");
21002        self.generate_expression(&f.this)?;
21003        self.write(", ");
21004        self.generate_expression(&f.offset)?;
21005        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
21006        if self.config.ignore_nulls_in_func {
21007            match f.ignore_nulls {
21008                Some(true) => {
21009                    self.write_space();
21010                    self.write_keyword("IGNORE NULLS");
21011                }
21012                Some(false) => {
21013                    self.write_space();
21014                    self.write_keyword("RESPECT NULLS");
21015                }
21016                None => {}
21017            }
21018        }
21019        self.write(")");
21020        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
21021        if matches!(
21022            self.config.dialect,
21023            Some(crate::dialects::DialectType::Snowflake)
21024        ) {
21025            match f.from_first {
21026                Some(true) => {
21027                    self.write_space();
21028                    self.write_keyword("FROM FIRST");
21029                }
21030                Some(false) => {
21031                    self.write_space();
21032                    self.write_keyword("FROM LAST");
21033                }
21034                None => {}
21035            }
21036        }
21037        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
21038        if !self.config.ignore_nulls_in_func {
21039            match f.ignore_nulls {
21040                Some(true) => {
21041                    self.write_space();
21042                    self.write_keyword("IGNORE NULLS");
21043                }
21044                Some(false) => {
21045                    self.write_space();
21046                    self.write_keyword("RESPECT NULLS");
21047                }
21048                None => {}
21049            }
21050        }
21051        Ok(())
21052    }
21053
21054    // Additional string function generators
21055
21056    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
21057        // Standard syntax: POSITION(substr IN str)
21058        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
21059        if matches!(
21060            self.config.dialect,
21061            Some(crate::dialects::DialectType::ClickHouse)
21062        ) {
21063            self.write_keyword("POSITION");
21064            self.write("(");
21065            self.generate_expression(&f.string)?;
21066            self.write(", ");
21067            self.generate_expression(&f.substring)?;
21068            if let Some(ref start) = f.start {
21069                self.write(", ");
21070                self.generate_expression(start)?;
21071            }
21072            self.write(")");
21073            return Ok(());
21074        }
21075
21076        self.write_keyword("POSITION");
21077        self.write("(");
21078        self.generate_expression(&f.substring)?;
21079        self.write_space();
21080        self.write_keyword("IN");
21081        self.write_space();
21082        self.generate_expression(&f.string)?;
21083        if let Some(ref start) = f.start {
21084            self.write(", ");
21085            self.generate_expression(start)?;
21086        }
21087        self.write(")");
21088        Ok(())
21089    }
21090
21091    // Additional math function generators
21092
21093    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
21094        // Teradata RANDOM(lower, upper)
21095        if f.lower.is_some() || f.upper.is_some() {
21096            self.write_keyword("RANDOM");
21097            self.write("(");
21098            if let Some(ref lower) = f.lower {
21099                self.generate_expression(lower)?;
21100            }
21101            if let Some(ref upper) = f.upper {
21102                self.write(", ");
21103                self.generate_expression(upper)?;
21104            }
21105            self.write(")");
21106            return Ok(());
21107        }
21108        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
21109        let func_name = match self.config.dialect {
21110            Some(crate::dialects::DialectType::Snowflake)
21111            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
21112            _ => "RAND",
21113        };
21114        self.write_keyword(func_name);
21115        self.write("(");
21116        // DuckDB doesn't support seeded RANDOM, so skip the seed
21117        if !matches!(
21118            self.config.dialect,
21119            Some(crate::dialects::DialectType::DuckDB)
21120        ) {
21121            if let Some(ref seed) = f.seed {
21122                self.generate_expression(seed)?;
21123            }
21124        }
21125        self.write(")");
21126        Ok(())
21127    }
21128
21129    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
21130        self.write_keyword("TRUNCATE");
21131        self.write("(");
21132        self.generate_expression(&f.this)?;
21133        if let Some(ref decimals) = f.decimals {
21134            self.write(", ");
21135            self.generate_expression(decimals)?;
21136        }
21137        self.write(")");
21138        Ok(())
21139    }
21140
21141    // Control flow generators
21142
21143    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
21144        self.write_keyword("DECODE");
21145        self.write("(");
21146        self.generate_expression(&f.this)?;
21147        for (search, result) in &f.search_results {
21148            self.write(", ");
21149            self.generate_expression(search)?;
21150            self.write(", ");
21151            self.generate_expression(result)?;
21152        }
21153        if let Some(ref default) = f.default {
21154            self.write(", ");
21155            self.generate_expression(default)?;
21156        }
21157        self.write(")");
21158        Ok(())
21159    }
21160
21161    // Date/time function generators
21162
21163    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
21164        self.write_keyword(name);
21165        self.write("(");
21166        self.generate_expression(&f.this)?;
21167        self.write(", ");
21168        self.generate_expression(&f.format)?;
21169        self.write(")");
21170        Ok(())
21171    }
21172
21173    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
21174        self.write_keyword("FROM_UNIXTIME");
21175        self.write("(");
21176        self.generate_expression(&f.this)?;
21177        if let Some(ref format) = f.format {
21178            self.write(", ");
21179            self.generate_expression(format)?;
21180        }
21181        self.write(")");
21182        Ok(())
21183    }
21184
21185    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
21186        self.write_keyword("UNIX_TIMESTAMP");
21187        self.write("(");
21188        if let Some(ref expr) = f.this {
21189            self.generate_expression(expr)?;
21190            if let Some(ref format) = f.format {
21191                self.write(", ");
21192                self.generate_expression(format)?;
21193            }
21194        } else if matches!(
21195            self.config.dialect,
21196            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
21197        ) {
21198            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
21199            self.write_keyword("CURRENT_TIMESTAMP");
21200            self.write("()");
21201        }
21202        self.write(")");
21203        Ok(())
21204    }
21205
21206    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
21207        self.write_keyword("MAKE_DATE");
21208        self.write("(");
21209        self.generate_expression(&f.year)?;
21210        self.write(", ");
21211        self.generate_expression(&f.month)?;
21212        self.write(", ");
21213        self.generate_expression(&f.day)?;
21214        self.write(")");
21215        Ok(())
21216    }
21217
21218    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
21219        self.write_keyword("MAKE_TIMESTAMP");
21220        self.write("(");
21221        self.generate_expression(&f.year)?;
21222        self.write(", ");
21223        self.generate_expression(&f.month)?;
21224        self.write(", ");
21225        self.generate_expression(&f.day)?;
21226        self.write(", ");
21227        self.generate_expression(&f.hour)?;
21228        self.write(", ");
21229        self.generate_expression(&f.minute)?;
21230        self.write(", ");
21231        self.generate_expression(&f.second)?;
21232        if let Some(ref tz) = f.timezone {
21233            self.write(", ");
21234            self.generate_expression(tz)?;
21235        }
21236        self.write(")");
21237        Ok(())
21238    }
21239
21240    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
21241    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
21242        match expr {
21243            Expression::Struct(s) => {
21244                if s.fields.iter().all(|(name, _)| name.is_some()) {
21245                    Some(
21246                        s.fields
21247                            .iter()
21248                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
21249                            .collect(),
21250                    )
21251                } else {
21252                    None
21253                }
21254            }
21255            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
21256                // Check if all args are Alias (named fields)
21257                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
21258                    Some(
21259                        f.args
21260                            .iter()
21261                            .filter_map(|a| {
21262                                if let Expression::Alias(alias) = a {
21263                                    Some(alias.alias.name.clone())
21264                                } else {
21265                                    None
21266                                }
21267                            })
21268                            .collect(),
21269                    )
21270                } else {
21271                    None
21272                }
21273            }
21274            _ => None,
21275        }
21276    }
21277
21278    /// Check if a struct expression has any unnamed fields
21279    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
21280        match expr {
21281            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
21282            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
21283                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
21284            }
21285            _ => false,
21286        }
21287    }
21288
21289    /// Get the field count of a struct expression
21290    fn struct_field_count(expr: &Expression) -> usize {
21291        match expr {
21292            Expression::Struct(s) => s.fields.len(),
21293            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => f.args.len(),
21294            _ => 0,
21295        }
21296    }
21297
21298    /// Apply field names to an unnamed struct expression, producing a new expression with names
21299    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
21300        match expr {
21301            Expression::Struct(s) => {
21302                let mut new_fields = Vec::with_capacity(s.fields.len());
21303                for (i, (name, value)) in s.fields.iter().enumerate() {
21304                    if name.is_none() && i < field_names.len() {
21305                        new_fields.push((Some(field_names[i].clone()), value.clone()));
21306                    } else {
21307                        new_fields.push((name.clone(), value.clone()));
21308                    }
21309                }
21310                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
21311            }
21312            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
21313                let mut new_args = Vec::with_capacity(f.args.len());
21314                for (i, arg) in f.args.iter().enumerate() {
21315                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
21316                        // Wrap the value in an Alias with the inherited name
21317                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
21318                            this: arg.clone(),
21319                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
21320                            column_aliases: Vec::new(),
21321                            alias_explicit_as: false,
21322                            alias_keyword: None,
21323                            pre_alias_comments: Vec::new(),
21324                            trailing_comments: Vec::new(),
21325                            inferred_type: None,
21326                        })));
21327                    } else {
21328                        new_args.push(arg.clone());
21329                    }
21330                }
21331                Expression::Function(Box::new(crate::expressions::Function {
21332                    name: f.name.clone(),
21333                    args: new_args,
21334                    distinct: f.distinct,
21335                    trailing_comments: f.trailing_comments.clone(),
21336                    use_bracket_syntax: f.use_bracket_syntax,
21337                    no_parens: f.no_parens,
21338                    quoted: f.quoted,
21339                    span: None,
21340                    inferred_type: None,
21341                }))
21342            }
21343            _ => expr.clone(),
21344        }
21345    }
21346
21347    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
21348    /// This implements BigQuery's implicit field name inheritance for struct arrays.
21349    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
21350    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
21351        let first = match expressions.first() {
21352            Some(e) => e,
21353            None => return expressions.to_vec(),
21354        };
21355
21356        let field_names = match Self::extract_struct_field_names(first) {
21357            Some(names) if !names.is_empty() => names,
21358            _ => return expressions.to_vec(),
21359        };
21360
21361        let mut result = Vec::with_capacity(expressions.len());
21362        for (idx, expr) in expressions.iter().enumerate() {
21363            if idx == 0 {
21364                result.push(expr.clone());
21365                continue;
21366            }
21367            // Check if this is a struct with unnamed fields that needs name propagation
21368            if Self::struct_field_count(expr) == field_names.len()
21369                && Self::struct_has_unnamed_fields(expr)
21370            {
21371                result.push(Self::apply_struct_field_names(expr, &field_names));
21372            } else {
21373                result.push(expr.clone());
21374            }
21375        }
21376        result
21377    }
21378
21379    // Array function generators
21380
21381    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
21382        // Apply struct name inheritance for target dialects that need it
21383        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
21384        let needs_inheritance = matches!(
21385            self.config.dialect,
21386            Some(DialectType::DuckDB)
21387                | Some(DialectType::Spark)
21388                | Some(DialectType::Databricks)
21389                | Some(DialectType::Hive)
21390                | Some(DialectType::Snowflake)
21391                | Some(DialectType::Presto)
21392                | Some(DialectType::Trino)
21393        );
21394        let propagated: Vec<Expression>;
21395        let expressions = if needs_inheritance && f.expressions.len() > 1 {
21396            propagated = Self::inherit_struct_field_names(&f.expressions);
21397            &propagated
21398        } else {
21399            &f.expressions
21400        };
21401
21402        // Check if elements should be split onto multiple lines (pretty + too wide)
21403        let should_split = if self.config.pretty && !expressions.is_empty() {
21404            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
21405            for expr in expressions {
21406                let mut temp_gen = Generator::with_arc_config(self.config.clone());
21407                Arc::make_mut(&mut temp_gen.config).pretty = false;
21408                temp_gen.generate_expression(expr)?;
21409                expr_strings.push(temp_gen.output);
21410            }
21411            self.too_wide(&expr_strings)
21412        } else {
21413            false
21414        };
21415
21416        if f.bracket_notation {
21417            // For Spark/Databricks, use ARRAY(...) with parens
21418            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
21419            // For others (DuckDB, Snowflake), use bare [...]
21420            let (open, close) = match self.config.dialect {
21421                None
21422                | Some(DialectType::Generic)
21423                | Some(DialectType::Spark)
21424                | Some(DialectType::Databricks)
21425                | Some(DialectType::Hive) => {
21426                    self.write_keyword("ARRAY");
21427                    ("(", ")")
21428                }
21429                Some(DialectType::Presto)
21430                | Some(DialectType::Trino)
21431                | Some(DialectType::PostgreSQL)
21432                | Some(DialectType::Redshift)
21433                | Some(DialectType::Materialize)
21434                | Some(DialectType::RisingWave)
21435                | Some(DialectType::CockroachDB) => {
21436                    self.write_keyword("ARRAY");
21437                    ("[", "]")
21438                }
21439                _ => ("[", "]"),
21440            };
21441            self.write(open);
21442            if should_split {
21443                self.write_newline();
21444                self.indent_level += 1;
21445                for (i, expr) in expressions.iter().enumerate() {
21446                    self.write_indent();
21447                    self.generate_expression(expr)?;
21448                    if i + 1 < expressions.len() {
21449                        self.write(",");
21450                    }
21451                    self.write_newline();
21452                }
21453                self.indent_level -= 1;
21454                self.write_indent();
21455            } else {
21456                for (i, expr) in expressions.iter().enumerate() {
21457                    if i > 0 {
21458                        self.write(", ");
21459                    }
21460                    self.generate_expression(expr)?;
21461                }
21462            }
21463            self.write(close);
21464        } else {
21465            // Use LIST keyword if that was the original syntax (DuckDB)
21466            if f.use_list_keyword {
21467                self.write_keyword("LIST");
21468            } else {
21469                self.write_keyword("ARRAY");
21470            }
21471            // For Spark/Hive, always use ARRAY(...) with parens
21472            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
21473            let has_subquery = expressions
21474                .iter()
21475                .any(|e| matches!(e, Expression::Select(_)));
21476            let (open, close) = if matches!(
21477                self.config.dialect,
21478                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
21479            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
21480                && has_subquery)
21481            {
21482                ("(", ")")
21483            } else {
21484                ("[", "]")
21485            };
21486            self.write(open);
21487            if should_split {
21488                self.write_newline();
21489                self.indent_level += 1;
21490                for (i, expr) in expressions.iter().enumerate() {
21491                    self.write_indent();
21492                    self.generate_expression(expr)?;
21493                    if i + 1 < expressions.len() {
21494                        self.write(",");
21495                    }
21496                    self.write_newline();
21497                }
21498                self.indent_level -= 1;
21499                self.write_indent();
21500            } else {
21501                for (i, expr) in expressions.iter().enumerate() {
21502                    if i > 0 {
21503                        self.write(", ");
21504                    }
21505                    self.generate_expression(expr)?;
21506                }
21507            }
21508            self.write(close);
21509        }
21510        Ok(())
21511    }
21512
21513    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
21514        self.write_keyword("ARRAY_SORT");
21515        self.write("(");
21516        self.generate_expression(&f.this)?;
21517        if let Some(ref comp) = f.comparator {
21518            self.write(", ");
21519            self.generate_expression(comp)?;
21520        }
21521        self.write(")");
21522        Ok(())
21523    }
21524
21525    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
21526        self.write_keyword(name);
21527        self.write("(");
21528        self.generate_expression(&f.this)?;
21529        self.write(", ");
21530        self.generate_expression(&f.separator)?;
21531        if let Some(ref null_rep) = f.null_replacement {
21532            self.write(", ");
21533            self.generate_expression(null_rep)?;
21534        }
21535        self.write(")");
21536        Ok(())
21537    }
21538
21539    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
21540        self.write_keyword("UNNEST");
21541        self.write("(");
21542        self.generate_expression(&f.this)?;
21543        for extra in &f.expressions {
21544            self.write(", ");
21545            self.generate_expression(extra)?;
21546        }
21547        self.write(")");
21548        if f.with_ordinality {
21549            self.write_space();
21550            if self.config.unnest_with_ordinality {
21551                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
21552                self.write_keyword("WITH ORDINALITY");
21553            } else if f.offset_alias.is_some() {
21554                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
21555                // Alias (if any) comes BEFORE WITH OFFSET
21556                if let Some(ref alias) = f.alias {
21557                    self.write_keyword("AS");
21558                    self.write_space();
21559                    self.generate_identifier(alias)?;
21560                    self.write_space();
21561                }
21562                self.write_keyword("WITH OFFSET");
21563                if let Some(ref offset_alias) = f.offset_alias {
21564                    self.write_space();
21565                    self.write_keyword("AS");
21566                    self.write_space();
21567                    self.generate_identifier(offset_alias)?;
21568                }
21569            } else {
21570                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
21571                self.write_keyword("WITH OFFSET");
21572                if f.alias.is_none() {
21573                    self.write(" AS offset");
21574                }
21575            }
21576        }
21577        if let Some(ref alias) = f.alias {
21578            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
21579            let should_add_alias = if !f.with_ordinality {
21580                true
21581            } else if self.config.unnest_with_ordinality {
21582                // Presto/Trino: alias comes after WITH ORDINALITY
21583                true
21584            } else if f.offset_alias.is_some() {
21585                // BigQuery expansion: alias already handled above
21586                false
21587            } else {
21588                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
21589                true
21590            };
21591            if should_add_alias {
21592                self.write_space();
21593                self.write_keyword("AS");
21594                self.write_space();
21595                self.generate_identifier(alias)?;
21596            }
21597        }
21598        Ok(())
21599    }
21600
21601    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
21602        self.write_keyword("FILTER");
21603        self.write("(");
21604        self.generate_expression(&f.this)?;
21605        self.write(", ");
21606        self.generate_expression(&f.filter)?;
21607        self.write(")");
21608        Ok(())
21609    }
21610
21611    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
21612        self.write_keyword("TRANSFORM");
21613        self.write("(");
21614        self.generate_expression(&f.this)?;
21615        self.write(", ");
21616        self.generate_expression(&f.transform)?;
21617        self.write(")");
21618        Ok(())
21619    }
21620
21621    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
21622        self.write_keyword(name);
21623        self.write("(");
21624        self.generate_expression(&f.start)?;
21625        self.write(", ");
21626        self.generate_expression(&f.stop)?;
21627        if let Some(ref step) = f.step {
21628            self.write(", ");
21629            self.generate_expression(step)?;
21630        }
21631        self.write(")");
21632        Ok(())
21633    }
21634
21635    // Struct function generators
21636
21637    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
21638        self.write_keyword("STRUCT");
21639        self.write("(");
21640        for (i, (name, expr)) in f.fields.iter().enumerate() {
21641            if i > 0 {
21642                self.write(", ");
21643            }
21644            if let Some(ref id) = name {
21645                self.generate_identifier(id)?;
21646                self.write(" ");
21647                self.write_keyword("AS");
21648                self.write(" ");
21649            }
21650            self.generate_expression(expr)?;
21651        }
21652        self.write(")");
21653        Ok(())
21654    }
21655
21656    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
21657    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
21658        // Extract named/unnamed fields from function args
21659        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
21660        let mut names: Vec<Option<String>> = Vec::new();
21661        let mut values: Vec<&Expression> = Vec::new();
21662        let mut all_named = true;
21663
21664        for arg in &func.args {
21665            match arg {
21666                Expression::Alias(a) => {
21667                    names.push(Some(a.alias.name.clone()));
21668                    values.push(&a.this);
21669                }
21670                _ => {
21671                    names.push(None);
21672                    values.push(arg);
21673                    all_named = false;
21674                }
21675            }
21676        }
21677
21678        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
21679            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
21680            self.write("{");
21681            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
21682                if i > 0 {
21683                    self.write(", ");
21684                }
21685                if let Some(n) = name {
21686                    self.write("'");
21687                    self.write(n);
21688                    self.write("'");
21689                } else {
21690                    self.write("'_");
21691                    self.write(&i.to_string());
21692                    self.write("'");
21693                }
21694                self.write(": ");
21695                self.generate_expression(value)?;
21696            }
21697            self.write("}");
21698            return Ok(());
21699        }
21700
21701        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
21702            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
21703            self.write_keyword("OBJECT_CONSTRUCT");
21704            self.write("(");
21705            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
21706                if i > 0 {
21707                    self.write(", ");
21708                }
21709                if let Some(n) = name {
21710                    self.write("'");
21711                    self.write(n);
21712                    self.write("'");
21713                } else {
21714                    self.write("'_");
21715                    self.write(&i.to_string());
21716                    self.write("'");
21717                }
21718                self.write(", ");
21719                self.generate_expression(value)?;
21720            }
21721            self.write(")");
21722            return Ok(());
21723        }
21724
21725        if matches!(
21726            self.config.dialect,
21727            Some(DialectType::Presto) | Some(DialectType::Trino)
21728        ) {
21729            if all_named && !names.is_empty() {
21730                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
21731                // Need to infer types from values
21732                self.write_keyword("CAST");
21733                self.write("(");
21734                self.write_keyword("ROW");
21735                self.write("(");
21736                for (i, value) in values.iter().enumerate() {
21737                    if i > 0 {
21738                        self.write(", ");
21739                    }
21740                    self.generate_expression(value)?;
21741                }
21742                self.write(")");
21743                self.write(" ");
21744                self.write_keyword("AS");
21745                self.write(" ");
21746                self.write_keyword("ROW");
21747                self.write("(");
21748                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
21749                    if i > 0 {
21750                        self.write(", ");
21751                    }
21752                    if let Some(n) = name {
21753                        self.write(n);
21754                    }
21755                    self.write(" ");
21756                    let type_str = Self::infer_sql_type_for_presto(value);
21757                    self.write_keyword(&type_str);
21758                }
21759                self.write(")");
21760                self.write(")");
21761            } else {
21762                // Unnamed: ROW(values...)
21763                self.write_keyword("ROW");
21764                self.write("(");
21765                for (i, value) in values.iter().enumerate() {
21766                    if i > 0 {
21767                        self.write(", ");
21768                    }
21769                    self.generate_expression(value)?;
21770                }
21771                self.write(")");
21772            }
21773            return Ok(());
21774        }
21775
21776        // Default: ROW(values...) for other dialects
21777        self.write_keyword("ROW");
21778        self.write("(");
21779        for (i, value) in values.iter().enumerate() {
21780            if i > 0 {
21781                self.write(", ");
21782            }
21783            self.generate_expression(value)?;
21784        }
21785        self.write(")");
21786        Ok(())
21787    }
21788
21789    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
21790    fn infer_sql_type_for_presto(expr: &Expression) -> String {
21791        match expr {
21792            Expression::Literal(lit)
21793                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
21794            {
21795                "VARCHAR".to_string()
21796            }
21797            Expression::Literal(lit)
21798                if matches!(lit.as_ref(), crate::expressions::Literal::Number(_)) =>
21799            {
21800                let crate::expressions::Literal::Number(n) = lit.as_ref() else {
21801                    unreachable!()
21802                };
21803                if n.contains('.') {
21804                    "DOUBLE".to_string()
21805                } else {
21806                    "INTEGER".to_string()
21807                }
21808            }
21809            Expression::Boolean(_) => "BOOLEAN".to_string(),
21810            Expression::Literal(lit)
21811                if matches!(lit.as_ref(), crate::expressions::Literal::Date(_)) =>
21812            {
21813                "DATE".to_string()
21814            }
21815            Expression::Literal(lit)
21816                if matches!(lit.as_ref(), crate::expressions::Literal::Timestamp(_)) =>
21817            {
21818                "TIMESTAMP".to_string()
21819            }
21820            Expression::Literal(lit)
21821                if matches!(lit.as_ref(), crate::expressions::Literal::Datetime(_)) =>
21822            {
21823                "TIMESTAMP".to_string()
21824            }
21825            Expression::Array(_) | Expression::ArrayFunc(_) => {
21826                // Try to infer element type from first element
21827                "ARRAY(VARCHAR)".to_string()
21828            }
21829            // For nested structs - generate a nested ROW type by inspecting fields
21830            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
21831            Expression::Function(f) => {
21832                if f.name.eq_ignore_ascii_case("STRUCT") {
21833                    "ROW".to_string()
21834                } else if f.name.eq_ignore_ascii_case("CURRENT_DATE") {
21835                    "DATE".to_string()
21836                } else if f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
21837                    || f.name.eq_ignore_ascii_case("NOW")
21838                {
21839                    "TIMESTAMP".to_string()
21840                } else {
21841                    "VARCHAR".to_string()
21842                }
21843            }
21844            _ => "VARCHAR".to_string(),
21845        }
21846    }
21847
21848    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
21849        // DuckDB uses STRUCT_EXTRACT function syntax
21850        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
21851            self.write_keyword("STRUCT_EXTRACT");
21852            self.write("(");
21853            self.generate_expression(&f.this)?;
21854            self.write(", ");
21855            // Output field name as string literal
21856            self.write("'");
21857            self.write(&f.field.name);
21858            self.write("'");
21859            self.write(")");
21860            return Ok(());
21861        }
21862        self.generate_expression(&f.this)?;
21863        self.write(".");
21864        self.generate_identifier(&f.field)
21865    }
21866
21867    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
21868        if matches!(
21869            self.config.dialect,
21870            Some(DialectType::Spark | DialectType::Databricks)
21871        ) {
21872            self.write_keyword("STRUCT");
21873            self.write("(");
21874            for (i, (name, value)) in f.pairs.iter().enumerate() {
21875                if i > 0 {
21876                    self.write(", ");
21877                }
21878                self.generate_expression(value)?;
21879                self.write(" ");
21880                self.write_keyword("AS");
21881                self.write(" ");
21882                if let Expression::Literal(lit) = name {
21883                    if let Literal::String(field_name) = lit.as_ref() {
21884                        self.generate_identifier(&Identifier::new(field_name))?;
21885                    } else {
21886                        self.generate_expression(name)?;
21887                    }
21888                } else {
21889                    self.generate_expression(name)?;
21890                }
21891            }
21892            self.write(")");
21893            return Ok(());
21894        }
21895
21896        self.write_keyword("NAMED_STRUCT");
21897        self.write("(");
21898        for (i, (name, value)) in f.pairs.iter().enumerate() {
21899            if i > 0 {
21900                self.write(", ");
21901            }
21902            self.generate_expression(name)?;
21903            self.write(", ");
21904            self.generate_expression(value)?;
21905        }
21906        self.write(")");
21907        Ok(())
21908    }
21909
21910    // Map function generators
21911
21912    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
21913        if f.curly_brace_syntax {
21914            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
21915            if f.with_map_keyword {
21916                self.write_keyword("MAP");
21917                self.write(" ");
21918            }
21919            self.write("{");
21920            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
21921                if i > 0 {
21922                    self.write(", ");
21923                }
21924                self.generate_expression(key)?;
21925                self.write(": ");
21926                self.generate_expression(val)?;
21927            }
21928            self.write("}");
21929        } else {
21930            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
21931            self.write_keyword("MAP");
21932            self.write("(");
21933            self.write_keyword("ARRAY");
21934            self.write("[");
21935            for (i, key) in f.keys.iter().enumerate() {
21936                if i > 0 {
21937                    self.write(", ");
21938                }
21939                self.generate_expression(key)?;
21940            }
21941            self.write("], ");
21942            self.write_keyword("ARRAY");
21943            self.write("[");
21944            for (i, val) in f.values.iter().enumerate() {
21945                if i > 0 {
21946                    self.write(", ");
21947                }
21948                self.generate_expression(val)?;
21949            }
21950            self.write("])");
21951        }
21952        Ok(())
21953    }
21954
21955    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
21956        self.write_keyword(name);
21957        self.write("(");
21958        self.generate_expression(&f.this)?;
21959        self.write(", ");
21960        self.generate_expression(&f.transform)?;
21961        self.write(")");
21962        Ok(())
21963    }
21964
21965    // JSON function generators
21966
21967    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
21968        use crate::dialects::DialectType;
21969
21970        // Check if we should use arrow syntax (-> or ->>)
21971        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
21972
21973        if use_arrow {
21974            // Output arrow syntax: expr -> path or expr ->> path
21975            self.generate_expression(&f.this)?;
21976            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
21977                self.write(" ->> ");
21978            } else {
21979                self.write(" -> ");
21980            }
21981            self.generate_expression(&f.path)?;
21982            return Ok(());
21983        }
21984
21985        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
21986        if f.hash_arrow_syntax
21987            && matches!(
21988                self.config.dialect,
21989                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21990            )
21991        {
21992            self.generate_expression(&f.this)?;
21993            self.write(" #>> ");
21994            self.generate_expression(&f.path)?;
21995            return Ok(());
21996        }
21997
21998        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
21999        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
22000        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
22001            match name {
22002                "JSON_EXTRACT_SCALAR"
22003                | "JSON_EXTRACT_PATH_TEXT"
22004                | "JSON_EXTRACT"
22005                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
22006                _ => name,
22007            }
22008        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
22009            match name {
22010                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
22011                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
22012                _ => name,
22013            }
22014        } else {
22015            name
22016        };
22017
22018        self.write_keyword(func_name);
22019        self.write("(");
22020        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
22021        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
22022            if let Expression::Cast(ref cast) = f.this {
22023                if matches!(cast.to, crate::expressions::DataType::Json) {
22024                    self.generate_expression(&cast.this)?;
22025                } else {
22026                    self.generate_expression(&f.this)?;
22027                }
22028            } else {
22029                self.generate_expression(&f.this)?;
22030            }
22031        } else {
22032            self.generate_expression(&f.this)?;
22033        }
22034        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
22035        // decompose JSON path into separate string arguments
22036        if matches!(
22037            self.config.dialect,
22038            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22039        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
22040        {
22041            if let Expression::Literal(ref lit) = f.path {
22042                if let Literal::String(ref s) = lit.as_ref() {
22043                    let parts = Self::decompose_json_path(s);
22044                    for part in &parts {
22045                        self.write(", '");
22046                        self.write(part);
22047                        self.write("'");
22048                    }
22049                }
22050            } else {
22051                self.write(", ");
22052                self.generate_expression(&f.path)?;
22053            }
22054        } else {
22055            self.write(", ");
22056            self.generate_expression(&f.path)?;
22057        }
22058
22059        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
22060        // These go BEFORE the closing parenthesis
22061        if let Some(ref wrapper) = f.wrapper_option {
22062            self.write_space();
22063            self.write_keyword(wrapper);
22064        }
22065        if let Some(ref quotes) = f.quotes_option {
22066            self.write_space();
22067            self.write_keyword(quotes);
22068            if f.on_scalar_string {
22069                self.write_space();
22070                self.write_keyword("ON SCALAR STRING");
22071            }
22072        }
22073        if let Some(ref on_err) = f.on_error {
22074            self.write_space();
22075            self.write_keyword(on_err);
22076        }
22077        if let Some(ref ret_type) = f.returning {
22078            self.write_space();
22079            self.write_keyword("RETURNING");
22080            self.write_space();
22081            self.generate_data_type(ret_type)?;
22082        }
22083
22084        self.write(")");
22085        Ok(())
22086    }
22087
22088    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
22089    fn dialect_supports_json_arrow(&self) -> bool {
22090        use crate::dialects::DialectType;
22091        match self.config.dialect {
22092            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
22093            Some(DialectType::PostgreSQL) => true,
22094            Some(DialectType::MySQL) => true,
22095            Some(DialectType::DuckDB) => true,
22096            Some(DialectType::CockroachDB) => true,
22097            Some(DialectType::StarRocks) => true,
22098            Some(DialectType::SQLite) => true,
22099            // Other dialects use function syntax
22100            _ => false,
22101        }
22102    }
22103
22104    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
22105        use crate::dialects::DialectType;
22106
22107        // PostgreSQL uses #> operator for JSONB path extraction
22108        if matches!(
22109            self.config.dialect,
22110            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22111        ) && name == "JSON_EXTRACT_PATH"
22112        {
22113            self.generate_expression(&f.this)?;
22114            self.write(" #> ");
22115            if f.paths.len() == 1 {
22116                self.generate_expression(&f.paths[0])?;
22117            } else {
22118                // Multiple paths: ARRAY[path1, path2, ...]
22119                self.write_keyword("ARRAY");
22120                self.write("[");
22121                for (i, path) in f.paths.iter().enumerate() {
22122                    if i > 0 {
22123                        self.write(", ");
22124                    }
22125                    self.generate_expression(path)?;
22126                }
22127                self.write("]");
22128            }
22129            return Ok(());
22130        }
22131
22132        self.write_keyword(name);
22133        self.write("(");
22134        self.generate_expression(&f.this)?;
22135        for path in &f.paths {
22136            self.write(", ");
22137            self.generate_expression(path)?;
22138        }
22139        self.write(")");
22140        Ok(())
22141    }
22142
22143    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
22144        use crate::dialects::DialectType;
22145
22146        self.write_keyword("JSON_OBJECT");
22147        self.write("(");
22148        if f.star {
22149            self.write("*");
22150        } else {
22151            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
22152            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
22153            // Also respect the json_key_value_pair_sep config
22154            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
22155                || matches!(
22156                    self.config.dialect,
22157                    Some(DialectType::BigQuery)
22158                        | Some(DialectType::MySQL)
22159                        | Some(DialectType::SQLite)
22160                );
22161
22162            for (i, (key, value)) in f.pairs.iter().enumerate() {
22163                if i > 0 {
22164                    self.write(", ");
22165                }
22166                self.generate_expression(key)?;
22167                if use_comma_syntax {
22168                    self.write(", ");
22169                } else {
22170                    self.write(": ");
22171                }
22172                self.generate_expression(value)?;
22173            }
22174        }
22175        if let Some(null_handling) = f.null_handling {
22176            self.write_space();
22177            match null_handling {
22178                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
22179                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
22180            }
22181        }
22182        if f.with_unique_keys {
22183            self.write_space();
22184            self.write_keyword("WITH UNIQUE KEYS");
22185        }
22186        if let Some(ref ret_type) = f.returning_type {
22187            self.write_space();
22188            self.write_keyword("RETURNING");
22189            self.write_space();
22190            self.generate_data_type(ret_type)?;
22191            if f.format_json {
22192                self.write_space();
22193                self.write_keyword("FORMAT JSON");
22194            }
22195            if let Some(ref enc) = f.encoding {
22196                self.write_space();
22197                self.write_keyword("ENCODING");
22198                self.write_space();
22199                self.write(enc);
22200            }
22201        }
22202        self.write(")");
22203        Ok(())
22204    }
22205
22206    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
22207        self.write_keyword(name);
22208        self.write("(");
22209        self.generate_expression(&f.this)?;
22210        for (path, value) in &f.path_values {
22211            self.write(", ");
22212            self.generate_expression(path)?;
22213            self.write(", ");
22214            self.generate_expression(value)?;
22215        }
22216        self.write(")");
22217        Ok(())
22218    }
22219
22220    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
22221        self.write_keyword("JSON_ARRAYAGG");
22222        self.write("(");
22223        self.generate_expression(&f.this)?;
22224        if let Some(ref order_by) = f.order_by {
22225            self.write_space();
22226            self.write_keyword("ORDER BY");
22227            self.write_space();
22228            for (i, ord) in order_by.iter().enumerate() {
22229                if i > 0 {
22230                    self.write(", ");
22231                }
22232                self.generate_ordered(ord)?;
22233            }
22234        }
22235        if let Some(null_handling) = f.null_handling {
22236            self.write_space();
22237            match null_handling {
22238                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
22239                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
22240            }
22241        }
22242        self.write(")");
22243        if let Some(ref filter) = f.filter {
22244            self.write_space();
22245            self.write_keyword("FILTER");
22246            self.write("(");
22247            self.write_keyword("WHERE");
22248            self.write_space();
22249            self.generate_expression(filter)?;
22250            self.write(")");
22251        }
22252        Ok(())
22253    }
22254
22255    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
22256        self.write_keyword("JSON_OBJECTAGG");
22257        self.write("(");
22258        self.generate_expression(&f.key)?;
22259        self.write(": ");
22260        self.generate_expression(&f.value)?;
22261        if let Some(null_handling) = f.null_handling {
22262            self.write_space();
22263            match null_handling {
22264                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
22265                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
22266            }
22267        }
22268        self.write(")");
22269        if let Some(ref filter) = f.filter {
22270            self.write_space();
22271            self.write_keyword("FILTER");
22272            self.write("(");
22273            self.write_keyword("WHERE");
22274            self.write_space();
22275            self.generate_expression(filter)?;
22276            self.write(")");
22277        }
22278        Ok(())
22279    }
22280
22281    // Type casting/conversion generators
22282
22283    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
22284        use crate::dialects::DialectType;
22285
22286        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
22287        if self.config.dialect == Some(DialectType::Redshift) {
22288            self.write_keyword("CAST");
22289            self.write("(");
22290            self.generate_expression(&f.this)?;
22291            self.write_space();
22292            self.write_keyword("AS");
22293            self.write_space();
22294            self.generate_data_type(&f.to)?;
22295            self.write(")");
22296            return Ok(());
22297        }
22298
22299        self.write_keyword("CONVERT");
22300        self.write("(");
22301        self.generate_data_type(&f.to)?;
22302        self.write(", ");
22303        self.generate_expression(&f.this)?;
22304        if let Some(ref style) = f.style {
22305            self.write(", ");
22306            self.generate_expression(style)?;
22307        }
22308        self.write(")");
22309        Ok(())
22310    }
22311
22312    // Additional expression generators
22313
22314    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
22315        if f.colon {
22316            // DuckDB syntax: LAMBDA x : expr
22317            self.write_keyword("LAMBDA");
22318            self.write_space();
22319            for (i, param) in f.parameters.iter().enumerate() {
22320                if i > 0 {
22321                    self.write(", ");
22322                }
22323                self.generate_identifier(param)?;
22324            }
22325            self.write(" : ");
22326        } else {
22327            // Standard syntax: x -> expr or (x, y) -> expr
22328            if f.parameters.len() == 1 {
22329                self.generate_identifier(&f.parameters[0])?;
22330            } else {
22331                self.write("(");
22332                for (i, param) in f.parameters.iter().enumerate() {
22333                    if i > 0 {
22334                        self.write(", ");
22335                    }
22336                    self.generate_identifier(param)?;
22337                }
22338                self.write(")");
22339            }
22340            self.write(" -> ");
22341        }
22342        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22343            if let Expression::Lambda(inner) = &f.body {
22344                self.generate_lambda_with_parenthesized_single_param(inner)?;
22345                return Ok(());
22346            }
22347        }
22348
22349        self.generate_expression(&f.body)
22350    }
22351
22352    fn generate_lambda_with_parenthesized_single_param(&mut self, f: &LambdaExpr) -> Result<()> {
22353        if f.colon {
22354            return self.generate_lambda(f);
22355        }
22356
22357        self.write("(");
22358        for (i, param) in f.parameters.iter().enumerate() {
22359            if i > 0 {
22360                self.write(", ");
22361            }
22362            self.generate_identifier(param)?;
22363        }
22364        self.write(") -> ");
22365        self.generate_expression(&f.body)
22366    }
22367
22368    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
22369        self.generate_identifier(&f.name)?;
22370        match f.separator {
22371            NamedArgSeparator::DArrow => self.write(" => "),
22372            NamedArgSeparator::ColonEq => self.write(" := "),
22373            NamedArgSeparator::Eq => self.write(" = "),
22374        }
22375        self.generate_expression(&f.value)
22376    }
22377
22378    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
22379        self.write_keyword(&f.prefix);
22380        self.write(" ");
22381        self.generate_expression(&f.this)
22382    }
22383
22384    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
22385        match f.style {
22386            ParameterStyle::Question => self.write("?"),
22387            ParameterStyle::Dollar => {
22388                if let Some(idx) = f.index {
22389                    if matches!(
22390                        self.config.dialect,
22391                        Some(DialectType::TSQL) | Some(DialectType::Fabric)
22392                    ) {
22393                        self.write("@P");
22394                        self.write(&idx.to_string());
22395                    } else {
22396                        self.write("$");
22397                        self.write(&idx.to_string());
22398                    }
22399                } else {
22400                    self.write("$");
22401                    if let Some(ref name) = f.name {
22402                        // Session variable like $x or $query_id
22403                        self.write(name);
22404                    }
22405                }
22406            }
22407            ParameterStyle::DollarBrace => {
22408                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
22409                self.write("${");
22410                if let Some(ref name) = f.name {
22411                    self.write(name);
22412                }
22413                if let Some(ref expr) = f.expression {
22414                    self.write(":");
22415                    self.write(expr);
22416                }
22417                self.write("}");
22418            }
22419            ParameterStyle::Colon => {
22420                self.write(":");
22421                if let Some(idx) = f.index {
22422                    self.write(&idx.to_string());
22423                } else if let Some(ref name) = f.name {
22424                    self.write(name);
22425                }
22426            }
22427            ParameterStyle::At => {
22428                self.write("@");
22429                if let Some(ref name) = f.name {
22430                    if f.string_quoted {
22431                        self.write("'");
22432                        self.write(name);
22433                        self.write("'");
22434                    } else if f.quoted {
22435                        self.write("\"");
22436                        self.write(name);
22437                        self.write("\"");
22438                    } else {
22439                        self.write(name);
22440                    }
22441                }
22442            }
22443            ParameterStyle::DoubleAt => {
22444                self.write("@@");
22445                if let Some(ref name) = f.name {
22446                    self.write(name);
22447                }
22448            }
22449            ParameterStyle::DoubleDollar => {
22450                self.write("$$");
22451                if let Some(ref name) = f.name {
22452                    self.write(name);
22453                }
22454            }
22455            ParameterStyle::Percent => {
22456                if let Some(ref name) = f.name {
22457                    // %(name)s format
22458                    self.write("%(");
22459                    self.write(name);
22460                    self.write(")s");
22461                } else {
22462                    // %s format
22463                    self.write("%s");
22464                }
22465            }
22466            ParameterStyle::Brace => {
22467                // Spark/Databricks widget template variable: {name}
22468                // ClickHouse query parameter may include kind: {name: Type}
22469                self.write("{");
22470                if let Some(ref name) = f.name {
22471                    self.write(name);
22472                }
22473                if let Some(ref expr) = f.expression {
22474                    self.write(": ");
22475                    self.write(expr);
22476                }
22477                self.write("}");
22478            }
22479        }
22480        Ok(())
22481    }
22482
22483    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
22484        self.write("?");
22485        if let Some(idx) = f.index {
22486            self.write(&idx.to_string());
22487        }
22488        Ok(())
22489    }
22490
22491    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
22492        if f.is_block {
22493            self.write("/*");
22494            self.write(&f.text);
22495            self.write("*/");
22496        } else {
22497            self.write("--");
22498            self.write(&f.text);
22499        }
22500        Ok(())
22501    }
22502
22503    // Additional predicate generators
22504
22505    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
22506        self.generate_expression(&f.this)?;
22507        if f.not {
22508            self.write_space();
22509            self.write_keyword("NOT");
22510        }
22511        self.write_space();
22512        self.write_keyword("SIMILAR TO");
22513        self.write_space();
22514        self.generate_expression(&f.pattern)?;
22515        if let Some(ref escape) = f.escape {
22516            self.write_space();
22517            self.write_keyword("ESCAPE");
22518            self.write_space();
22519            self.generate_expression(escape)?;
22520        }
22521        Ok(())
22522    }
22523
22524    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
22525        self.generate_expression(&f.this)?;
22526        self.write_space();
22527        // Output comparison operator if present
22528        if let Some(op) = &f.op {
22529            match op {
22530                QuantifiedOp::Eq => self.write("="),
22531                QuantifiedOp::Neq => self.write("<>"),
22532                QuantifiedOp::Lt => self.write("<"),
22533                QuantifiedOp::Lte => self.write("<="),
22534                QuantifiedOp::Gt => self.write(">"),
22535                QuantifiedOp::Gte => self.write(">="),
22536            }
22537            self.write_space();
22538        }
22539        self.write_keyword(name);
22540
22541        // If the child is a Subquery, it provides its own parens — output with space
22542        if matches!(&f.subquery, Expression::Subquery(_)) {
22543            self.write_space();
22544            self.generate_expression(&f.subquery)?;
22545        } else {
22546            let is_statement = matches!(
22547                &f.subquery,
22548                Expression::Select(_)
22549                    | Expression::Union(_)
22550                    | Expression::Intersect(_)
22551                    | Expression::Except(_)
22552            );
22553            if is_statement
22554                && !self.config.quantified_no_paren_space
22555                && matches!(self.config.dialect, Some(DialectType::ClickHouse))
22556            {
22557                self.write_space();
22558            }
22559            self.write("(");
22560
22561            if self.config.pretty && is_statement {
22562                self.write_newline();
22563                self.indent_level += 1;
22564                self.write_indent();
22565            }
22566            self.generate_expression(&f.subquery)?;
22567            if self.config.pretty && is_statement {
22568                self.write_newline();
22569                self.indent_level -= 1;
22570                self.write_indent();
22571            }
22572            self.write(")");
22573        }
22574        Ok(())
22575    }
22576
22577    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
22578        // Check if this is a simple binary form (this OVERLAPS expression)
22579        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
22580            self.generate_expression(this)?;
22581            self.write_space();
22582            self.write_keyword("OVERLAPS");
22583            self.write_space();
22584            self.generate_expression(expr)?;
22585        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
22586            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
22587        {
22588            // Full ANSI form: (a, b) OVERLAPS (c, d)
22589            self.write("(");
22590            self.generate_expression(ls)?;
22591            self.write(", ");
22592            self.generate_expression(le)?;
22593            self.write(")");
22594            self.write_space();
22595            self.write_keyword("OVERLAPS");
22596            self.write_space();
22597            self.write("(");
22598            self.generate_expression(rs)?;
22599            self.write(", ");
22600            self.generate_expression(re)?;
22601            self.write(")");
22602        }
22603        Ok(())
22604    }
22605
22606    // Type conversion generators
22607
22608    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
22609        use crate::dialects::DialectType;
22610
22611        // SingleStore uses !:> syntax for try cast
22612        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
22613            self.generate_expression(&cast.this)?;
22614            self.write(" !:> ");
22615            self.generate_data_type(&cast.to)?;
22616            return Ok(());
22617        }
22618
22619        // Teradata uses TRYCAST (no underscore)
22620        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
22621            self.write_keyword("TRYCAST");
22622            self.write("(");
22623            self.generate_expression(&cast.this)?;
22624            self.write_space();
22625            self.write_keyword("AS");
22626            self.write_space();
22627            self.generate_data_type(&cast.to)?;
22628            self.write(")");
22629            return Ok(());
22630        }
22631
22632        // Dialects without TRY_CAST: generate as regular CAST
22633        let keyword = if matches!(
22634            self.config.dialect,
22635            Some(DialectType::Hive)
22636                | Some(DialectType::MySQL)
22637                | Some(DialectType::SQLite)
22638                | Some(DialectType::Oracle)
22639                | Some(DialectType::ClickHouse)
22640                | Some(DialectType::Redshift)
22641                | Some(DialectType::PostgreSQL)
22642                | Some(DialectType::StarRocks)
22643                | Some(DialectType::Doris)
22644        ) {
22645            "CAST"
22646        } else {
22647            "TRY_CAST"
22648        };
22649
22650        self.write_keyword(keyword);
22651        self.write("(");
22652        self.generate_expression(&cast.this)?;
22653        self.write_space();
22654        self.write_keyword("AS");
22655        self.write_space();
22656        self.generate_data_type(&cast.to)?;
22657
22658        // Output FORMAT clause if present
22659        if let Some(format) = &cast.format {
22660            self.write_space();
22661            self.write_keyword("FORMAT");
22662            self.write_space();
22663            self.generate_expression(format)?;
22664        }
22665
22666        self.write(")");
22667        Ok(())
22668    }
22669
22670    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
22671        self.write_keyword("SAFE_CAST");
22672        self.write("(");
22673        self.generate_expression(&cast.this)?;
22674        self.write_space();
22675        self.write_keyword("AS");
22676        self.write_space();
22677        self.generate_data_type(&cast.to)?;
22678
22679        // Output FORMAT clause if present
22680        if let Some(format) = &cast.format {
22681            self.write_space();
22682            self.write_keyword("FORMAT");
22683            self.write_space();
22684            self.generate_expression(format)?;
22685        }
22686
22687        self.write(")");
22688        Ok(())
22689    }
22690
22691    // Array/struct/map access generators
22692
22693    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
22694        // Wrap the base expression in parentheses when it uses arrow syntax (->)
22695        // which has lower precedence than bracket subscript ([]).
22696        // E.g., (t.v -> '$.a')[s.x] instead of t.v -> '$.a'[s.x]
22697        let needs_parens = matches!(&s.this, Expression::JsonExtract(ref f) if f.arrow_syntax);
22698        if needs_parens {
22699            self.write("(");
22700        }
22701        self.generate_expression(&s.this)?;
22702        if needs_parens {
22703            self.write(")");
22704        }
22705        self.write("[");
22706        self.generate_expression(&s.index)?;
22707        self.write("]");
22708        Ok(())
22709    }
22710
22711    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
22712        self.generate_expression(&d.this)?;
22713        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
22714        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
22715        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
22716            && matches!(
22717                &d.this,
22718                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
22719            );
22720        if use_colon {
22721            self.write(":");
22722        } else {
22723            self.write(".");
22724        }
22725        self.generate_identifier(&d.field)
22726    }
22727
22728    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
22729        self.generate_expression(&m.this)?;
22730        self.write(".");
22731        // Method names after a dot should not be quoted based on reserved keywords
22732        // Only quote if explicitly marked as quoted in the AST
22733        if m.method.quoted {
22734            let q = self.config.identifier_quote;
22735            self.write(&format!("{}{}{}", q, m.method.name, q));
22736        } else {
22737            self.write(&m.method.name);
22738        }
22739        self.write("(");
22740        for (i, arg) in m.args.iter().enumerate() {
22741            if i > 0 {
22742                self.write(", ");
22743            }
22744            self.generate_expression(arg)?;
22745        }
22746        self.write(")");
22747        Ok(())
22748    }
22749
22750    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
22751        // Check if we need to wrap the inner expression in parentheses
22752        // JSON arrow expressions have lower precedence than array subscript
22753        let needs_parens = matches!(
22754            &s.this,
22755            Expression::JsonExtract(f) if f.arrow_syntax
22756        ) || matches!(
22757            &s.this,
22758            Expression::JsonExtractScalar(f) if f.arrow_syntax
22759        );
22760
22761        if needs_parens {
22762            self.write("(");
22763        }
22764        self.generate_expression(&s.this)?;
22765        if needs_parens {
22766            self.write(")");
22767        }
22768        self.write("[");
22769        if let Some(start) = &s.start {
22770            self.generate_expression(start)?;
22771        }
22772        self.write(":");
22773        if let Some(end) = &s.end {
22774            self.generate_expression(end)?;
22775        }
22776        self.write("]");
22777        Ok(())
22778    }
22779
22780    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
22781        // Generate left expression, but skip trailing comments if they're already in left_comments
22782        // to avoid duplication (comments are captured as both expr.trailing_comments
22783        // and BinaryOp.left_comments during parsing)
22784        match &op.left {
22785            Expression::Column(col) => {
22786                // Generate column with trailing comments but skip them if they're
22787                // already captured in BinaryOp.left_comments to avoid duplication
22788                if let Some(table) = &col.table {
22789                    self.generate_identifier(table)?;
22790                    self.write(".");
22791                }
22792                self.generate_identifier(&col.name)?;
22793                // Oracle-style join marker (+)
22794                if col.join_mark && self.config.supports_column_join_marks {
22795                    self.write(" (+)");
22796                }
22797                // Output column trailing comments if they're not already in left_comments
22798                if op.left_comments.is_empty() {
22799                    for comment in &col.trailing_comments {
22800                        self.write_space();
22801                        self.write_formatted_comment(comment);
22802                    }
22803                }
22804            }
22805            Expression::Add(inner_op)
22806            | Expression::Sub(inner_op)
22807            | Expression::Mul(inner_op)
22808            | Expression::Div(inner_op)
22809            | Expression::Concat(inner_op) => {
22810                // Generate binary op without its trailing comments
22811                self.generate_binary_op_no_trailing(inner_op, match &op.left {
22812                    Expression::Add(_) => "+",
22813                    Expression::Sub(_) => "-",
22814                    Expression::Mul(_) => "*",
22815                    Expression::Div(_) => "/",
22816                    Expression::Concat(_) => "||",
22817                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
22818                })?;
22819            }
22820            _ => {
22821                self.generate_expression(&op.left)?;
22822            }
22823        }
22824        // Output comments after left operand
22825        for comment in &op.left_comments {
22826            self.write_space();
22827            self.write_formatted_comment(comment);
22828        }
22829        if self.config.pretty
22830            && matches!(self.config.dialect, Some(DialectType::Snowflake))
22831            && (operator == "AND" || operator == "OR")
22832        {
22833            self.write_newline();
22834            self.write_indent();
22835            self.write_keyword(operator);
22836        } else {
22837            self.write_space();
22838            if operator.chars().all(|c| c.is_alphabetic()) {
22839                self.write_keyword(operator);
22840            } else {
22841                self.write(operator);
22842            }
22843        }
22844        // Output comments after operator (before right operand)
22845        for comment in &op.operator_comments {
22846            self.write_space();
22847            self.write_formatted_comment(comment);
22848        }
22849        self.write_space();
22850        self.generate_expression(&op.right)?;
22851        // Output trailing comments after right operand
22852        for comment in &op.trailing_comments {
22853            self.write_space();
22854            self.write_formatted_comment(comment);
22855        }
22856        Ok(())
22857    }
22858
22859    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
22860        let keyword = connector.keyword();
22861        let Some(terms) = self.flatten_connector_terms(op, connector) else {
22862            return self.generate_binary_op(op, keyword);
22863        };
22864
22865        let wrap_clickhouse_or_term = |generator: &mut Self, term: &Expression| -> Result<()> {
22866            let should_wrap = matches!(connector, ConnectorOperator::Or)
22867                && matches!(generator.config.dialect, Some(DialectType::ClickHouse))
22868                && matches!(
22869                    generator.config.source_dialect,
22870                    Some(DialectType::ClickHouse)
22871                )
22872                && matches!(term, Expression::And(_));
22873            if should_wrap {
22874                generator.write("(");
22875                generator.generate_expression(term)?;
22876                generator.write(")");
22877            } else {
22878                generator.generate_expression(term)?;
22879            }
22880            Ok(())
22881        };
22882
22883        wrap_clickhouse_or_term(self, terms[0])?;
22884        for term in terms.iter().skip(1) {
22885            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
22886                self.write_newline();
22887                self.write_indent();
22888                self.write_keyword(keyword);
22889            } else {
22890                self.write_space();
22891                self.write_keyword(keyword);
22892            }
22893            self.write_space();
22894            wrap_clickhouse_or_term(self, term)?;
22895        }
22896
22897        Ok(())
22898    }
22899
22900    fn flatten_connector_terms<'a>(
22901        &self,
22902        root: &'a BinaryOp,
22903        connector: ConnectorOperator,
22904    ) -> Option<Vec<&'a Expression>> {
22905        if !root.left_comments.is_empty()
22906            || !root.operator_comments.is_empty()
22907            || !root.trailing_comments.is_empty()
22908        {
22909            return None;
22910        }
22911
22912        let mut terms = Vec::new();
22913        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
22914
22915        while let Some(expr) = stack.pop() {
22916            match (connector, expr) {
22917                (ConnectorOperator::And, Expression::And(inner))
22918                    if inner.left_comments.is_empty()
22919                        && inner.operator_comments.is_empty()
22920                        && inner.trailing_comments.is_empty() =>
22921                {
22922                    stack.push(&inner.right);
22923                    stack.push(&inner.left);
22924                }
22925                (ConnectorOperator::Or, Expression::Or(inner))
22926                    if inner.left_comments.is_empty()
22927                        && inner.operator_comments.is_empty()
22928                        && inner.trailing_comments.is_empty() =>
22929                {
22930                    stack.push(&inner.right);
22931                    stack.push(&inner.left);
22932                }
22933                _ => terms.push(expr),
22934            }
22935        }
22936
22937        if terms.len() > 1 {
22938            Some(terms)
22939        } else {
22940            None
22941        }
22942    }
22943
22944    fn missing_closing_parens_outside_quotes(sql: &str) -> usize {
22945        let mut depth = 0usize;
22946        let mut quote: Option<char> = None;
22947        let mut chars = sql.chars().peekable();
22948
22949        while let Some(ch) = chars.next() {
22950            if let Some(quote_char) = quote {
22951                if ch == '\\' {
22952                    chars.next();
22953                } else if ch == quote_char {
22954                    if quote_char == '\'' && chars.peek() == Some(&'\'') {
22955                        chars.next();
22956                    } else {
22957                        quote = None;
22958                    }
22959                }
22960                continue;
22961            }
22962
22963            match ch {
22964                '\'' | '"' | '`' => quote = Some(ch),
22965                '(' => depth += 1,
22966                ')' => depth = depth.saturating_sub(1),
22967                _ => {}
22968            }
22969        }
22970
22971        depth
22972    }
22973
22974    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
22975    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
22976        self.generate_like_op_inner(op, operator, false)
22977    }
22978
22979    fn generate_like_op_negated(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
22980        self.generate_like_op_inner(op, operator, true)
22981    }
22982
22983    fn generate_like_op_inner(&mut self, op: &LikeOp, operator: &str, negated: bool) -> Result<()> {
22984        if negated
22985            && matches!(
22986                self.config.dialect,
22987                Some(DialectType::ClickHouse)
22988                    | Some(DialectType::DataFusion)
22989                    | Some(DialectType::TSQL)
22990                    | Some(DialectType::Fabric)
22991            )
22992        {
22993            self.write_keyword("NOT");
22994            self.write_space();
22995            return self.generate_like_op_inner(op, operator, false);
22996        }
22997
22998        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22999            if let Expression::Star(star) = &op.left {
23000                if star
23001                    .except
23002                    .as_ref()
23003                    .is_some_and(|except| !except.is_empty())
23004                {
23005                    if let Some(table) = &star.table {
23006                        self.generate_identifier(table)?;
23007                        self.write(".");
23008                    }
23009                    self.write("*");
23010                    self.write_space();
23011                    self.write_keyword(operator);
23012                    if let Some(quantifier) = &op.quantifier {
23013                        self.write_space();
23014                        self.write_keyword(quantifier);
23015                        self.write_space();
23016                    } else {
23017                        self.write_space();
23018                    }
23019                    self.generate_expression(&op.right)?;
23020                    if let Some(escape) = &op.escape {
23021                        self.write_space();
23022                        self.write_keyword("ESCAPE");
23023                        self.write_space();
23024                        self.generate_expression(escape)?;
23025                    }
23026                    if let Some(except) = &star.except {
23027                        self.write_space();
23028                        self.write_keyword("EXCEPT");
23029                        self.write(" (");
23030                        for (i, col) in except.iter().enumerate() {
23031                            if i > 0 {
23032                                self.write(", ");
23033                            }
23034                            self.generate_identifier(col)?;
23035                        }
23036                        self.write(")");
23037                    }
23038                    return Ok(());
23039                }
23040            }
23041        }
23042
23043        self.generate_expression(&op.left)?;
23044        self.write_space();
23045        if negated {
23046            self.write_keyword("NOT");
23047            self.write_space();
23048        }
23049        // Drill backtick-quotes ILIKE
23050        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
23051            self.write("`ILIKE`");
23052        } else {
23053            self.write_keyword(operator);
23054        }
23055        if let Some(quantifier) = &op.quantifier {
23056            self.write_space();
23057            self.write_keyword(quantifier);
23058            // Match Python sqlglot behavior:
23059            // ANY + Paren (single value): no space → ILIKE ANY('%a%')
23060            // ANY + Tuple (multiple values): space → LIKE ANY ('a', 'b')
23061            // ALL + anything: always space → LIKE ALL ('%a%'), LIKE ALL ('a', 'b')
23062            let is_any =
23063                quantifier.eq_ignore_ascii_case("ANY") || quantifier.eq_ignore_ascii_case("SOME");
23064            if !(is_any && matches!(&op.right, Expression::Paren(_))) {
23065                self.write_space();
23066            }
23067        } else {
23068            self.write_space();
23069        }
23070        self.generate_expression(&op.right)?;
23071        if let Some(escape) = &op.escape {
23072            self.write_space();
23073            self.write_keyword("ESCAPE");
23074            self.write_space();
23075            self.generate_expression(escape)?;
23076        }
23077        Ok(())
23078    }
23079
23080    /// Generate null-safe equality
23081    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
23082    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
23083        use crate::dialects::DialectType;
23084        self.generate_expression(&op.left)?;
23085        self.write_space();
23086        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
23087            self.write("<=>");
23088        } else {
23089            self.write_keyword("IS NOT DISTINCT FROM");
23090        }
23091        self.write_space();
23092        self.generate_expression(&op.right)?;
23093        Ok(())
23094    }
23095
23096    /// Generate IS DISTINCT FROM (null-safe inequality)
23097    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
23098        self.generate_expression(&op.left)?;
23099        self.write_space();
23100        self.write_keyword("IS DISTINCT FROM");
23101        self.write_space();
23102        self.generate_expression(&op.right)?;
23103        Ok(())
23104    }
23105
23106    /// Generate binary op without trailing comments (used when nested inside another binary op)
23107    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
23108        // Generate left expression, but skip trailing comments
23109        match &op.left {
23110            Expression::Column(col) => {
23111                if let Some(table) = &col.table {
23112                    self.generate_identifier(table)?;
23113                    self.write(".");
23114                }
23115                self.generate_identifier(&col.name)?;
23116                // Oracle-style join marker (+)
23117                if col.join_mark && self.config.supports_column_join_marks {
23118                    self.write(" (+)");
23119                }
23120            }
23121            Expression::Add(inner_op)
23122            | Expression::Sub(inner_op)
23123            | Expression::Mul(inner_op)
23124            | Expression::Div(inner_op)
23125            | Expression::Concat(inner_op) => {
23126                self.generate_binary_op_no_trailing(inner_op, match &op.left {
23127                    Expression::Add(_) => "+",
23128                    Expression::Sub(_) => "-",
23129                    Expression::Mul(_) => "*",
23130                    Expression::Div(_) => "/",
23131                    Expression::Concat(_) => "||",
23132                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
23133                })?;
23134            }
23135            _ => {
23136                self.generate_expression(&op.left)?;
23137            }
23138        }
23139        // Output left_comments
23140        for comment in &op.left_comments {
23141            self.write_space();
23142            self.write_formatted_comment(comment);
23143        }
23144        self.write_space();
23145        if operator.chars().all(|c| c.is_alphabetic()) {
23146            self.write_keyword(operator);
23147        } else {
23148            self.write(operator);
23149        }
23150        // Output operator_comments
23151        for comment in &op.operator_comments {
23152            self.write_space();
23153            self.write_formatted_comment(comment);
23154        }
23155        self.write_space();
23156        // Generate right expression, but skip trailing comments if it's a Column
23157        // (the parent's left_comments will output them)
23158        match &op.right {
23159            Expression::Column(col) => {
23160                if let Some(table) = &col.table {
23161                    self.generate_identifier(table)?;
23162                    self.write(".");
23163                }
23164                self.generate_identifier(&col.name)?;
23165                // Oracle-style join marker (+)
23166                if col.join_mark && self.config.supports_column_join_marks {
23167                    self.write(" (+)");
23168                }
23169            }
23170            _ => {
23171                self.generate_expression(&op.right)?;
23172            }
23173        }
23174        // Skip trailing_comments - parent will handle them via its left_comments
23175        Ok(())
23176    }
23177
23178    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
23179        if operator.chars().all(|c| c.is_alphabetic()) {
23180            self.write_keyword(operator);
23181            self.write_space();
23182        } else {
23183            self.write(operator);
23184            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
23185            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
23186                self.write_space();
23187            }
23188        }
23189        self.generate_expression(&op.this)
23190    }
23191
23192    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
23193        // Generic mode supports two styles for negated IN:
23194        // - Prefix: NOT a IN (...)
23195        // - Infix:  a NOT IN (...)
23196        let is_generic =
23197            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
23198        let use_prefix_not =
23199            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
23200        if use_prefix_not {
23201            self.write_keyword("NOT");
23202            self.write_space();
23203        }
23204        self.generate_expression(&in_expr.this)?;
23205        if in_expr.global {
23206            self.write_space();
23207            self.write_keyword("GLOBAL");
23208        }
23209        if in_expr.not && !use_prefix_not {
23210            self.write_space();
23211            self.write_keyword("NOT");
23212        }
23213        self.write_space();
23214        self.write_keyword("IN");
23215
23216        // BigQuery: IN UNNEST(expr)
23217        if let Some(unnest_expr) = &in_expr.unnest {
23218            self.write_space();
23219            self.write_keyword("UNNEST");
23220            self.write("(");
23221            self.generate_expression(unnest_expr)?;
23222            self.write(")");
23223            return Ok(());
23224        }
23225
23226        if let Some(query) = &in_expr.query {
23227            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
23228            // vs a subquery (col IN (SELECT ...))
23229            let is_bare = in_expr.expressions.is_empty()
23230                && !matches!(
23231                    query,
23232                    Expression::Select(_)
23233                        | Expression::Union(_)
23234                        | Expression::Intersect(_)
23235                        | Expression::Except(_)
23236                        | Expression::Subquery(_)
23237                );
23238            if is_bare {
23239                // Bare identifier: no parentheses
23240                self.write_space();
23241                self.generate_expression(query)?;
23242            } else {
23243                // Subquery: with parentheses
23244                self.write(" (");
23245                let is_statement = matches!(
23246                    query,
23247                    Expression::Select(_)
23248                        | Expression::Union(_)
23249                        | Expression::Intersect(_)
23250                        | Expression::Except(_)
23251                        | Expression::Subquery(_)
23252                );
23253                if self.config.pretty && is_statement {
23254                    self.write_newline();
23255                    self.indent_level += 1;
23256                    self.write_indent();
23257                }
23258                self.generate_expression(query)?;
23259                if self.config.pretty && is_statement {
23260                    self.write_newline();
23261                    self.indent_level -= 1;
23262                    self.write_indent();
23263                }
23264                self.write(")");
23265            }
23266        } else {
23267            // DuckDB: IN without parentheses for single expression that is NOT a literal
23268            // (array/list membership like 'red' IN tbl.flags)
23269            // ClickHouse: IN without parentheses for single non-array expressions
23270            let is_duckdb = matches!(
23271                self.config.dialect,
23272                Some(crate::dialects::DialectType::DuckDB)
23273            );
23274            let is_clickhouse = matches!(
23275                self.config.dialect,
23276                Some(crate::dialects::DialectType::ClickHouse)
23277            );
23278            let single_expr = in_expr.expressions.len() == 1;
23279            if is_clickhouse && single_expr {
23280                if let Expression::Array(arr) = &in_expr.expressions[0] {
23281                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
23282                    self.write(" (");
23283                    for (i, expr) in arr.expressions.iter().enumerate() {
23284                        if i > 0 {
23285                            self.write(", ");
23286                        }
23287                        self.generate_expression(expr)?;
23288                    }
23289                    self.write(")");
23290                } else if in_expr.is_field {
23291                    self.write_space();
23292                    self.generate_expression(&in_expr.expressions[0])?;
23293                } else {
23294                    self.write(" (");
23295                    self.generate_expression(&in_expr.expressions[0])?;
23296                    self.write(")");
23297                }
23298            } else {
23299                let is_bare_ref = single_expr
23300                    && matches!(
23301                        &in_expr.expressions[0],
23302                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
23303                    );
23304                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
23305                    // Bare field reference (no parens in source): IN identifier
23306                    // Also DuckDB: IN without parentheses for array/list membership
23307                    self.write_space();
23308                    self.generate_expression(&in_expr.expressions[0])?;
23309                } else {
23310                    // Standard IN (list)
23311                    self.write(" (");
23312                    for (i, expr) in in_expr.expressions.iter().enumerate() {
23313                        if i > 0 {
23314                            self.write(", ");
23315                        }
23316                        self.generate_expression(expr)?;
23317                    }
23318                    self.write(")");
23319                }
23320            }
23321        }
23322
23323        Ok(())
23324    }
23325
23326    fn generate_between(&mut self, between: &Between) -> Result<()> {
23327        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
23328        let use_prefix_not = between.not
23329            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
23330        if use_prefix_not {
23331            self.write_keyword("NOT");
23332            self.write_space();
23333        }
23334        self.generate_expression(&between.this)?;
23335        if between.not && !use_prefix_not {
23336            self.write_space();
23337            self.write_keyword("NOT");
23338        }
23339        self.write_space();
23340        self.write_keyword("BETWEEN");
23341        // Emit SYMMETRIC/ASYMMETRIC if present
23342        if let Some(sym) = between.symmetric {
23343            if sym {
23344                self.write(" SYMMETRIC");
23345            } else {
23346                self.write(" ASYMMETRIC");
23347            }
23348        }
23349        self.write_space();
23350        self.generate_expression(&between.low)?;
23351        self.write_space();
23352        self.write_keyword("AND");
23353        self.write_space();
23354        self.generate_expression(&between.high)
23355    }
23356
23357    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
23358        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
23359        let use_prefix_not = is_null.not
23360            && (self.config.dialect.is_none()
23361                || self.config.dialect == Some(DialectType::Generic)
23362                || is_null.postfix_form);
23363        if use_prefix_not {
23364            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
23365            self.write_keyword("NOT");
23366            self.write_space();
23367            self.generate_expression(&is_null.this)?;
23368            self.write_space();
23369            self.write_keyword("IS");
23370            self.write_space();
23371            self.write_keyword("NULL");
23372        } else {
23373            self.generate_expression(&is_null.this)?;
23374            self.write_space();
23375            self.write_keyword("IS");
23376            if is_null.not {
23377                self.write_space();
23378                self.write_keyword("NOT");
23379            }
23380            self.write_space();
23381            self.write_keyword("NULL");
23382        }
23383        Ok(())
23384    }
23385
23386    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
23387        self.generate_expression(&is_true.this)?;
23388        self.write_space();
23389        self.write_keyword("IS");
23390        if is_true.not {
23391            self.write_space();
23392            self.write_keyword("NOT");
23393        }
23394        self.write_space();
23395        self.write_keyword("TRUE");
23396        Ok(())
23397    }
23398
23399    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
23400        self.generate_expression(&is_false.this)?;
23401        self.write_space();
23402        self.write_keyword("IS");
23403        if is_false.not {
23404            self.write_space();
23405            self.write_keyword("NOT");
23406        }
23407        self.write_space();
23408        self.write_keyword("FALSE");
23409        Ok(())
23410    }
23411
23412    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
23413        self.generate_expression(&is_json.this)?;
23414        self.write_space();
23415        self.write_keyword("IS");
23416        if is_json.negated {
23417            self.write_space();
23418            self.write_keyword("NOT");
23419        }
23420        self.write_space();
23421        self.write_keyword("JSON");
23422
23423        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
23424        if let Some(ref json_type) = is_json.json_type {
23425            self.write_space();
23426            self.write_keyword(json_type);
23427        }
23428
23429        // Output key uniqueness constraint if specified
23430        match &is_json.unique_keys {
23431            Some(JsonUniqueKeys::With) => {
23432                self.write_space();
23433                self.write_keyword("WITH UNIQUE KEYS");
23434            }
23435            Some(JsonUniqueKeys::Without) => {
23436                self.write_space();
23437                self.write_keyword("WITHOUT UNIQUE KEYS");
23438            }
23439            Some(JsonUniqueKeys::Shorthand) => {
23440                self.write_space();
23441                self.write_keyword("UNIQUE KEYS");
23442            }
23443            None => {}
23444        }
23445
23446        Ok(())
23447    }
23448
23449    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
23450        self.generate_expression(&is_expr.left)?;
23451        self.write_space();
23452        self.write_keyword("IS");
23453        self.write_space();
23454        self.generate_expression(&is_expr.right)
23455    }
23456
23457    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
23458        if exists.not {
23459            self.write_keyword("NOT");
23460            self.write_space();
23461        }
23462        self.write_keyword("EXISTS");
23463        self.write("(");
23464        let is_statement = matches!(
23465            &exists.this,
23466            Expression::Select(_)
23467                | Expression::Union(_)
23468                | Expression::Intersect(_)
23469                | Expression::Except(_)
23470        );
23471        if self.config.pretty && is_statement {
23472            self.write_newline();
23473            self.indent_level += 1;
23474            self.write_indent();
23475            self.generate_expression(&exists.this)?;
23476            self.write_newline();
23477            self.indent_level -= 1;
23478            self.write_indent();
23479            self.write(")");
23480        } else {
23481            self.generate_expression(&exists.this)?;
23482            self.write(")");
23483        }
23484        Ok(())
23485    }
23486
23487    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
23488        self.generate_expression(&op.left)?;
23489        self.write_space();
23490        self.write_keyword("MEMBER OF");
23491        self.write("(");
23492        self.generate_expression(&op.right)?;
23493        self.write(")");
23494        Ok(())
23495    }
23496
23497    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
23498        if subquery.lateral {
23499            self.write_keyword("LATERAL");
23500            self.write_space();
23501        }
23502
23503        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
23504        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
23505        // to carry the LIMIT modifier without adding more parens
23506        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
23507            matches!(
23508                &p.this,
23509                Expression::Select(_)
23510                    | Expression::Union(_)
23511                    | Expression::Intersect(_)
23512                    | Expression::Except(_)
23513                    | Expression::Subquery(_)
23514            )
23515        } else {
23516            false
23517        };
23518
23519        // Check if inner expression is a statement for pretty formatting
23520        let is_statement = matches!(
23521            &subquery.this,
23522            Expression::Select(_)
23523                | Expression::Union(_)
23524                | Expression::Intersect(_)
23525                | Expression::Except(_)
23526                | Expression::Merge(_)
23527        );
23528
23529        if !skip_outer_parens {
23530            self.write("(");
23531            if self.config.pretty && is_statement {
23532                self.write_newline();
23533                self.indent_level += 1;
23534                self.write_indent();
23535            }
23536        }
23537        self.generate_expression(&subquery.this)?;
23538
23539        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
23540        if subquery.modifiers_inside {
23541            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
23542            if let Some(order_by) = &subquery.order_by {
23543                self.write_space();
23544                self.write_keyword("ORDER BY");
23545                self.write_space();
23546                for (i, ord) in order_by.expressions.iter().enumerate() {
23547                    if i > 0 {
23548                        self.write(", ");
23549                    }
23550                    self.generate_ordered(ord)?;
23551                }
23552            }
23553
23554            if let Some(limit) = &subquery.limit {
23555                self.write_space();
23556                self.write_keyword("LIMIT");
23557                self.write_space();
23558                self.generate_expression(&limit.this)?;
23559                if limit.percent {
23560                    self.write_space();
23561                    self.write_keyword("PERCENT");
23562                }
23563            }
23564
23565            if let Some(offset) = &subquery.offset {
23566                self.write_space();
23567                self.write_keyword("OFFSET");
23568                self.write_space();
23569                self.generate_expression(&offset.this)?;
23570            }
23571        }
23572
23573        if !skip_outer_parens {
23574            if self.config.pretty && is_statement {
23575                self.write_newline();
23576                self.indent_level -= 1;
23577                self.write_indent();
23578            }
23579            self.write(")");
23580        }
23581
23582        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
23583        if !subquery.modifiers_inside {
23584            if let Some(order_by) = &subquery.order_by {
23585                self.write_space();
23586                self.write_keyword("ORDER BY");
23587                self.write_space();
23588                for (i, ord) in order_by.expressions.iter().enumerate() {
23589                    if i > 0 {
23590                        self.write(", ");
23591                    }
23592                    self.generate_ordered(ord)?;
23593                }
23594            }
23595
23596            if let Some(limit) = &subquery.limit {
23597                self.write_space();
23598                self.write_keyword("LIMIT");
23599                self.write_space();
23600                self.generate_expression(&limit.this)?;
23601                if limit.percent {
23602                    self.write_space();
23603                    self.write_keyword("PERCENT");
23604                }
23605            }
23606
23607            if let Some(offset) = &subquery.offset {
23608                self.write_space();
23609                self.write_keyword("OFFSET");
23610                self.write_space();
23611                self.generate_expression(&offset.this)?;
23612            }
23613
23614            // Generate DISTRIBUTE BY (Hive/Spark)
23615            if let Some(distribute_by) = &subquery.distribute_by {
23616                self.write_space();
23617                self.write_keyword("DISTRIBUTE BY");
23618                self.write_space();
23619                for (i, expr) in distribute_by.expressions.iter().enumerate() {
23620                    if i > 0 {
23621                        self.write(", ");
23622                    }
23623                    self.generate_expression(expr)?;
23624                }
23625            }
23626
23627            // Generate SORT BY (Hive/Spark)
23628            if let Some(sort_by) = &subquery.sort_by {
23629                self.write_space();
23630                self.write_keyword("SORT BY");
23631                self.write_space();
23632                for (i, ord) in sort_by.expressions.iter().enumerate() {
23633                    if i > 0 {
23634                        self.write(", ");
23635                    }
23636                    self.generate_ordered(ord)?;
23637                }
23638            }
23639
23640            // Generate CLUSTER BY (Hive/Spark)
23641            if let Some(cluster_by) = &subquery.cluster_by {
23642                self.write_space();
23643                self.write_keyword("CLUSTER BY");
23644                self.write_space();
23645                for (i, ord) in cluster_by.expressions.iter().enumerate() {
23646                    if i > 0 {
23647                        self.write(", ");
23648                    }
23649                    self.generate_ordered(ord)?;
23650                }
23651            }
23652        }
23653
23654        if let Some(alias) = &subquery.alias {
23655            self.write_space();
23656            let skip_as = matches!(self.config.dialect, Some(DialectType::Oracle))
23657                || (matches!(self.config.dialect, Some(DialectType::ClickHouse))
23658                    && !subquery.alias_explicit_as);
23659            if !skip_as {
23660                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23661                    self.write(subquery.alias_keyword.as_deref().unwrap_or("AS"));
23662                } else {
23663                    self.write_keyword("AS");
23664                }
23665                self.write_space();
23666            }
23667            self.generate_identifier(alias)?;
23668            if !subquery.column_aliases.is_empty() {
23669                self.write("(");
23670                for (i, col) in subquery.column_aliases.iter().enumerate() {
23671                    if i > 0 {
23672                        self.write(", ");
23673                    }
23674                    self.generate_identifier(col)?;
23675                }
23676                self.write(")");
23677            }
23678        }
23679        // Output trailing comments
23680        for comment in &subquery.trailing_comments {
23681            self.write(" ");
23682            self.write_formatted_comment(comment);
23683        }
23684        Ok(())
23685    }
23686
23687    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
23688        // Generate WITH clause if present
23689        if let Some(ref with) = pivot.with {
23690            self.generate_with(with)?;
23691            self.write_space();
23692        }
23693
23694        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
23695
23696        // Check for Redshift UNPIVOT in FROM clause:
23697        // UNPIVOT expr [AS val AT attr]
23698        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
23699        let is_redshift_unpivot = pivot.unpivot
23700            && pivot.expressions.is_empty()
23701            && pivot.fields.is_empty()
23702            && pivot.using.is_empty()
23703            && pivot.into.is_none()
23704            && !matches!(&pivot.this, Expression::Null(_));
23705
23706        if is_redshift_unpivot {
23707            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
23708            self.write_keyword("UNPIVOT");
23709            self.write_space();
23710            self.generate_expression(&pivot.this)?;
23711            // Alias - for Redshift it can be "val AT attr" format
23712            if let Some(alias) = &pivot.alias {
23713                self.write_space();
23714                self.write_keyword("AS");
23715                self.write_space();
23716                // The alias might contain " AT " for the attr part
23717                self.write(&alias.name);
23718            }
23719            return Ok(());
23720        }
23721
23722        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
23723        let is_simplified = !pivot.using.is_empty()
23724            || pivot.into.is_some()
23725            || (pivot.fields.is_empty()
23726                && !pivot.expressions.is_empty()
23727                && !matches!(&pivot.this, Expression::Null(_)));
23728
23729        if is_simplified {
23730            // DuckDB simplified syntax:
23731            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
23732            //   UNPIVOT table ON cols INTO NAME col VALUE col
23733            self.write_keyword(direction);
23734            self.write_space();
23735            self.generate_expression(&pivot.this)?;
23736
23737            if !pivot.expressions.is_empty() {
23738                self.write_space();
23739                self.write_keyword("ON");
23740                self.write_space();
23741                for (i, expr) in pivot.expressions.iter().enumerate() {
23742                    if i > 0 {
23743                        self.write(", ");
23744                    }
23745                    self.generate_expression(expr)?;
23746                }
23747            }
23748
23749            // INTO (for UNPIVOT)
23750            if let Some(into) = &pivot.into {
23751                self.write_space();
23752                self.write_keyword("INTO");
23753                self.write_space();
23754                self.generate_expression(into)?;
23755            }
23756
23757            // USING (for PIVOT)
23758            if !pivot.using.is_empty() {
23759                self.write_space();
23760                self.write_keyword("USING");
23761                self.write_space();
23762                for (i, expr) in pivot.using.iter().enumerate() {
23763                    if i > 0 {
23764                        self.write(", ");
23765                    }
23766                    self.generate_expression(expr)?;
23767                }
23768            }
23769
23770            // GROUP BY
23771            if let Some(group) = &pivot.group {
23772                self.write_space();
23773                self.generate_expression(group)?;
23774            }
23775        } else {
23776            // Standard syntax:
23777            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
23778            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
23779            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
23780            if !matches!(&pivot.this, Expression::Null(_)) {
23781                self.generate_expression(&pivot.this)?;
23782                self.write_space();
23783            }
23784            self.write_keyword(direction);
23785            self.write("(");
23786
23787            // Aggregation expressions
23788            for (i, expr) in pivot.expressions.iter().enumerate() {
23789                if i > 0 {
23790                    self.write(", ");
23791                }
23792                self.generate_expression(expr)?;
23793            }
23794
23795            // FOR...IN fields
23796            if !pivot.fields.is_empty() {
23797                if !pivot.expressions.is_empty() {
23798                    self.write_space();
23799                }
23800                self.write_keyword("FOR");
23801                self.write_space();
23802                for (i, field) in pivot.fields.iter().enumerate() {
23803                    if i > 0 {
23804                        self.write_space();
23805                    }
23806                    // field is an In expression: column IN (values)
23807                    self.generate_expression(field)?;
23808                }
23809            }
23810
23811            // DEFAULT ON NULL
23812            if let Some(default_val) = &pivot.default_on_null {
23813                self.write_space();
23814                self.write_keyword("DEFAULT ON NULL");
23815                self.write(" (");
23816                self.generate_expression(default_val)?;
23817                self.write(")");
23818            }
23819
23820            // GROUP BY inside PIVOT parens
23821            if let Some(group) = &pivot.group {
23822                self.write_space();
23823                self.generate_expression(group)?;
23824            }
23825
23826            self.write(")");
23827        }
23828
23829        // Alias
23830        if let Some(alias) = &pivot.alias {
23831            self.write_space();
23832            self.write_keyword("AS");
23833            self.write_space();
23834            self.generate_identifier(alias)?;
23835            self.generate_alias_column_list(&pivot.alias_columns)?;
23836        }
23837
23838        Ok(())
23839    }
23840
23841    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
23842        self.generate_expression(&unpivot.this)?;
23843        self.write_space();
23844        self.write_keyword("UNPIVOT");
23845        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
23846        if let Some(include) = unpivot.include_nulls {
23847            self.write_space();
23848            if include {
23849                self.write_keyword("INCLUDE NULLS");
23850            } else {
23851                self.write_keyword("EXCLUDE NULLS");
23852            }
23853            self.write_space();
23854        }
23855        self.write("(");
23856        if unpivot.value_column_parenthesized {
23857            self.write("(");
23858        }
23859        self.generate_identifier(&unpivot.value_column)?;
23860        // Output additional value columns if present
23861        for extra_col in &unpivot.extra_value_columns {
23862            self.write(", ");
23863            self.generate_identifier(extra_col)?;
23864        }
23865        if unpivot.value_column_parenthesized {
23866            self.write(")");
23867        }
23868        self.write_space();
23869        self.write_keyword("FOR");
23870        self.write_space();
23871        self.generate_identifier(&unpivot.name_column)?;
23872        self.write_space();
23873        self.write_keyword("IN");
23874        self.write(" (");
23875        for (i, col) in unpivot.columns.iter().enumerate() {
23876            if i > 0 {
23877                self.write(", ");
23878            }
23879            self.generate_expression(col)?;
23880        }
23881        self.write("))");
23882        if let Some(alias) = &unpivot.alias {
23883            self.write_space();
23884            self.write_keyword("AS");
23885            self.write_space();
23886            self.generate_identifier(alias)?;
23887            self.generate_alias_column_list(&unpivot.alias_columns)?;
23888        }
23889        Ok(())
23890    }
23891
23892    fn generate_alias_column_list(&mut self, columns: &[Identifier]) -> Result<()> {
23893        if columns.is_empty() {
23894            return Ok(());
23895        }
23896
23897        self.write("(");
23898        for (i, column) in columns.iter().enumerate() {
23899            if i > 0 {
23900                self.write(", ");
23901            }
23902            self.generate_identifier(column)?;
23903        }
23904        self.write(")");
23905        Ok(())
23906    }
23907
23908    fn generate_values(&mut self, values: &Values) -> Result<()> {
23909        self.write_keyword("VALUES");
23910        for (i, row) in values.expressions.iter().enumerate() {
23911            if i > 0 {
23912                self.write(",");
23913            }
23914            self.write(" (");
23915            for (j, expr) in row.expressions.iter().enumerate() {
23916                if j > 0 {
23917                    self.write(", ");
23918                }
23919                self.generate_expression(expr)?;
23920            }
23921            self.write(")");
23922        }
23923        if let Some(alias) = &values.alias {
23924            self.write_space();
23925            self.write_keyword("AS");
23926            self.write_space();
23927            self.generate_identifier(alias)?;
23928            if !values.column_aliases.is_empty() {
23929                self.write("(");
23930                for (i, col) in values.column_aliases.iter().enumerate() {
23931                    if i > 0 {
23932                        self.write(", ");
23933                    }
23934                    self.generate_identifier(col)?;
23935                }
23936                self.write(")");
23937            }
23938        }
23939        Ok(())
23940    }
23941
23942    fn generate_array(&mut self, arr: &Array) -> Result<()> {
23943        // Apply struct name inheritance for target dialects that need it
23944        let needs_inheritance = matches!(
23945            self.config.dialect,
23946            Some(DialectType::DuckDB)
23947                | Some(DialectType::Spark)
23948                | Some(DialectType::Databricks)
23949                | Some(DialectType::Hive)
23950                | Some(DialectType::Snowflake)
23951                | Some(DialectType::Presto)
23952                | Some(DialectType::Trino)
23953        );
23954        let propagated: Vec<Expression>;
23955        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
23956            propagated = Self::inherit_struct_field_names(&arr.expressions);
23957            &propagated
23958        } else {
23959            &arr.expressions
23960        };
23961
23962        // Generic mode: ARRAY(1, 2, 3) with parentheses
23963        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
23964        let use_parens =
23965            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
23966        if !self.config.array_bracket_only {
23967            self.write_keyword("ARRAY");
23968        }
23969        if use_parens {
23970            self.write("(");
23971        } else {
23972            self.write("[");
23973        }
23974        for (i, expr) in expressions.iter().enumerate() {
23975            if i > 0 {
23976                self.write(", ");
23977            }
23978            self.generate_expression(expr)?;
23979        }
23980        if use_parens {
23981            self.write(")");
23982        } else {
23983            self.write("]");
23984        }
23985        Ok(())
23986    }
23987
23988    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
23989        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
23990        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
23991        if tuple.expressions.len() == 2 {
23992            if let Expression::TableAlias(_) = &tuple.expressions[1] {
23993                // First element is the function/expression, second is the TableAlias
23994                self.generate_expression(&tuple.expressions[0])?;
23995                self.write_space();
23996                self.write_keyword("AS");
23997                self.write_space();
23998                self.generate_expression(&tuple.expressions[1])?;
23999                return Ok(());
24000            }
24001        }
24002
24003        // In pretty mode, format long tuples with each element on a new line
24004        // Only expand if total width exceeds threshold
24005        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
24006            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
24007            for expr in &tuple.expressions {
24008                expr_strings.push(self.generate_to_string(expr)?);
24009            }
24010            self.too_wide(&expr_strings)
24011        } else {
24012            false
24013        };
24014
24015        if expand_tuple {
24016            self.write("(");
24017            self.write_newline();
24018            self.indent_level += 1;
24019            for (i, expr) in tuple.expressions.iter().enumerate() {
24020                if i > 0 {
24021                    self.write(",");
24022                    self.write_newline();
24023                }
24024                self.write_indent();
24025                self.generate_expression(expr)?;
24026            }
24027            self.indent_level -= 1;
24028            self.write_newline();
24029            self.write_indent();
24030            self.write(")");
24031        } else {
24032            self.write("(");
24033            for (i, expr) in tuple.expressions.iter().enumerate() {
24034                if i > 0 {
24035                    self.write(", ");
24036                }
24037                self.generate_expression(expr)?;
24038            }
24039            self.write(")");
24040        }
24041        Ok(())
24042    }
24043
24044    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
24045        self.generate_expression(&pipe.this)?;
24046        self.write(" |> ");
24047        self.generate_expression(&pipe.expression)?;
24048        Ok(())
24049    }
24050
24051    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
24052        let unsupported_tsql_null_ordering = ordered.nulls_first.is_some()
24053            && !self.config.null_ordering_supported
24054            && matches!(
24055                self.config.dialect,
24056                Some(DialectType::TSQL) | Some(DialectType::Fabric)
24057            );
24058        let random_ordering = matches!(ordered.this, Expression::Rand(_) | Expression::Random(_));
24059        let emulate_tsql_null_ordering = if let Some(nulls_first) = ordered.nulls_first {
24060            let target_default_nulls_first = !ordered.desc;
24061
24062            unsupported_tsql_null_ordering
24063                && nulls_first != target_default_nulls_first
24064                && !random_ordering
24065        } else {
24066            false
24067        };
24068
24069        if emulate_tsql_null_ordering {
24070            if Self::is_integer_ordering_literal(&ordered.this) {
24071                let nulls_order = if ordered.nulls_first == Some(true) {
24072                    "NULLS FIRST"
24073                } else {
24074                    "NULLS LAST"
24075                };
24076                self.unsupported(format!(
24077                    "'{nulls_order}' translation not supported with positional ordering"
24078                ))?;
24079            } else {
24080                self.write_keyword("CASE WHEN");
24081                self.write_space();
24082                self.generate_expression(&ordered.this)?;
24083                self.write_space();
24084                self.write_keyword("IS NULL THEN 1 ELSE 0 END");
24085                if ordered.nulls_first == Some(true) {
24086                    self.write_space();
24087                    self.write_keyword("DESC");
24088                }
24089                self.write(", ");
24090            }
24091        }
24092
24093        self.generate_expression(&ordered.this)?;
24094        if ordered.desc {
24095            self.write_space();
24096            self.write_keyword("DESC");
24097        } else if ordered.explicit_asc {
24098            self.write_space();
24099            self.write_keyword("ASC");
24100        }
24101        if let Some(nulls_first) = ordered.nulls_first {
24102            if !unsupported_tsql_null_ordering
24103                && (self.config.null_ordering_supported
24104                    || !matches!(self.config.dialect, Some(DialectType::Fabric)))
24105            {
24106                // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
24107                // for the dialect. Different dialects have different NULL ordering defaults:
24108                //
24109                // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
24110                //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
24111                //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
24112                //
24113                // nulls_are_small (Spark, Hive, BigQuery, most others):
24114                //   - ASC: NULLS FIRST is default
24115                //   - DESC: NULLS LAST is default
24116                //
24117                // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
24118                //   - NULLS LAST is always the default regardless of sort direction
24119                let is_asc = !ordered.desc;
24120                let is_nulls_are_large = matches!(
24121                    self.config.dialect,
24122                    Some(DialectType::Oracle)
24123                        | Some(DialectType::PostgreSQL)
24124                        | Some(DialectType::Redshift)
24125                        | Some(DialectType::Snowflake)
24126                );
24127                let is_nulls_are_last = matches!(
24128                    self.config.dialect,
24129                    Some(DialectType::Dremio)
24130                        | Some(DialectType::DuckDB)
24131                        | Some(DialectType::Presto)
24132                        | Some(DialectType::Trino)
24133                        | Some(DialectType::Athena)
24134                        | Some(DialectType::ClickHouse)
24135                        | Some(DialectType::Drill)
24136                        | Some(DialectType::Exasol)
24137                );
24138
24139                // Check if the NULLS ordering matches the default for this dialect
24140                let is_default_nulls = if is_nulls_are_large {
24141                    // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
24142                    (is_asc && !nulls_first) || (!is_asc && nulls_first)
24143                } else if is_nulls_are_last {
24144                    // For nulls_are_last: NULLS LAST is always default
24145                    !nulls_first
24146                } else {
24147                    false
24148                };
24149
24150                if !is_default_nulls {
24151                    self.write_space();
24152                    self.write_keyword("NULLS");
24153                    self.write_space();
24154                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
24155                }
24156            }
24157        }
24158        // WITH FILL clause (ClickHouse)
24159        if let Some(ref with_fill) = ordered.with_fill {
24160            self.write_space();
24161            self.generate_with_fill(with_fill)?;
24162        }
24163        Ok(())
24164    }
24165
24166    fn is_integer_ordering_literal(expr: &Expression) -> bool {
24167        matches!(
24168            expr,
24169            Expression::Literal(lit)
24170                if matches!(lit.as_ref(), Literal::Number(n) if n.parse::<u64>().is_ok())
24171        )
24172    }
24173
24174    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
24175    fn write_clickhouse_type(&mut self, type_str: &str) {
24176        if self.clickhouse_nullable_depth < 0 {
24177            // Map key context: don't wrap in Nullable
24178            self.write(type_str);
24179        } else {
24180            self.write(&format!("Nullable({})", type_str));
24181        }
24182    }
24183
24184    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
24185        use crate::dialects::DialectType;
24186
24187        match dt {
24188            DataType::Boolean => {
24189                // Dialect-specific boolean type mappings
24190                match self.config.dialect {
24191                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
24192                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
24193                    Some(DialectType::Oracle) => {
24194                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
24195                        self.write_keyword("NUMBER(1)")
24196                    }
24197                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
24198                    _ => self.write_keyword("BOOLEAN"),
24199                }
24200            }
24201            DataType::TinyInt { length } => {
24202                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
24203                // Dremio maps TINYINT to INT
24204                // ClickHouse maps TINYINT to Int8
24205                match self.config.dialect {
24206                    Some(DialectType::PostgreSQL)
24207                    | Some(DialectType::Redshift)
24208                    | Some(DialectType::Oracle)
24209                    | Some(DialectType::Exasol) => {
24210                        self.write_keyword("SMALLINT");
24211                    }
24212                    Some(DialectType::Teradata) => {
24213                        // Teradata uses BYTEINT for smallest integer
24214                        self.write_keyword("BYTEINT");
24215                    }
24216                    Some(DialectType::Dremio) => {
24217                        // Dremio maps TINYINT to INT
24218                        self.write_keyword("INT");
24219                    }
24220                    Some(DialectType::ClickHouse) => {
24221                        self.write_clickhouse_type("Int8");
24222                    }
24223                    _ => {
24224                        self.write_keyword("TINYINT");
24225                    }
24226                }
24227                if let Some(n) = length {
24228                    if !matches!(
24229                        self.config.dialect,
24230                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
24231                    ) {
24232                        self.write(&format!("({})", n));
24233                    }
24234                }
24235            }
24236            DataType::SmallInt { length } => {
24237                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
24238                match self.config.dialect {
24239                    Some(DialectType::Dremio) => {
24240                        self.write_keyword("INT");
24241                    }
24242                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
24243                        self.write_keyword("INTEGER");
24244                    }
24245                    Some(DialectType::BigQuery) => {
24246                        self.write_keyword("INT64");
24247                    }
24248                    Some(DialectType::ClickHouse) => {
24249                        self.write_clickhouse_type("Int16");
24250                    }
24251                    _ => {
24252                        self.write_keyword("SMALLINT");
24253                        if let Some(n) = length {
24254                            self.write(&format!("({})", n));
24255                        }
24256                    }
24257                }
24258            }
24259            DataType::Int {
24260                length,
24261                integer_spelling: _,
24262            } => {
24263                // BigQuery uses INT64 for INT
24264                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
24265                    self.write_keyword("INT64");
24266                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24267                    self.write_clickhouse_type("Int32");
24268                } else {
24269                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
24270                    let use_integer = match self.config.dialect {
24271                        Some(DialectType::TSQL)
24272                        | Some(DialectType::Fabric)
24273                        | Some(DialectType::Presto)
24274                        | Some(DialectType::Trino)
24275                        | Some(DialectType::SQLite)
24276                        | Some(DialectType::Redshift) => true,
24277                        _ => false,
24278                    };
24279                    if use_integer {
24280                        self.write_keyword("INTEGER");
24281                    } else {
24282                        self.write_keyword("INT");
24283                    }
24284                    if let Some(n) = length {
24285                        self.write(&format!("({})", n));
24286                    }
24287                }
24288            }
24289            DataType::BigInt { length } => {
24290                // Dialect-specific bigint type mappings
24291                match self.config.dialect {
24292                    Some(DialectType::Oracle) => {
24293                        // Oracle doesn't have BIGINT, uses INT
24294                        self.write_keyword("INT");
24295                    }
24296                    Some(DialectType::ClickHouse) => {
24297                        self.write_clickhouse_type("Int64");
24298                    }
24299                    _ => {
24300                        self.write_keyword("BIGINT");
24301                        if let Some(n) = length {
24302                            self.write(&format!("({})", n));
24303                        }
24304                    }
24305                }
24306            }
24307            DataType::Float {
24308                precision,
24309                scale,
24310                real_spelling,
24311            } => {
24312                // Dialect-specific float type mappings
24313                // If real_spelling is true, preserve REAL; otherwise use dialect default
24314                // Spark/Hive don't support REAL, always use FLOAT
24315                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24316                    self.write_clickhouse_type("Float32");
24317                } else if *real_spelling
24318                    && !matches!(
24319                        self.config.dialect,
24320                        Some(DialectType::Spark)
24321                            | Some(DialectType::Databricks)
24322                            | Some(DialectType::Hive)
24323                            | Some(DialectType::Snowflake)
24324                            | Some(DialectType::MySQL)
24325                            | Some(DialectType::BigQuery)
24326                    )
24327                {
24328                    self.write_keyword("REAL")
24329                } else {
24330                    match self.config.dialect {
24331                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
24332                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
24333                        _ => self.write_keyword("FLOAT"),
24334                    }
24335                }
24336                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
24337                // Spark/Hive don't support FLOAT(precision)
24338                if !matches!(
24339                    self.config.dialect,
24340                    Some(DialectType::Spark)
24341                        | Some(DialectType::Databricks)
24342                        | Some(DialectType::Hive)
24343                        | Some(DialectType::Presto)
24344                        | Some(DialectType::Trino)
24345                ) {
24346                    if let Some(p) = precision {
24347                        self.write(&format!("({}", p));
24348                        if let Some(s) = scale {
24349                            self.write(&format!(", {})", s));
24350                        } else {
24351                            self.write(")");
24352                        }
24353                    }
24354                }
24355            }
24356            DataType::Double { precision, scale } => {
24357                // Dialect-specific double type mappings
24358                match self.config.dialect {
24359                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24360                        self.write_keyword("FLOAT")
24361                    } // SQL Server/Fabric FLOAT is double
24362                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
24363                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
24364                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
24365                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
24366                    Some(DialectType::PostgreSQL)
24367                    | Some(DialectType::Redshift)
24368                    | Some(DialectType::Teradata)
24369                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
24370                    _ => self.write_keyword("DOUBLE"),
24371                }
24372                // MySQL supports DOUBLE(precision, scale)
24373                if let Some(p) = precision {
24374                    self.write(&format!("({}", p));
24375                    if let Some(s) = scale {
24376                        self.write(&format!(", {})", s));
24377                    } else {
24378                        self.write(")");
24379                    }
24380                }
24381            }
24382            DataType::Decimal { precision, scale } => {
24383                // Dialect-specific decimal type mappings
24384                match self.config.dialect {
24385                    Some(DialectType::ClickHouse) => {
24386                        self.write("Decimal");
24387                        if let Some(p) = precision {
24388                            self.write(&format!("({}", p));
24389                            if let Some(s) = scale {
24390                                self.write(&format!(", {}", s));
24391                            }
24392                            self.write(")");
24393                        }
24394                    }
24395                    Some(DialectType::Oracle) => {
24396                        // Oracle uses NUMBER instead of DECIMAL
24397                        self.write_keyword("NUMBER");
24398                        if let Some(p) = precision {
24399                            self.write(&format!("({}", p));
24400                            if let Some(s) = scale {
24401                                self.write(&format!(", {}", s));
24402                            }
24403                            self.write(")");
24404                        }
24405                    }
24406                    Some(DialectType::BigQuery) => {
24407                        // BigQuery uses NUMERIC instead of DECIMAL
24408                        self.write_keyword("NUMERIC");
24409                        if let Some(p) = precision {
24410                            self.write(&format!("({}", p));
24411                            if let Some(s) = scale {
24412                                self.write(&format!(", {}", s));
24413                            }
24414                            self.write(")");
24415                        }
24416                    }
24417                    _ => {
24418                        self.write_keyword("DECIMAL");
24419                        if let Some(p) = precision {
24420                            self.write(&format!("({}", p));
24421                            if let Some(s) = scale {
24422                                self.write(&format!(", {}", s));
24423                            }
24424                            self.write(")");
24425                        }
24426                    }
24427                }
24428            }
24429            DataType::Char { length } => {
24430                // Dialect-specific char type mappings
24431                match self.config.dialect {
24432                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
24433                        // DuckDB/SQLite maps CHAR to TEXT
24434                        self.write_keyword("TEXT");
24435                    }
24436                    Some(DialectType::Hive)
24437                    | Some(DialectType::Spark)
24438                    | Some(DialectType::Databricks) => {
24439                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
24440                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
24441                        if length.is_some()
24442                            && !matches!(self.config.dialect, Some(DialectType::Hive))
24443                        {
24444                            self.write_keyword("CHAR");
24445                            if let Some(n) = length {
24446                                self.write(&format!("({})", n));
24447                            }
24448                        } else {
24449                            self.write_keyword("STRING");
24450                        }
24451                    }
24452                    Some(DialectType::Dremio) => {
24453                        // Dremio maps CHAR to VARCHAR
24454                        self.write_keyword("VARCHAR");
24455                        if let Some(n) = length {
24456                            self.write(&format!("({})", n));
24457                        }
24458                    }
24459                    _ => {
24460                        self.write_keyword("CHAR");
24461                        if let Some(n) = length {
24462                            self.write(&format!("({})", n));
24463                        }
24464                    }
24465                }
24466            }
24467            DataType::VarChar {
24468                length,
24469                parenthesized_length,
24470            } => {
24471                // Dialect-specific varchar type mappings
24472                match self.config.dialect {
24473                    Some(DialectType::Oracle) => {
24474                        self.write_keyword("VARCHAR2");
24475                        if let Some(n) = length {
24476                            self.write(&format!("({})", n));
24477                        }
24478                    }
24479                    Some(DialectType::DuckDB) => {
24480                        // DuckDB maps VARCHAR to TEXT, preserving length
24481                        self.write_keyword("TEXT");
24482                        if let Some(n) = length {
24483                            self.write(&format!("({})", n));
24484                        }
24485                    }
24486                    Some(DialectType::SQLite) => {
24487                        // SQLite maps VARCHAR to TEXT, preserving length
24488                        self.write_keyword("TEXT");
24489                        if let Some(n) = length {
24490                            self.write(&format!("({})", n));
24491                        }
24492                    }
24493                    Some(DialectType::MySQL) if length.is_none() => {
24494                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
24495                        self.write_keyword("TEXT");
24496                    }
24497                    Some(DialectType::Hive)
24498                    | Some(DialectType::Spark)
24499                    | Some(DialectType::Databricks)
24500                        if length.is_none() =>
24501                    {
24502                        // Hive/Spark/Databricks: VARCHAR without length → STRING
24503                        self.write_keyword("STRING");
24504                    }
24505                    _ => {
24506                        self.write_keyword("VARCHAR");
24507                        if let Some(n) = length {
24508                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
24509                            if *parenthesized_length {
24510                                self.write(&format!("(({}))", n));
24511                            } else {
24512                                self.write(&format!("({})", n));
24513                            }
24514                        }
24515                    }
24516                }
24517            }
24518            DataType::Text => {
24519                // Dialect-specific text type mappings
24520                match self.config.dialect {
24521                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
24522                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24523                        self.write_keyword("VARCHAR(MAX)")
24524                    }
24525                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
24526                    Some(DialectType::Snowflake)
24527                    | Some(DialectType::Dremio)
24528                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
24529                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
24530                    Some(DialectType::Presto)
24531                    | Some(DialectType::Trino)
24532                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
24533                    Some(DialectType::Spark)
24534                    | Some(DialectType::Databricks)
24535                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
24536                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
24537                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
24538                        self.write_keyword("STRING")
24539                    }
24540                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
24541                    _ => self.write_keyword("TEXT"),
24542                }
24543            }
24544            DataType::TextWithLength { length } => {
24545                // TEXT(n) - dialect-specific type with length
24546                match self.config.dialect {
24547                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
24548                    Some(DialectType::Hive)
24549                    | Some(DialectType::Spark)
24550                    | Some(DialectType::Databricks) => {
24551                        self.write(&format!("VARCHAR({})", length));
24552                    }
24553                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
24554                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
24555                    Some(DialectType::Snowflake)
24556                    | Some(DialectType::Presto)
24557                    | Some(DialectType::Trino)
24558                    | Some(DialectType::Athena)
24559                    | Some(DialectType::Drill)
24560                    | Some(DialectType::Dremio) => {
24561                        self.write(&format!("VARCHAR({})", length));
24562                    }
24563                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24564                        self.write(&format!("VARCHAR({})", length))
24565                    }
24566                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
24567                        self.write(&format!("STRING({})", length))
24568                    }
24569                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
24570                    _ => self.write(&format!("TEXT({})", length)),
24571                }
24572            }
24573            DataType::String { length } => {
24574                // STRING type with optional length (BigQuery STRING(n))
24575                match self.config.dialect {
24576                    Some(DialectType::ClickHouse) => {
24577                        // ClickHouse uses String with specific casing
24578                        self.write("String");
24579                        if let Some(n) = length {
24580                            self.write(&format!("({})", n));
24581                        }
24582                    }
24583                    Some(DialectType::BigQuery)
24584                    | Some(DialectType::Hive)
24585                    | Some(DialectType::Spark)
24586                    | Some(DialectType::Databricks)
24587                    | Some(DialectType::StarRocks)
24588                    | Some(DialectType::Doris) => {
24589                        self.write_keyword("STRING");
24590                        if let Some(n) = length {
24591                            self.write(&format!("({})", n));
24592                        }
24593                    }
24594                    Some(DialectType::PostgreSQL) => {
24595                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
24596                        if let Some(n) = length {
24597                            self.write_keyword("VARCHAR");
24598                            self.write(&format!("({})", n));
24599                        } else {
24600                            self.write_keyword("TEXT");
24601                        }
24602                    }
24603                    Some(DialectType::Redshift) => {
24604                        // Redshift: STRING -> VARCHAR(MAX)
24605                        if let Some(n) = length {
24606                            self.write_keyword("VARCHAR");
24607                            self.write(&format!("({})", n));
24608                        } else {
24609                            self.write_keyword("VARCHAR(MAX)");
24610                        }
24611                    }
24612                    Some(DialectType::MySQL) => {
24613                        // MySQL doesn't have STRING - use VARCHAR or TEXT
24614                        if let Some(n) = length {
24615                            self.write_keyword("VARCHAR");
24616                            self.write(&format!("({})", n));
24617                        } else {
24618                            self.write_keyword("TEXT");
24619                        }
24620                    }
24621                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24622                        // TSQL: STRING -> VARCHAR(MAX)
24623                        if let Some(n) = length {
24624                            self.write_keyword("VARCHAR");
24625                            self.write(&format!("({})", n));
24626                        } else {
24627                            self.write_keyword("VARCHAR(MAX)");
24628                        }
24629                    }
24630                    Some(DialectType::Oracle) => {
24631                        // Oracle: STRING -> CLOB
24632                        self.write_keyword("CLOB");
24633                    }
24634                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
24635                        // DuckDB/Materialize uses TEXT for string types
24636                        self.write_keyword("TEXT");
24637                        if let Some(n) = length {
24638                            self.write(&format!("({})", n));
24639                        }
24640                    }
24641                    Some(DialectType::Presto)
24642                    | Some(DialectType::Trino)
24643                    | Some(DialectType::Drill)
24644                    | Some(DialectType::Dremio) => {
24645                        // Presto/Trino/Drill use VARCHAR for string types
24646                        self.write_keyword("VARCHAR");
24647                        if let Some(n) = length {
24648                            self.write(&format!("({})", n));
24649                        }
24650                    }
24651                    Some(DialectType::Snowflake) => {
24652                        // Snowflake: STRING stays as STRING (identity/DDL)
24653                        // CAST context STRING -> VARCHAR is handled in generate_cast
24654                        self.write_keyword("STRING");
24655                        if let Some(n) = length {
24656                            self.write(&format!("({})", n));
24657                        }
24658                    }
24659                    _ => {
24660                        // Default: output STRING with optional length
24661                        self.write_keyword("STRING");
24662                        if let Some(n) = length {
24663                            self.write(&format!("({})", n));
24664                        }
24665                    }
24666                }
24667            }
24668            DataType::Binary { length } => {
24669                // Dialect-specific binary type mappings
24670                match self.config.dialect {
24671                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
24672                        self.write_keyword("BYTEA");
24673                        if let Some(n) = length {
24674                            self.write(&format!("({})", n));
24675                        }
24676                    }
24677                    Some(DialectType::Redshift) => {
24678                        self.write_keyword("VARBYTE");
24679                        if let Some(n) = length {
24680                            self.write(&format!("({})", n));
24681                        }
24682                    }
24683                    Some(DialectType::DuckDB)
24684                    | Some(DialectType::SQLite)
24685                    | Some(DialectType::Oracle) => {
24686                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
24687                        self.write_keyword("BLOB");
24688                        if let Some(n) = length {
24689                            self.write(&format!("({})", n));
24690                        }
24691                    }
24692                    Some(DialectType::Presto)
24693                    | Some(DialectType::Trino)
24694                    | Some(DialectType::Athena)
24695                    | Some(DialectType::Drill)
24696                    | Some(DialectType::Dremio) => {
24697                        // These dialects map BINARY to VARBINARY
24698                        self.write_keyword("VARBINARY");
24699                        if let Some(n) = length {
24700                            self.write(&format!("({})", n));
24701                        }
24702                    }
24703                    Some(DialectType::ClickHouse) => {
24704                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
24705                        if self.clickhouse_nullable_depth < 0 {
24706                            self.write("BINARY");
24707                        } else {
24708                            self.write("Nullable(BINARY");
24709                        }
24710                        if let Some(n) = length {
24711                            self.write(&format!("({})", n));
24712                        }
24713                        if self.clickhouse_nullable_depth >= 0 {
24714                            self.write(")");
24715                        }
24716                    }
24717                    _ => {
24718                        self.write_keyword("BINARY");
24719                        if let Some(n) = length {
24720                            self.write(&format!("({})", n));
24721                        }
24722                    }
24723                }
24724            }
24725            DataType::VarBinary { length } => {
24726                // Dialect-specific varbinary type mappings
24727                match self.config.dialect {
24728                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
24729                        self.write_keyword("BYTEA");
24730                        if let Some(n) = length {
24731                            self.write(&format!("({})", n));
24732                        }
24733                    }
24734                    Some(DialectType::Redshift) => {
24735                        self.write_keyword("VARBYTE");
24736                        if let Some(n) = length {
24737                            self.write(&format!("({})", n));
24738                        }
24739                    }
24740                    Some(DialectType::DuckDB)
24741                    | Some(DialectType::SQLite)
24742                    | Some(DialectType::Oracle) => {
24743                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
24744                        self.write_keyword("BLOB");
24745                        if let Some(n) = length {
24746                            self.write(&format!("({})", n));
24747                        }
24748                    }
24749                    Some(DialectType::Exasol) => {
24750                        // Exasol maps VARBINARY to VARCHAR
24751                        self.write_keyword("VARCHAR");
24752                    }
24753                    Some(DialectType::Spark)
24754                    | Some(DialectType::Hive)
24755                    | Some(DialectType::Databricks) => {
24756                        // Spark/Hive use BINARY instead of VARBINARY
24757                        self.write_keyword("BINARY");
24758                        if let Some(n) = length {
24759                            self.write(&format!("({})", n));
24760                        }
24761                    }
24762                    Some(DialectType::ClickHouse) => {
24763                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
24764                        self.write_clickhouse_type("String");
24765                    }
24766                    _ => {
24767                        self.write_keyword("VARBINARY");
24768                        if let Some(n) = length {
24769                            self.write(&format!("({})", n));
24770                        }
24771                    }
24772                }
24773            }
24774            DataType::Blob => {
24775                // Dialect-specific blob type mappings
24776                match self.config.dialect {
24777                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
24778                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
24779                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24780                        self.write_keyword("VARBINARY")
24781                    }
24782                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
24783                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
24784                    Some(DialectType::Presto)
24785                    | Some(DialectType::Trino)
24786                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
24787                    Some(DialectType::DuckDB) => {
24788                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
24789                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
24790                        self.write_keyword("VARBINARY");
24791                    }
24792                    Some(DialectType::Spark)
24793                    | Some(DialectType::Databricks)
24794                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
24795                    Some(DialectType::ClickHouse) => {
24796                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
24797                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
24798                        // This matches Python sqlglot behavior.
24799                        self.write("Nullable(String)");
24800                    }
24801                    _ => self.write_keyword("BLOB"),
24802                }
24803            }
24804            DataType::Bit { length } => {
24805                // Dialect-specific bit type mappings
24806                match self.config.dialect {
24807                    Some(DialectType::Dremio)
24808                    | Some(DialectType::Spark)
24809                    | Some(DialectType::Databricks)
24810                    | Some(DialectType::Hive)
24811                    | Some(DialectType::Snowflake)
24812                    | Some(DialectType::BigQuery)
24813                    | Some(DialectType::Presto)
24814                    | Some(DialectType::Trino)
24815                    | Some(DialectType::ClickHouse)
24816                    | Some(DialectType::Redshift) => {
24817                        // These dialects don't support BIT type, use BOOLEAN
24818                        self.write_keyword("BOOLEAN");
24819                    }
24820                    _ => {
24821                        self.write_keyword("BIT");
24822                        if let Some(n) = length {
24823                            self.write(&format!("({})", n));
24824                        }
24825                    }
24826                }
24827            }
24828            DataType::VarBit { length } => {
24829                self.write_keyword("VARBIT");
24830                if let Some(n) = length {
24831                    self.write(&format!("({})", n));
24832                }
24833            }
24834            DataType::Date => self.write_keyword("DATE"),
24835            DataType::Time {
24836                precision,
24837                timezone,
24838            } => {
24839                if *timezone {
24840                    // Dialect-specific TIME WITH TIME ZONE output
24841                    match self.config.dialect {
24842                        Some(DialectType::DuckDB) => {
24843                            // DuckDB: TIMETZ (drops precision)
24844                            self.write_keyword("TIMETZ");
24845                        }
24846                        Some(DialectType::PostgreSQL) => {
24847                            // PostgreSQL: TIMETZ or TIMETZ(p)
24848                            self.write_keyword("TIMETZ");
24849                            if let Some(p) = precision {
24850                                self.write(&format!("({})", p));
24851                            }
24852                        }
24853                        _ => {
24854                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
24855                            self.write_keyword("TIME");
24856                            if let Some(p) = precision {
24857                                self.write(&format!("({})", p));
24858                            }
24859                            self.write_keyword(" WITH TIME ZONE");
24860                        }
24861                    }
24862                } else {
24863                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
24864                    if matches!(
24865                        self.config.dialect,
24866                        Some(DialectType::Spark)
24867                            | Some(DialectType::Databricks)
24868                            | Some(DialectType::Hive)
24869                    ) {
24870                        self.write_keyword("TIMESTAMP");
24871                    } else {
24872                        self.write_keyword("TIME");
24873                        if let Some(p) = precision {
24874                            self.write(&format!("({})", p));
24875                        }
24876                    }
24877                }
24878            }
24879            DataType::Timestamp {
24880                precision,
24881                timezone,
24882            } => {
24883                // Dialect-specific timestamp type mappings
24884                match self.config.dialect {
24885                    Some(DialectType::Snowflake) if *timezone => {
24886                        self.write_keyword("TIMESTAMPTZ");
24887                        if let Some(p) = precision {
24888                            self.write(&format!("({})", p));
24889                        }
24890                    }
24891                    Some(DialectType::ClickHouse) => {
24892                        self.write("DateTime");
24893                        if let Some(p) = precision {
24894                            self.write(&format!("({})", p));
24895                        }
24896                    }
24897                    Some(DialectType::TSQL) => {
24898                        if *timezone {
24899                            self.write_keyword("DATETIMEOFFSET");
24900                        } else {
24901                            self.write_keyword("DATETIME2");
24902                        }
24903                        if let Some(p) = precision {
24904                            self.write(&format!("({})", p));
24905                        }
24906                    }
24907                    Some(DialectType::MySQL) => {
24908                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
24909                        self.write_keyword("TIMESTAMP");
24910                        if let Some(p) = precision {
24911                            self.write(&format!("({})", p));
24912                        }
24913                    }
24914                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
24915                        // Doris/StarRocks: TIMESTAMP -> DATETIME
24916                        self.write_keyword("DATETIME");
24917                        if let Some(p) = precision {
24918                            self.write(&format!("({})", p));
24919                        }
24920                    }
24921                    Some(DialectType::BigQuery) => {
24922                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
24923                        if *timezone {
24924                            self.write_keyword("TIMESTAMP");
24925                        } else {
24926                            self.write_keyword("DATETIME");
24927                        }
24928                    }
24929                    Some(DialectType::DuckDB) => {
24930                        // DuckDB: TIMESTAMPTZ shorthand
24931                        if *timezone {
24932                            self.write_keyword("TIMESTAMPTZ");
24933                        } else {
24934                            self.write_keyword("TIMESTAMP");
24935                            if let Some(p) = precision {
24936                                self.write(&format!("({})", p));
24937                            }
24938                        }
24939                    }
24940                    _ => {
24941                        if *timezone && !self.config.tz_to_with_time_zone {
24942                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
24943                            self.write_keyword("TIMESTAMPTZ");
24944                            if let Some(p) = precision {
24945                                self.write(&format!("({})", p));
24946                            }
24947                        } else {
24948                            self.write_keyword("TIMESTAMP");
24949                            if let Some(p) = precision {
24950                                self.write(&format!("({})", p));
24951                            }
24952                            if *timezone {
24953                                self.write_space();
24954                                self.write_keyword("WITH TIME ZONE");
24955                            }
24956                        }
24957                    }
24958                }
24959            }
24960            DataType::Interval { unit, to } => {
24961                self.write_keyword("INTERVAL");
24962                if let Some(u) = unit {
24963                    self.write_space();
24964                    self.write_keyword(u);
24965                }
24966                // Handle range intervals like DAY TO HOUR
24967                if let Some(t) = to {
24968                    self.write_space();
24969                    self.write_keyword("TO");
24970                    self.write_space();
24971                    self.write_keyword(t);
24972                }
24973            }
24974            DataType::Json => {
24975                // Dialect-specific JSON type mappings
24976                match self.config.dialect {
24977                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
24978                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
24979                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
24980                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
24981                    _ => self.write_keyword("JSON"),
24982                }
24983            }
24984            DataType::JsonB => {
24985                // JSONB is PostgreSQL specific, but Doris also supports it
24986                match self.config.dialect {
24987                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
24988                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
24989                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
24990                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
24991                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
24992                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
24993                }
24994            }
24995            DataType::Uuid => {
24996                // Dialect-specific UUID type mappings
24997                match self.config.dialect {
24998                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
24999                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
25000                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
25001                    Some(DialectType::BigQuery)
25002                    | Some(DialectType::Spark)
25003                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
25004                    _ => self.write_keyword("UUID"),
25005                }
25006            }
25007            DataType::Array {
25008                element_type,
25009                dimension,
25010            } => {
25011                // Dialect-specific array syntax
25012                match self.config.dialect {
25013                    Some(DialectType::PostgreSQL)
25014                    | Some(DialectType::Redshift)
25015                    | Some(DialectType::DuckDB) => {
25016                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
25017                        self.generate_data_type(element_type)?;
25018                        if let Some(dim) = dimension {
25019                            self.write(&format!("[{}]", dim));
25020                        } else {
25021                            self.write("[]");
25022                        }
25023                    }
25024                    Some(DialectType::BigQuery) => {
25025                        self.write_keyword("ARRAY<");
25026                        self.generate_data_type(element_type)?;
25027                        self.write(">");
25028                    }
25029                    Some(DialectType::Snowflake)
25030                    | Some(DialectType::Presto)
25031                    | Some(DialectType::Trino)
25032                    | Some(DialectType::ClickHouse) => {
25033                        // These dialects use Array(TYPE) parentheses syntax
25034                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
25035                            self.write("Array(");
25036                        } else {
25037                            self.write_keyword("ARRAY(");
25038                        }
25039                        self.generate_data_type(element_type)?;
25040                        self.write(")");
25041                    }
25042                    Some(DialectType::TSQL)
25043                    | Some(DialectType::MySQL)
25044                    | Some(DialectType::Oracle) => {
25045                        // These dialects don't have native array types
25046                        // Fall back to JSON or use native workarounds
25047                        match self.config.dialect {
25048                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
25049                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
25050                            _ => self.write_keyword("JSON"),
25051                        }
25052                    }
25053                    _ => {
25054                        // Default: use angle bracket syntax (ARRAY<T>)
25055                        self.write_keyword("ARRAY<");
25056                        self.generate_data_type(element_type)?;
25057                        self.write(">");
25058                    }
25059                }
25060            }
25061            DataType::List { element_type } => {
25062                // Materialize: element_type LIST (postfix syntax)
25063                self.generate_data_type(element_type)?;
25064                self.write_keyword(" LIST");
25065            }
25066            DataType::Map {
25067                key_type,
25068                value_type,
25069            } => {
25070                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
25071                match self.config.dialect {
25072                    Some(DialectType::Materialize) => {
25073                        // Materialize: MAP[key_type => value_type]
25074                        self.write_keyword("MAP[");
25075                        self.generate_data_type(key_type)?;
25076                        self.write(" => ");
25077                        self.generate_data_type(value_type)?;
25078                        self.write("]");
25079                    }
25080                    Some(DialectType::Snowflake)
25081                    | Some(DialectType::RisingWave)
25082                    | Some(DialectType::DuckDB)
25083                    | Some(DialectType::Presto)
25084                    | Some(DialectType::Trino)
25085                    | Some(DialectType::Athena) => {
25086                        self.write_keyword("MAP(");
25087                        self.generate_data_type(key_type)?;
25088                        self.write(", ");
25089                        self.generate_data_type(value_type)?;
25090                        self.write(")");
25091                    }
25092                    Some(DialectType::ClickHouse) => {
25093                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
25094                        // Key types must NOT be wrapped in Nullable
25095                        self.write("Map(");
25096                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
25097                        self.generate_data_type(key_type)?;
25098                        self.clickhouse_nullable_depth = 0;
25099                        self.write(", ");
25100                        self.generate_data_type(value_type)?;
25101                        self.write(")");
25102                    }
25103                    _ => {
25104                        self.write_keyword("MAP<");
25105                        self.generate_data_type(key_type)?;
25106                        self.write(", ");
25107                        self.generate_data_type(value_type)?;
25108                        self.write(">");
25109                    }
25110                }
25111            }
25112            DataType::Vector {
25113                element_type,
25114                dimension,
25115            } => {
25116                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
25117                    // SingleStore format: VECTOR(dimension, type_alias)
25118                    self.write_keyword("VECTOR(");
25119                    if let Some(dim) = dimension {
25120                        self.write(&dim.to_string());
25121                    }
25122                    // Map type back to SingleStore alias
25123                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
25124                        DataType::TinyInt { .. } => Some("I8"),
25125                        DataType::SmallInt { .. } => Some("I16"),
25126                        DataType::Int { .. } => Some("I32"),
25127                        DataType::BigInt { .. } => Some("I64"),
25128                        DataType::Float { .. } => Some("F32"),
25129                        DataType::Double { .. } => Some("F64"),
25130                        _ => None,
25131                    });
25132                    if let Some(alias) = type_alias {
25133                        if dimension.is_some() {
25134                            self.write(", ");
25135                        }
25136                        self.write(alias);
25137                    }
25138                    self.write(")");
25139                } else {
25140                    // Snowflake format: VECTOR(type, dimension)
25141                    self.write_keyword("VECTOR(");
25142                    if let Some(ref et) = element_type {
25143                        self.generate_data_type(et)?;
25144                        if dimension.is_some() {
25145                            self.write(", ");
25146                        }
25147                    }
25148                    if let Some(dim) = dimension {
25149                        self.write(&dim.to_string());
25150                    }
25151                    self.write(")");
25152                }
25153            }
25154            DataType::Object { fields, modifier } => {
25155                self.write_keyword("OBJECT(");
25156                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
25157                    if i > 0 {
25158                        self.write(", ");
25159                    }
25160                    self.write(name);
25161                    self.write(" ");
25162                    self.generate_data_type(dt)?;
25163                    if *not_null {
25164                        self.write_keyword(" NOT NULL");
25165                    }
25166                }
25167                self.write(")");
25168                if let Some(mod_str) = modifier {
25169                    self.write(" ");
25170                    self.write_keyword(mod_str);
25171                }
25172            }
25173            DataType::Struct { fields, nested } => {
25174                // Dialect-specific struct type mappings
25175                match self.config.dialect {
25176                    Some(DialectType::Snowflake) => {
25177                        // Snowflake maps STRUCT to OBJECT
25178                        self.write_keyword("OBJECT(");
25179                        for (i, field) in fields.iter().enumerate() {
25180                            if i > 0 {
25181                                self.write(", ");
25182                            }
25183                            if !field.name.is_empty() {
25184                                self.write(&field.name);
25185                                self.write(" ");
25186                            }
25187                            self.generate_data_type(&field.data_type)?;
25188                        }
25189                        self.write(")");
25190                    }
25191                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
25192                        // Presto/Trino use ROW(name TYPE, ...) syntax
25193                        self.write_keyword("ROW(");
25194                        for (i, field) in fields.iter().enumerate() {
25195                            if i > 0 {
25196                                self.write(", ");
25197                            }
25198                            if !field.name.is_empty() {
25199                                self.write(&field.name);
25200                                self.write(" ");
25201                            }
25202                            self.generate_data_type(&field.data_type)?;
25203                        }
25204                        self.write(")");
25205                    }
25206                    Some(DialectType::DuckDB) => {
25207                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
25208                        self.write_keyword("STRUCT(");
25209                        for (i, field) in fields.iter().enumerate() {
25210                            if i > 0 {
25211                                self.write(", ");
25212                            }
25213                            if !field.name.is_empty() {
25214                                self.write(&field.name);
25215                                self.write(" ");
25216                            }
25217                            self.generate_data_type(&field.data_type)?;
25218                        }
25219                        self.write(")");
25220                    }
25221                    Some(DialectType::ClickHouse) => {
25222                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
25223                        self.write("Tuple(");
25224                        for (i, field) in fields.iter().enumerate() {
25225                            if i > 0 {
25226                                self.write(", ");
25227                            }
25228                            if !field.name.is_empty() {
25229                                self.write(&field.name);
25230                                self.write(" ");
25231                            }
25232                            self.generate_data_type(&field.data_type)?;
25233                        }
25234                        self.write(")");
25235                    }
25236                    Some(DialectType::SingleStore) => {
25237                        // SingleStore uses RECORD(name TYPE, ...) for struct types
25238                        self.write_keyword("RECORD(");
25239                        for (i, field) in fields.iter().enumerate() {
25240                            if i > 0 {
25241                                self.write(", ");
25242                            }
25243                            if !field.name.is_empty() {
25244                                self.write(&field.name);
25245                                self.write(" ");
25246                            }
25247                            self.generate_data_type(&field.data_type)?;
25248                        }
25249                        self.write(")");
25250                    }
25251                    _ => {
25252                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
25253                        let force_angle_brackets = matches!(
25254                            self.config.dialect,
25255                            Some(DialectType::Hive)
25256                                | Some(DialectType::Spark)
25257                                | Some(DialectType::Databricks)
25258                        );
25259                        if *nested && !force_angle_brackets {
25260                            self.write_keyword("STRUCT(");
25261                            for (i, field) in fields.iter().enumerate() {
25262                                if i > 0 {
25263                                    self.write(", ");
25264                                }
25265                                if !field.name.is_empty() {
25266                                    self.write(&field.name);
25267                                    self.write(" ");
25268                                }
25269                                self.generate_data_type(&field.data_type)?;
25270                            }
25271                            self.write(")");
25272                        } else {
25273                            self.write_keyword("STRUCT<");
25274                            for (i, field) in fields.iter().enumerate() {
25275                                if i > 0 {
25276                                    self.write(", ");
25277                                }
25278                                if !field.name.is_empty() {
25279                                    // Named field: name TYPE (with configurable separator for Hive)
25280                                    self.write(&field.name);
25281                                    self.write(self.config.struct_field_sep);
25282                                }
25283                                // For anonymous fields, just output the type
25284                                self.generate_data_type(&field.data_type)?;
25285                                // Spark/Databricks: Output COMMENT clause if present
25286                                if let Some(comment) = &field.comment {
25287                                    self.write(" COMMENT '");
25288                                    self.write(comment);
25289                                    self.write("'");
25290                                }
25291                                // BigQuery: Output OPTIONS clause if present
25292                                if !field.options.is_empty() {
25293                                    self.write(" ");
25294                                    self.generate_options_clause(&field.options)?;
25295                                }
25296                            }
25297                            self.write(">");
25298                        }
25299                    }
25300                }
25301            }
25302            DataType::Enum {
25303                values,
25304                assignments,
25305            } => {
25306                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
25307                // ClickHouse: Enum('hello' = 1, 'world' = 2)
25308                if self.config.dialect == Some(DialectType::ClickHouse) {
25309                    self.write("Enum(");
25310                } else {
25311                    self.write_keyword("ENUM(");
25312                }
25313                for (i, val) in values.iter().enumerate() {
25314                    if i > 0 {
25315                        self.write(", ");
25316                    }
25317                    self.write("'");
25318                    self.write(val);
25319                    self.write("'");
25320                    if let Some(Some(assignment)) = assignments.get(i) {
25321                        self.write(" = ");
25322                        self.write(assignment);
25323                    }
25324                }
25325                self.write(")");
25326            }
25327            DataType::Set { values } => {
25328                // MySQL SET type: SET('a', 'b', 'c')
25329                self.write_keyword("SET(");
25330                for (i, val) in values.iter().enumerate() {
25331                    if i > 0 {
25332                        self.write(", ");
25333                    }
25334                    self.write("'");
25335                    self.write(val);
25336                    self.write("'");
25337                }
25338                self.write(")");
25339            }
25340            DataType::Union { fields } => {
25341                // DuckDB UNION type: UNION(num INT, str TEXT)
25342                self.write_keyword("UNION(");
25343                for (i, (name, dt)) in fields.iter().enumerate() {
25344                    if i > 0 {
25345                        self.write(", ");
25346                    }
25347                    if !name.is_empty() {
25348                        self.write(name);
25349                        self.write(" ");
25350                    }
25351                    self.generate_data_type(dt)?;
25352                }
25353                self.write(")");
25354            }
25355            DataType::Nullable { inner } => {
25356                // ClickHouse: Nullable(T), other dialects: just the inner type
25357                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
25358                    self.write("Nullable(");
25359                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
25360                    let saved_depth = self.clickhouse_nullable_depth;
25361                    self.clickhouse_nullable_depth = -1;
25362                    self.generate_data_type(inner)?;
25363                    self.clickhouse_nullable_depth = saved_depth;
25364                    self.write(")");
25365                } else {
25366                    // Map ClickHouse-specific custom type names to standard types
25367                    match inner.as_ref() {
25368                        DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME") => {
25369                            self.generate_data_type(&DataType::Timestamp {
25370                                precision: None,
25371                                timezone: false,
25372                            })?;
25373                        }
25374                        _ => {
25375                            self.generate_data_type(inner)?;
25376                        }
25377                    }
25378                }
25379            }
25380            DataType::Custom { name } => {
25381                // Handle dialect-specific type transformations
25382                let name_upper = name.to_ascii_uppercase();
25383                match self.config.dialect {
25384                    Some(DialectType::ClickHouse) => {
25385                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
25386                            (name_upper[..idx].to_string(), &name[idx..])
25387                        } else {
25388                            (name_upper.clone(), "")
25389                        };
25390                        let mapped = match base_upper.as_str() {
25391                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
25392                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
25393                            "DATETIME64" => "DateTime64",
25394                            "DATE32" => "Date32",
25395                            "INT" => "Int32",
25396                            "MEDIUMINT" => "Int32",
25397                            "INT8" => "Int8",
25398                            "INT16" => "Int16",
25399                            "INT32" => "Int32",
25400                            "INT64" => "Int64",
25401                            "INT128" => "Int128",
25402                            "INT256" => "Int256",
25403                            "UINT8" => "UInt8",
25404                            "UINT16" => "UInt16",
25405                            "UINT32" => "UInt32",
25406                            "UINT64" => "UInt64",
25407                            "UINT128" => "UInt128",
25408                            "UINT256" => "UInt256",
25409                            "FLOAT32" => "Float32",
25410                            "FLOAT64" => "Float64",
25411                            "DECIMAL32" => "Decimal32",
25412                            "DECIMAL64" => "Decimal64",
25413                            "DECIMAL128" => "Decimal128",
25414                            "DECIMAL256" => "Decimal256",
25415                            "ENUM" => "Enum",
25416                            "ENUM8" => "Enum8",
25417                            "ENUM16" => "Enum16",
25418                            "FIXEDSTRING" => "FixedString",
25419                            "NESTED" => "Nested",
25420                            "LOWCARDINALITY" => "LowCardinality",
25421                            "NULLABLE" => "Nullable",
25422                            "IPV4" => "IPv4",
25423                            "IPV6" => "IPv6",
25424                            "POINT" => "Point",
25425                            "RING" => "Ring",
25426                            "LINESTRING" => "LineString",
25427                            "MULTILINESTRING" => "MultiLineString",
25428                            "POLYGON" => "Polygon",
25429                            "MULTIPOLYGON" => "MultiPolygon",
25430                            "AGGREGATEFUNCTION" => "AggregateFunction",
25431                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
25432                            "DYNAMIC" => "Dynamic",
25433                            _ => "",
25434                        };
25435                        if mapped.is_empty() {
25436                            self.write(name);
25437                        } else {
25438                            self.write(mapped);
25439                            if matches!(base_upper.as_str(), "ENUM8" | "ENUM16")
25440                                && !suffix.is_empty()
25441                            {
25442                                let escaped_suffix = suffix
25443                                    .replace('\\', "\\\\")
25444                                    .replace('\t', "\\t")
25445                                    .replace('\n', "\\n")
25446                                    .replace('\r', "\\r");
25447                                self.write(&escaped_suffix);
25448                            } else {
25449                                self.write(suffix);
25450                            }
25451                        }
25452                    }
25453                    Some(DialectType::MySQL)
25454                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
25455                    {
25456                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
25457                        self.write_keyword("TIMESTAMP");
25458                    }
25459                    Some(DialectType::Snowflake) => {
25460                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
25461                            (name_upper[..idx].to_string(), &name[idx..])
25462                        } else {
25463                            (name_upper.clone(), "")
25464                        };
25465
25466                        match base_upper.as_str() {
25467                            "TIMESTAMPNTZ" | "TIMESTAMP_NTZ" => {
25468                                self.write_keyword("TIMESTAMPNTZ");
25469                                self.write(suffix);
25470                            }
25471                            "TIMESTAMPLTZ" | "TIMESTAMP_LTZ" => {
25472                                self.write_keyword("TIMESTAMPLTZ");
25473                                self.write(suffix);
25474                            }
25475                            "TIMESTAMPTZ" | "TIMESTAMP_TZ" => {
25476                                self.write_keyword("TIMESTAMPTZ");
25477                                self.write(suffix);
25478                            }
25479                            _ => self.write(name),
25480                        }
25481                    }
25482                    Some(DialectType::Fabric) => {
25483                        let (base_upper, args_str) = if let Some(idx) = name.find('(') {
25484                            (name_upper[..idx].to_string(), Some(&name[idx..]))
25485                        } else {
25486                            (name_upper.clone(), None)
25487                        };
25488
25489                        match base_upper.as_str() {
25490                            "NVARCHAR" => {
25491                                self.write_keyword("VARCHAR");
25492                                if let Some(args) = args_str {
25493                                    self.write(args);
25494                                }
25495                            }
25496                            "NCHAR" => {
25497                                self.write_keyword("CHAR");
25498                                if let Some(args) = args_str {
25499                                    self.write(args);
25500                                }
25501                            }
25502                            _ => self.write(name),
25503                        }
25504                    }
25505                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
25506                        self.write_keyword("SQL_VARIANT");
25507                    }
25508                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
25509                        self.write_keyword("DECIMAL(38, 5)");
25510                    }
25511                    Some(DialectType::Exasol) => {
25512                        // Exasol type mappings for custom types
25513                        match name_upper.as_str() {
25514                            // Binary types → VARCHAR
25515                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
25516                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
25517                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
25518                            // Integer types
25519                            "MEDIUMINT" => self.write_keyword("INT"),
25520                            // Decimal types → DECIMAL
25521                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
25522                                self.write_keyword("DECIMAL")
25523                            }
25524                            // Timestamp types
25525                            "DATETIME" => self.write_keyword("TIMESTAMP"),
25526                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
25527                            _ => self.write(name),
25528                        }
25529                    }
25530                    Some(DialectType::Dremio) => {
25531                        // Dremio type mappings for custom types
25532                        match name_upper.as_str() {
25533                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
25534                            "ARRAY" => self.write_keyword("LIST"),
25535                            "NCHAR" => self.write_keyword("VARCHAR"),
25536                            _ => self.write(name),
25537                        }
25538                    }
25539                    // Map dialect-specific custom types to standard SQL types for other dialects
25540                    _ => {
25541                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
25542                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
25543                            (name_upper[..idx].to_string(), Some(&name[idx..]))
25544                        } else {
25545                            (name_upper.clone(), None)
25546                        };
25547
25548                        match base_upper.as_str() {
25549                            "INT64"
25550                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
25551                            {
25552                                self.write_keyword("BIGINT");
25553                            }
25554                            "FLOAT64"
25555                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
25556                            {
25557                                self.write_keyword("DOUBLE");
25558                            }
25559                            "BOOL"
25560                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
25561                            {
25562                                self.write_keyword("BOOLEAN");
25563                            }
25564                            "BYTES"
25565                                if matches!(
25566                                    self.config.dialect,
25567                                    Some(DialectType::Spark)
25568                                        | Some(DialectType::Hive)
25569                                        | Some(DialectType::Databricks)
25570                                ) =>
25571                            {
25572                                self.write_keyword("BINARY");
25573                            }
25574                            "BYTES"
25575                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
25576                            {
25577                                self.write_keyword("VARBINARY");
25578                            }
25579                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
25580                            "DATETIME2" | "SMALLDATETIME"
25581                                if !matches!(
25582                                    self.config.dialect,
25583                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25584                                ) =>
25585                            {
25586                                // PostgreSQL preserves precision, others don't
25587                                if matches!(
25588                                    self.config.dialect,
25589                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
25590                                ) {
25591                                    self.write_keyword("TIMESTAMP");
25592                                    if let Some(args) = _args_str {
25593                                        self.write(args);
25594                                    }
25595                                } else {
25596                                    self.write_keyword("TIMESTAMP");
25597                                }
25598                            }
25599                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
25600                            "DATETIMEOFFSET"
25601                                if !matches!(
25602                                    self.config.dialect,
25603                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25604                                ) =>
25605                            {
25606                                if matches!(
25607                                    self.config.dialect,
25608                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
25609                                ) {
25610                                    self.write_keyword("TIMESTAMPTZ");
25611                                    if let Some(args) = _args_str {
25612                                        self.write(args);
25613                                    }
25614                                } else {
25615                                    self.write_keyword("TIMESTAMPTZ");
25616                                }
25617                            }
25618                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
25619                            "UNIQUEIDENTIFIER"
25620                                if !matches!(
25621                                    self.config.dialect,
25622                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25623                                ) =>
25624                            {
25625                                match self.config.dialect {
25626                                    Some(DialectType::Spark)
25627                                    | Some(DialectType::Databricks)
25628                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
25629                                    _ => self.write_keyword("UUID"),
25630                                }
25631                            }
25632                            // TSQL BIT -> BOOLEAN for most dialects
25633                            "BIT"
25634                                if !matches!(
25635                                    self.config.dialect,
25636                                    Some(DialectType::TSQL)
25637                                        | Some(DialectType::Fabric)
25638                                        | Some(DialectType::PostgreSQL)
25639                                        | Some(DialectType::MySQL)
25640                                        | Some(DialectType::DuckDB)
25641                                ) =>
25642                            {
25643                                self.write_keyword("BOOLEAN");
25644                            }
25645                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
25646                            "NVARCHAR"
25647                                if !matches!(
25648                                    self.config.dialect,
25649                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25650                                ) =>
25651                            {
25652                                match self.config.dialect {
25653                                    Some(DialectType::Oracle) => {
25654                                        // Oracle: NVARCHAR -> NVARCHAR2
25655                                        self.write_keyword("NVARCHAR2");
25656                                        if let Some(args) = _args_str {
25657                                            self.write(args);
25658                                        }
25659                                    }
25660                                    Some(DialectType::BigQuery) => {
25661                                        // BigQuery: NVARCHAR -> STRING
25662                                        self.write_keyword("STRING");
25663                                    }
25664                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
25665                                        self.write_keyword("TEXT");
25666                                        if let Some(args) = _args_str {
25667                                            self.write(args);
25668                                        }
25669                                    }
25670                                    Some(DialectType::Hive) => {
25671                                        // Hive: NVARCHAR -> STRING
25672                                        self.write_keyword("STRING");
25673                                    }
25674                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
25675                                        if _args_str.is_some() {
25676                                            self.write_keyword("VARCHAR");
25677                                            self.write(_args_str.unwrap());
25678                                        } else {
25679                                            self.write_keyword("STRING");
25680                                        }
25681                                    }
25682                                    _ => {
25683                                        self.write_keyword("VARCHAR");
25684                                        if let Some(args) = _args_str {
25685                                            self.write(args);
25686                                        }
25687                                    }
25688                                }
25689                            }
25690                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
25691                            "NCHAR"
25692                                if !matches!(
25693                                    self.config.dialect,
25694                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25695                                ) =>
25696                            {
25697                                match self.config.dialect {
25698                                    Some(DialectType::Oracle) => {
25699                                        // Oracle natively supports NCHAR
25700                                        self.write_keyword("NCHAR");
25701                                        if let Some(args) = _args_str {
25702                                            self.write(args);
25703                                        }
25704                                    }
25705                                    Some(DialectType::BigQuery) => {
25706                                        // BigQuery: NCHAR -> STRING
25707                                        self.write_keyword("STRING");
25708                                    }
25709                                    Some(DialectType::Hive) => {
25710                                        // Hive: NCHAR -> STRING
25711                                        self.write_keyword("STRING");
25712                                    }
25713                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
25714                                        self.write_keyword("TEXT");
25715                                        if let Some(args) = _args_str {
25716                                            self.write(args);
25717                                        }
25718                                    }
25719                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
25720                                        if _args_str.is_some() {
25721                                            self.write_keyword("CHAR");
25722                                            self.write(_args_str.unwrap());
25723                                        } else {
25724                                            self.write_keyword("STRING");
25725                                        }
25726                                    }
25727                                    _ => {
25728                                        self.write_keyword("CHAR");
25729                                        if let Some(args) = _args_str {
25730                                            self.write(args);
25731                                        }
25732                                    }
25733                                }
25734                            }
25735                            // MySQL text variant types -> map to appropriate target type
25736                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
25737                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
25738                                Some(DialectType::MySQL)
25739                                | Some(DialectType::SingleStore)
25740                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
25741                                Some(DialectType::Spark)
25742                                | Some(DialectType::Databricks)
25743                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
25744                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
25745                                Some(DialectType::Presto)
25746                                | Some(DialectType::Trino)
25747                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
25748                                Some(DialectType::Snowflake)
25749                                | Some(DialectType::Redshift)
25750                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
25751                                _ => self.write_keyword("TEXT"),
25752                            },
25753                            // MySQL blob variant types -> map to appropriate target type
25754                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
25755                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
25756                                Some(DialectType::MySQL)
25757                                | Some(DialectType::SingleStore)
25758                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
25759                                Some(DialectType::Spark)
25760                                | Some(DialectType::Databricks)
25761                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
25762                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
25763                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
25764                                Some(DialectType::Presto)
25765                                | Some(DialectType::Trino)
25766                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
25767                                Some(DialectType::Snowflake)
25768                                | Some(DialectType::Redshift)
25769                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
25770                                _ => self.write_keyword("BLOB"),
25771                            },
25772                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
25773                            "LONGVARCHAR" => match self.config.dialect {
25774                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
25775                                _ => self.write_keyword("VARCHAR"),
25776                            },
25777                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
25778                            "DATETIME" => {
25779                                match self.config.dialect {
25780                                    Some(DialectType::MySQL)
25781                                    | Some(DialectType::Doris)
25782                                    | Some(DialectType::StarRocks)
25783                                    | Some(DialectType::TSQL)
25784                                    | Some(DialectType::Fabric)
25785                                    | Some(DialectType::BigQuery)
25786                                    | Some(DialectType::SQLite)
25787                                    | Some(DialectType::Snowflake) => {
25788                                        self.write_keyword("DATETIME");
25789                                        if let Some(args) = _args_str {
25790                                            self.write(args);
25791                                        }
25792                                    }
25793                                    Some(_) => {
25794                                        // Only map to TIMESTAMP when we have a specific target dialect
25795                                        self.write_keyword("TIMESTAMP");
25796                                        if let Some(args) = _args_str {
25797                                            self.write(args);
25798                                        }
25799                                    }
25800                                    None => {
25801                                        // No dialect - preserve original
25802                                        self.write(name);
25803                                    }
25804                                }
25805                            }
25806                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
25807                            "VARCHAR2"
25808                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
25809                            {
25810                                match self.config.dialect {
25811                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
25812                                        self.write_keyword("TEXT");
25813                                    }
25814                                    Some(DialectType::Hive)
25815                                    | Some(DialectType::Spark)
25816                                    | Some(DialectType::Databricks)
25817                                    | Some(DialectType::BigQuery)
25818                                    | Some(DialectType::ClickHouse)
25819                                    | Some(DialectType::StarRocks)
25820                                    | Some(DialectType::Doris) => {
25821                                        self.write_keyword("STRING");
25822                                    }
25823                                    _ => {
25824                                        self.write_keyword("VARCHAR");
25825                                        if let Some(args) = _args_str {
25826                                            self.write(args);
25827                                        }
25828                                    }
25829                                }
25830                            }
25831                            "NVARCHAR2"
25832                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
25833                            {
25834                                match self.config.dialect {
25835                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
25836                                        self.write_keyword("TEXT");
25837                                    }
25838                                    Some(DialectType::Hive)
25839                                    | Some(DialectType::Spark)
25840                                    | Some(DialectType::Databricks)
25841                                    | Some(DialectType::BigQuery)
25842                                    | Some(DialectType::ClickHouse)
25843                                    | Some(DialectType::StarRocks)
25844                                    | Some(DialectType::Doris) => {
25845                                        self.write_keyword("STRING");
25846                                    }
25847                                    _ => {
25848                                        self.write_keyword("VARCHAR");
25849                                        if let Some(args) = _args_str {
25850                                            self.write(args);
25851                                        }
25852                                    }
25853                                }
25854                            }
25855                            _ => self.write(name),
25856                        }
25857                    }
25858                }
25859            }
25860            DataType::Geometry { subtype, srid } => {
25861                // Dialect-specific geometry type mappings
25862                match self.config.dialect {
25863                    Some(DialectType::MySQL) => {
25864                        // MySQL uses POINT SRID 4326 syntax for specific types
25865                        if let Some(sub) = subtype {
25866                            self.write_keyword(sub);
25867                            if let Some(s) = srid {
25868                                self.write(" SRID ");
25869                                self.write(&s.to_string());
25870                            }
25871                        } else {
25872                            self.write_keyword("GEOMETRY");
25873                        }
25874                    }
25875                    Some(DialectType::BigQuery) => {
25876                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
25877                        self.write_keyword("GEOGRAPHY");
25878                    }
25879                    Some(DialectType::Teradata) => {
25880                        // Teradata uses ST_GEOMETRY
25881                        self.write_keyword("ST_GEOMETRY");
25882                        if subtype.is_some() || srid.is_some() {
25883                            self.write("(");
25884                            if let Some(sub) = subtype {
25885                                self.write_keyword(sub);
25886                            }
25887                            if let Some(s) = srid {
25888                                if subtype.is_some() {
25889                                    self.write(", ");
25890                                }
25891                                self.write(&s.to_string());
25892                            }
25893                            self.write(")");
25894                        }
25895                    }
25896                    _ => {
25897                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
25898                        self.write_keyword("GEOMETRY");
25899                        if subtype.is_some() || srid.is_some() {
25900                            self.write("(");
25901                            if let Some(sub) = subtype {
25902                                self.write_keyword(sub);
25903                            }
25904                            if let Some(s) = srid {
25905                                if subtype.is_some() {
25906                                    self.write(", ");
25907                                }
25908                                self.write(&s.to_string());
25909                            }
25910                            self.write(")");
25911                        }
25912                    }
25913                }
25914            }
25915            DataType::Geography { subtype, srid } => {
25916                // Dialect-specific geography type mappings
25917                match self.config.dialect {
25918                    Some(DialectType::MySQL) => {
25919                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
25920                        if let Some(sub) = subtype {
25921                            self.write_keyword(sub);
25922                        } else {
25923                            self.write_keyword("GEOMETRY");
25924                        }
25925                        // Geography implies SRID 4326 (WGS84)
25926                        let effective_srid = srid.unwrap_or(4326);
25927                        self.write(" SRID ");
25928                        self.write(&effective_srid.to_string());
25929                    }
25930                    Some(DialectType::BigQuery) => {
25931                        // BigQuery uses simple GEOGRAPHY without parameters
25932                        self.write_keyword("GEOGRAPHY");
25933                    }
25934                    Some(DialectType::Snowflake) => {
25935                        // Snowflake uses GEOGRAPHY without parameters
25936                        self.write_keyword("GEOGRAPHY");
25937                    }
25938                    _ => {
25939                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
25940                        self.write_keyword("GEOGRAPHY");
25941                        if subtype.is_some() || srid.is_some() {
25942                            self.write("(");
25943                            if let Some(sub) = subtype {
25944                                self.write_keyword(sub);
25945                            }
25946                            if let Some(s) = srid {
25947                                if subtype.is_some() {
25948                                    self.write(", ");
25949                                }
25950                                self.write(&s.to_string());
25951                            }
25952                            self.write(")");
25953                        }
25954                    }
25955                }
25956            }
25957            DataType::CharacterSet { name } => {
25958                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
25959                self.write_keyword("CHAR CHARACTER SET ");
25960                self.write(name);
25961            }
25962            _ => self.write("UNKNOWN"),
25963        }
25964        Ok(())
25965    }
25966
25967    // === Helper methods ===
25968
25969    #[inline]
25970    fn write(&mut self, s: &str) {
25971        self.output.push_str(s);
25972    }
25973
25974    #[inline]
25975    fn write_space(&mut self) {
25976        self.output.push(' ');
25977    }
25978
25979    #[inline]
25980    fn write_keyword(&mut self, keyword: &str) {
25981        if self.config.uppercase_keywords {
25982            self.output.push_str(keyword);
25983        } else {
25984            for b in keyword.bytes() {
25985                self.output.push(b.to_ascii_lowercase() as char);
25986            }
25987        }
25988    }
25989
25990    /// Write a function name respecting the normalize_functions config setting
25991    fn write_func_name(&mut self, name: &str) {
25992        let normalized = self.normalize_func_name(name);
25993        self.output.push_str(normalized.as_ref());
25994    }
25995
25996    /// Convert strptime format string to Exasol format string
25997    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
25998    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
25999    fn convert_strptime_to_exasol_format(format: &str) -> String {
26000        let mut result = String::new();
26001        let chars: Vec<char> = format.chars().collect();
26002        let mut i = 0;
26003        while i < chars.len() {
26004            if chars[i] == '%' && i + 1 < chars.len() {
26005                let spec = chars[i + 1];
26006                let exasol_spec = match spec {
26007                    'Y' => "YYYY",
26008                    'y' => "YY",
26009                    'm' => "MM",
26010                    'd' => "DD",
26011                    'H' => "HH",
26012                    'M' => "MI",
26013                    'S' => "SS",
26014                    'a' => "DY",    // abbreviated weekday name
26015                    'A' => "DAY",   // full weekday name
26016                    'b' => "MON",   // abbreviated month name
26017                    'B' => "MONTH", // full month name
26018                    'I' => "H12",   // 12-hour format
26019                    'u' => "ID",    // ISO weekday (1-7)
26020                    'V' => "IW",    // ISO week number
26021                    'G' => "IYYY",  // ISO year
26022                    'W' => "UW",    // Week number (Monday as first day)
26023                    'U' => "UW",    // Week number (Sunday as first day)
26024                    'z' => "Z",     // timezone offset
26025                    _ => {
26026                        // Unknown specifier, keep as-is
26027                        result.push('%');
26028                        result.push(spec);
26029                        i += 2;
26030                        continue;
26031                    }
26032                };
26033                result.push_str(exasol_spec);
26034                i += 2;
26035            } else {
26036                result.push(chars[i]);
26037                i += 1;
26038            }
26039        }
26040        result
26041    }
26042
26043    /// Convert strptime format string to PostgreSQL/Redshift format string
26044    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
26045    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
26046    fn convert_strptime_to_postgres_format(format: &str) -> String {
26047        let mut result = String::new();
26048        let chars: Vec<char> = format.chars().collect();
26049        let mut i = 0;
26050        while i < chars.len() {
26051            if chars[i] == '%' && i + 1 < chars.len() {
26052                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
26053                if chars[i + 1] == '-' && i + 2 < chars.len() {
26054                    let spec = chars[i + 2];
26055                    let pg_spec = match spec {
26056                        'd' => "FMDD",
26057                        'm' => "FMMM",
26058                        'H' => "FMHH24",
26059                        'M' => "FMMI",
26060                        'S' => "FMSS",
26061                        _ => {
26062                            result.push('%');
26063                            result.push('-');
26064                            result.push(spec);
26065                            i += 3;
26066                            continue;
26067                        }
26068                    };
26069                    result.push_str(pg_spec);
26070                    i += 3;
26071                    continue;
26072                }
26073                let spec = chars[i + 1];
26074                let pg_spec = match spec {
26075                    'Y' => "YYYY",
26076                    'y' => "YY",
26077                    'm' => "MM",
26078                    'd' => "DD",
26079                    'H' => "HH24",
26080                    'I' => "HH12",
26081                    'M' => "MI",
26082                    'S' => "SS",
26083                    'f' => "US",      // microseconds
26084                    'u' => "D",       // day of week (1=Monday)
26085                    'j' => "DDD",     // day of year
26086                    'z' => "OF",      // UTC offset
26087                    'Z' => "TZ",      // timezone name
26088                    'A' => "TMDay",   // full weekday name
26089                    'a' => "TMDy",    // abbreviated weekday name
26090                    'b' => "TMMon",   // abbreviated month name
26091                    'B' => "TMMonth", // full month name
26092                    'U' => "WW",      // week number
26093                    _ => {
26094                        // Unknown specifier, keep as-is
26095                        result.push('%');
26096                        result.push(spec);
26097                        i += 2;
26098                        continue;
26099                    }
26100                };
26101                result.push_str(pg_spec);
26102                i += 2;
26103            } else {
26104                result.push(chars[i]);
26105                i += 1;
26106            }
26107        }
26108        result
26109    }
26110
26111    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
26112    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
26113        if self.config.limit_only_literals {
26114            if let Some(value) = Self::try_evaluate_constant(expr) {
26115                self.write(&value.to_string());
26116                return Ok(());
26117            }
26118        }
26119        self.generate_expression(expr)
26120    }
26121
26122    /// Format a comment with proper spacing.
26123    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
26124    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
26125    fn write_formatted_comment(&mut self, comment: &str) {
26126        // Normalize all comments to block comment format /* ... */
26127        // This matches Python sqlglot behavior which always outputs block comments
26128        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
26129            // Already block comment - extract inner content
26130            // Preserve internal whitespace, but ensure at least one space padding
26131            &comment[2..comment.len() - 2]
26132        } else if comment.starts_with("--") {
26133            // Line comment - extract content after --
26134            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
26135            &comment[2..]
26136        } else {
26137            // Raw content (no delimiters)
26138            comment
26139        };
26140        // Skip empty comments (e.g., bare "--" with no content)
26141        if content.trim().is_empty() {
26142            return;
26143        }
26144        // Escape nested block comment markers to prevent premature closure or unintended nesting.
26145        // This matches Python sqlglot's sanitize_comment behavior.
26146        let sanitized = content.replace("*/", "* /").replace("/*", "/ *");
26147        let content = &sanitized;
26148        // Ensure at least one space after /* and before */
26149        self.output.push_str("/*");
26150        if !content.starts_with(' ') {
26151            self.output.push(' ');
26152        }
26153        self.output.push_str(content);
26154        if !content.ends_with(' ') {
26155            self.output.push(' ');
26156        }
26157        self.output.push_str("*/");
26158    }
26159
26160    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
26161    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
26162    fn escape_block_for_single_quote(&self, block: &str) -> String {
26163        let escape_backslash = matches!(
26164            self.config.dialect,
26165            Some(crate::dialects::DialectType::Snowflake)
26166        );
26167        let mut escaped = String::with_capacity(block.len() + 4);
26168        for ch in block.chars() {
26169            if ch == '\'' {
26170                escaped.push('\\');
26171                escaped.push('\'');
26172            } else if escape_backslash && ch == '\\' {
26173                escaped.push('\\');
26174                escaped.push('\\');
26175            } else {
26176                escaped.push(ch);
26177            }
26178        }
26179        escaped
26180    }
26181
26182    fn write_newline(&mut self) {
26183        self.output.push('\n');
26184    }
26185
26186    fn write_indent(&mut self) {
26187        for _ in 0..self.indent_level {
26188            self.output.push_str(self.config.indent);
26189        }
26190    }
26191
26192    // === SQLGlot-style pretty printing helpers ===
26193
26194    /// Returns the separator string for pretty printing.
26195    /// Check if the total length of arguments exceeds max_text_width.
26196    /// Used for dynamic line breaking in expressions() formatting.
26197    fn too_wide(&self, args: &[String]) -> bool {
26198        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
26199    }
26200
26201    /// Generate an expression to a string using a temporary non-pretty generator.
26202    /// Useful for width calculations before deciding on formatting.
26203    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
26204        let config = GeneratorConfig {
26205            pretty: false,
26206            dialect: self.config.dialect,
26207            ..Default::default()
26208        };
26209        let mut gen = Generator::with_config(config);
26210        gen.generate_expression(expr)?;
26211        Ok(gen.output)
26212    }
26213
26214    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
26215    /// In pretty mode: newline + indented keyword + newline + indented condition
26216    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
26217        if self.config.pretty {
26218            self.write_newline();
26219            self.write_indent();
26220            self.write_keyword(keyword);
26221            self.write_newline();
26222            self.indent_level += 1;
26223            self.write_indent();
26224            self.generate_expression(condition)?;
26225            self.indent_level -= 1;
26226        } else {
26227            self.write_space();
26228            self.write_keyword(keyword);
26229            self.write_space();
26230            self.generate_expression(condition)?;
26231        }
26232        Ok(())
26233    }
26234
26235    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
26236    /// In pretty mode: each expression on new line with indentation
26237    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
26238        if exprs.is_empty() {
26239            return Ok(());
26240        }
26241
26242        if self.config.pretty {
26243            self.write_newline();
26244            self.write_indent();
26245            self.write_keyword(keyword);
26246            self.write_newline();
26247            self.indent_level += 1;
26248            for (i, expr) in exprs.iter().enumerate() {
26249                if i > 0 {
26250                    self.write(",");
26251                    self.write_newline();
26252                }
26253                self.write_indent();
26254                self.generate_expression(expr)?;
26255            }
26256            self.indent_level -= 1;
26257        } else {
26258            self.write_space();
26259            self.write_keyword(keyword);
26260            self.write_space();
26261            for (i, expr) in exprs.iter().enumerate() {
26262                if i > 0 {
26263                    self.write(", ");
26264                }
26265                self.generate_expression(expr)?;
26266            }
26267        }
26268        Ok(())
26269    }
26270
26271    /// Writes ORDER BY / SORT BY clause with Ordered expressions
26272    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
26273        if orderings.is_empty() {
26274            return Ok(());
26275        }
26276
26277        if self.config.pretty {
26278            self.write_newline();
26279            self.write_indent();
26280            self.write_keyword(keyword);
26281            self.write_newline();
26282            self.indent_level += 1;
26283            for (i, ordered) in orderings.iter().enumerate() {
26284                if i > 0 {
26285                    self.write(",");
26286                    self.write_newline();
26287                }
26288                self.write_indent();
26289                self.generate_ordered(ordered)?;
26290            }
26291            self.indent_level -= 1;
26292        } else {
26293            self.write_space();
26294            self.write_keyword(keyword);
26295            self.write_space();
26296            for (i, ordered) in orderings.iter().enumerate() {
26297                if i > 0 {
26298                    self.write(", ");
26299                }
26300                self.generate_ordered(ordered)?;
26301            }
26302        }
26303        Ok(())
26304    }
26305
26306    /// Writes WINDOW clause with named window definitions
26307    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
26308        if windows.is_empty() {
26309            return Ok(());
26310        }
26311
26312        if self.config.pretty {
26313            self.write_newline();
26314            self.write_indent();
26315            self.write_keyword("WINDOW");
26316            self.write_newline();
26317            self.indent_level += 1;
26318            for (i, named_window) in windows.iter().enumerate() {
26319                if i > 0 {
26320                    self.write(",");
26321                    self.write_newline();
26322                }
26323                self.write_indent();
26324                self.generate_identifier(&named_window.name)?;
26325                self.write_space();
26326                self.write_keyword("AS");
26327                self.write(" (");
26328                self.generate_over(&named_window.spec)?;
26329                self.write(")");
26330            }
26331            self.indent_level -= 1;
26332        } else {
26333            self.write_space();
26334            self.write_keyword("WINDOW");
26335            self.write_space();
26336            for (i, named_window) in windows.iter().enumerate() {
26337                if i > 0 {
26338                    self.write(", ");
26339                }
26340                self.generate_identifier(&named_window.name)?;
26341                self.write_space();
26342                self.write_keyword("AS");
26343                self.write(" (");
26344                self.generate_over(&named_window.spec)?;
26345                self.write(")");
26346            }
26347        }
26348        Ok(())
26349    }
26350
26351    // === BATCH-GENERATED STUB METHODS (481 variants) ===
26352    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
26353        // AI_AGG(this, expression)
26354        self.write_keyword("AI_AGG");
26355        self.write("(");
26356        self.generate_expression(&e.this)?;
26357        self.write(", ");
26358        self.generate_expression(&e.expression)?;
26359        self.write(")");
26360        Ok(())
26361    }
26362
26363    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
26364        // AI_CLASSIFY(input, [categories], [config])
26365        self.write_keyword("AI_CLASSIFY");
26366        self.write("(");
26367        self.generate_expression(&e.this)?;
26368        if let Some(categories) = &e.categories {
26369            self.write(", ");
26370            self.generate_expression(categories)?;
26371        }
26372        if let Some(config) = &e.config {
26373            self.write(", ");
26374            self.generate_expression(config)?;
26375        }
26376        self.write(")");
26377        Ok(())
26378    }
26379
26380    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
26381        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
26382        self.write_keyword("ADD");
26383        self.write_space();
26384        if e.exists {
26385            self.write_keyword("IF NOT EXISTS");
26386            self.write_space();
26387        }
26388        self.generate_expression(&e.this)?;
26389        if let Some(location) = &e.location {
26390            self.write_space();
26391            self.generate_expression(location)?;
26392        }
26393        Ok(())
26394    }
26395
26396    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
26397        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
26398        self.write_keyword("ALGORITHM");
26399        self.write("=");
26400        self.generate_expression(&e.this)?;
26401        Ok(())
26402    }
26403
26404    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
26405        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
26406        self.generate_expression(&e.this)?;
26407        self.write_space();
26408        self.write_keyword("AS");
26409        self.write(" (");
26410        for (i, expr) in e.expressions.iter().enumerate() {
26411            if i > 0 {
26412                self.write(", ");
26413            }
26414            self.generate_expression(expr)?;
26415        }
26416        self.write(")");
26417        Ok(())
26418    }
26419
26420    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
26421        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
26422        self.write_keyword("ALLOWED_VALUES");
26423        self.write_space();
26424        for (i, expr) in e.expressions.iter().enumerate() {
26425            if i > 0 {
26426                self.write(", ");
26427            }
26428            self.generate_expression(expr)?;
26429        }
26430        Ok(())
26431    }
26432
26433    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
26434        // Python: complex logic based on dtype, default, comment, visible, etc.
26435        self.write_keyword("ALTER COLUMN");
26436        self.write_space();
26437        self.generate_expression(&e.this)?;
26438
26439        if let Some(dtype) = &e.dtype {
26440            self.write_space();
26441            self.write_keyword("SET DATA TYPE");
26442            self.write_space();
26443            self.generate_expression(dtype)?;
26444            if let Some(collate) = &e.collate {
26445                self.write_space();
26446                self.write_keyword("COLLATE");
26447                self.write_space();
26448                self.generate_expression(collate)?;
26449            }
26450            if let Some(using) = &e.using {
26451                self.write_space();
26452                self.write_keyword("USING");
26453                self.write_space();
26454                self.generate_expression(using)?;
26455            }
26456        } else if let Some(default) = &e.default {
26457            self.write_space();
26458            self.write_keyword("SET DEFAULT");
26459            self.write_space();
26460            self.generate_expression(default)?;
26461        } else if let Some(comment) = &e.comment {
26462            self.write_space();
26463            self.write_keyword("COMMENT");
26464            self.write_space();
26465            self.generate_expression(comment)?;
26466        } else if let Some(drop) = &e.drop {
26467            self.write_space();
26468            self.write_keyword("DROP");
26469            self.write_space();
26470            self.generate_expression(drop)?;
26471        } else if let Some(visible) = &e.visible {
26472            self.write_space();
26473            self.generate_expression(visible)?;
26474        } else if let Some(rename_to) = &e.rename_to {
26475            self.write_space();
26476            self.write_keyword("RENAME TO");
26477            self.write_space();
26478            self.generate_expression(rename_to)?;
26479        } else if let Some(allow_null) = &e.allow_null {
26480            self.write_space();
26481            self.generate_expression(allow_null)?;
26482        }
26483        Ok(())
26484    }
26485
26486    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
26487        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
26488        self.write_keyword("ALTER SESSION");
26489        self.write_space();
26490        if e.unset.is_some() {
26491            self.write_keyword("UNSET");
26492        } else {
26493            self.write_keyword("SET");
26494        }
26495        self.write_space();
26496        for (i, expr) in e.expressions.iter().enumerate() {
26497            if i > 0 {
26498                self.write(", ");
26499            }
26500            self.generate_expression(expr)?;
26501        }
26502        Ok(())
26503    }
26504
26505    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
26506        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
26507        self.write_keyword("SET");
26508
26509        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
26510        if let Some(opt) = &e.option {
26511            self.write_space();
26512            self.generate_expression(opt)?;
26513        }
26514
26515        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
26516        // Check if expressions look like property assignments
26517        if !e.expressions.is_empty() {
26518            // Check if this looks like property assignments (for SET PROPERTIES)
26519            let is_properties = e
26520                .expressions
26521                .iter()
26522                .any(|expr| matches!(expr, Expression::Eq(_)));
26523            if is_properties && e.option.is_none() {
26524                self.write_space();
26525                self.write_keyword("PROPERTIES");
26526            }
26527            self.write_space();
26528            for (i, expr) in e.expressions.iter().enumerate() {
26529                if i > 0 {
26530                    self.write(", ");
26531                }
26532                self.generate_expression(expr)?;
26533            }
26534        }
26535
26536        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
26537        if let Some(file_format) = &e.file_format {
26538            self.write(" ");
26539            self.write_keyword("STAGE_FILE_FORMAT");
26540            self.write(" = (");
26541            self.generate_space_separated_properties(file_format)?;
26542            self.write(")");
26543        }
26544
26545        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
26546        if let Some(copy_options) = &e.copy_options {
26547            self.write(" ");
26548            self.write_keyword("STAGE_COPY_OPTIONS");
26549            self.write(" = (");
26550            self.generate_space_separated_properties(copy_options)?;
26551            self.write(")");
26552        }
26553
26554        // Generate TAG ...
26555        if let Some(tag) = &e.tag {
26556            self.write(" ");
26557            self.write_keyword("TAG");
26558            self.write(" ");
26559            self.generate_expression(tag)?;
26560        }
26561
26562        Ok(())
26563    }
26564
26565    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
26566    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
26567        match expr {
26568            Expression::Tuple(t) => {
26569                for (i, prop) in t.expressions.iter().enumerate() {
26570                    if i > 0 {
26571                        self.write(" ");
26572                    }
26573                    self.generate_expression(prop)?;
26574                }
26575            }
26576            _ => {
26577                self.generate_expression(expr)?;
26578            }
26579        }
26580        Ok(())
26581    }
26582
26583    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
26584        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
26585        self.write_keyword("ALTER");
26586        if e.compound.is_some() {
26587            self.write_space();
26588            self.write_keyword("COMPOUND");
26589        }
26590        self.write_space();
26591        self.write_keyword("SORTKEY");
26592        self.write_space();
26593        if let Some(this) = &e.this {
26594            self.generate_expression(this)?;
26595        } else if !e.expressions.is_empty() {
26596            self.write("(");
26597            for (i, expr) in e.expressions.iter().enumerate() {
26598                if i > 0 {
26599                    self.write(", ");
26600                }
26601                self.generate_expression(expr)?;
26602            }
26603            self.write(")");
26604        }
26605        Ok(())
26606    }
26607
26608    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
26609        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
26610        self.write_keyword("ANALYZE");
26611        if !e.options.is_empty() {
26612            self.write_space();
26613            for (i, opt) in e.options.iter().enumerate() {
26614                if i > 0 {
26615                    self.write_space();
26616                }
26617                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
26618                if let Expression::Identifier(id) = opt {
26619                    self.write_keyword(&id.name);
26620                } else {
26621                    self.generate_expression(opt)?;
26622                }
26623            }
26624        }
26625        if let Some(kind) = &e.kind {
26626            self.write_space();
26627            self.write_keyword(kind);
26628        }
26629        if let Some(this) = &e.this {
26630            self.write_space();
26631            self.generate_expression(this)?;
26632        }
26633        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
26634        if !e.columns.is_empty() {
26635            self.write("(");
26636            for (i, col) in e.columns.iter().enumerate() {
26637                if i > 0 {
26638                    self.write(", ");
26639                }
26640                self.write(col);
26641            }
26642            self.write(")");
26643        }
26644        if let Some(partition) = &e.partition {
26645            self.write_space();
26646            self.generate_expression(partition)?;
26647        }
26648        if let Some(mode) = &e.mode {
26649            self.write_space();
26650            self.generate_expression(mode)?;
26651        }
26652        if let Some(expression) = &e.expression {
26653            self.write_space();
26654            self.generate_expression(expression)?;
26655        }
26656        if !e.properties.is_empty() {
26657            self.write_space();
26658            self.write_keyword(self.config.with_properties_prefix);
26659            self.write(" (");
26660            for (i, prop) in e.properties.iter().enumerate() {
26661                if i > 0 {
26662                    self.write(", ");
26663                }
26664                self.generate_expression(prop)?;
26665            }
26666            self.write(")");
26667        }
26668        Ok(())
26669    }
26670
26671    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
26672        // Python: return f"DELETE{kind} STATISTICS"
26673        self.write_keyword("DELETE");
26674        if let Some(kind) = &e.kind {
26675            self.write_space();
26676            self.write_keyword(kind);
26677        }
26678        self.write_space();
26679        self.write_keyword("STATISTICS");
26680        Ok(())
26681    }
26682
26683    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
26684        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
26685        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
26686        if let Expression::Identifier(id) = e.this.as_ref() {
26687            self.write_keyword(&id.name);
26688        } else {
26689            self.generate_expression(&e.this)?;
26690        }
26691        self.write_space();
26692        self.write_keyword("HISTOGRAM ON");
26693        self.write_space();
26694        for (i, expr) in e.expressions.iter().enumerate() {
26695            if i > 0 {
26696                self.write(", ");
26697            }
26698            self.generate_expression(expr)?;
26699        }
26700        if let Some(expression) = &e.expression {
26701            self.write_space();
26702            self.generate_expression(expression)?;
26703        }
26704        if let Some(update_options) = &e.update_options {
26705            self.write_space();
26706            self.generate_expression(update_options)?;
26707            self.write_space();
26708            self.write_keyword("UPDATE");
26709        }
26710        Ok(())
26711    }
26712
26713    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
26714        // Python: return f"LIST CHAINED ROWS{inner_expression}"
26715        self.write_keyword("LIST CHAINED ROWS");
26716        if let Some(expression) = &e.expression {
26717            self.write_space();
26718            self.write_keyword("INTO");
26719            self.write_space();
26720            self.generate_expression(expression)?;
26721        }
26722        Ok(())
26723    }
26724
26725    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
26726        // Python: return f"SAMPLE {sample} {kind}"
26727        self.write_keyword("SAMPLE");
26728        self.write_space();
26729        if let Some(sample) = &e.sample {
26730            self.generate_expression(sample)?;
26731            self.write_space();
26732        }
26733        self.write_keyword(&e.kind);
26734        Ok(())
26735    }
26736
26737    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
26738        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
26739        self.write_keyword(&e.kind);
26740        if let Some(option) = &e.option {
26741            self.write_space();
26742            self.generate_expression(option)?;
26743        }
26744        self.write_space();
26745        self.write_keyword("STATISTICS");
26746        if let Some(this) = &e.this {
26747            self.write_space();
26748            self.generate_expression(this)?;
26749        }
26750        if !e.expressions.is_empty() {
26751            self.write_space();
26752            for (i, expr) in e.expressions.iter().enumerate() {
26753                if i > 0 {
26754                    self.write(", ");
26755                }
26756                self.generate_expression(expr)?;
26757            }
26758        }
26759        Ok(())
26760    }
26761
26762    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
26763        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
26764        self.write_keyword("VALIDATE");
26765        self.write_space();
26766        self.write_keyword(&e.kind);
26767        if let Some(this) = &e.this {
26768            self.write_space();
26769            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
26770            if let Expression::Identifier(id) = this.as_ref() {
26771                self.write_keyword(&id.name);
26772            } else {
26773                self.generate_expression(this)?;
26774            }
26775        }
26776        if let Some(expression) = &e.expression {
26777            self.write_space();
26778            self.write_keyword("INTO");
26779            self.write_space();
26780            self.generate_expression(expression)?;
26781        }
26782        Ok(())
26783    }
26784
26785    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
26786        // Python: return f"WITH {expressions}"
26787        self.write_keyword("WITH");
26788        self.write_space();
26789        for (i, expr) in e.expressions.iter().enumerate() {
26790            if i > 0 {
26791                self.write(", ");
26792            }
26793            self.generate_expression(expr)?;
26794        }
26795        Ok(())
26796    }
26797
26798    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
26799        // Anonymous represents a generic function call: FUNC_NAME(args...)
26800        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
26801        self.generate_expression(&e.this)?;
26802        self.write("(");
26803        for (i, arg) in e.expressions.iter().enumerate() {
26804            if i > 0 {
26805                self.write(", ");
26806            }
26807            self.generate_expression(arg)?;
26808        }
26809        self.write(")");
26810        Ok(())
26811    }
26812
26813    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
26814        // Same as Anonymous but for aggregate functions
26815        self.generate_expression(&e.this)?;
26816        self.write("(");
26817        for (i, arg) in e.expressions.iter().enumerate() {
26818            if i > 0 {
26819                self.write(", ");
26820            }
26821            self.generate_expression(arg)?;
26822        }
26823        self.write(")");
26824        Ok(())
26825    }
26826
26827    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
26828        // Python: return f"{this} APPLY({expr})"
26829        self.generate_expression(&e.this)?;
26830        self.write_space();
26831        self.write_keyword("APPLY");
26832        self.write("(");
26833        self.generate_expression(&e.expression)?;
26834        self.write(")");
26835        Ok(())
26836    }
26837
26838    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
26839        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
26840        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
26841        self.write("(");
26842        self.generate_expression(&e.this)?;
26843        if let Some(percentile) = &e.percentile {
26844            self.write(", ");
26845            self.generate_expression(percentile)?;
26846        }
26847        self.write(")");
26848        Ok(())
26849    }
26850
26851    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
26852        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
26853        self.write_keyword("APPROX_QUANTILE");
26854        self.write("(");
26855        self.generate_expression(&e.this)?;
26856        if let Some(quantile) = &e.quantile {
26857            self.write(", ");
26858            self.generate_expression(quantile)?;
26859        }
26860        if let Some(accuracy) = &e.accuracy {
26861            self.write(", ");
26862            self.generate_expression(accuracy)?;
26863        }
26864        if let Some(weight) = &e.weight {
26865            self.write(", ");
26866            self.generate_expression(weight)?;
26867        }
26868        self.write(")");
26869        Ok(())
26870    }
26871
26872    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
26873        // APPROX_QUANTILES(this, expression)
26874        self.write_keyword("APPROX_QUANTILES");
26875        self.write("(");
26876        self.generate_expression(&e.this)?;
26877        if let Some(expression) = &e.expression {
26878            self.write(", ");
26879            self.generate_expression(expression)?;
26880        }
26881        self.write(")");
26882        Ok(())
26883    }
26884
26885    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
26886        // APPROX_TOP_K(this[, expression][, counters])
26887        self.write_keyword("APPROX_TOP_K");
26888        self.write("(");
26889        self.generate_expression(&e.this)?;
26890        if let Some(expression) = &e.expression {
26891            self.write(", ");
26892            self.generate_expression(expression)?;
26893        }
26894        if let Some(counters) = &e.counters {
26895            self.write(", ");
26896            self.generate_expression(counters)?;
26897        }
26898        self.write(")");
26899        Ok(())
26900    }
26901
26902    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
26903        // APPROX_TOP_K_ACCUMULATE(this[, expression])
26904        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
26905        self.write("(");
26906        self.generate_expression(&e.this)?;
26907        if let Some(expression) = &e.expression {
26908            self.write(", ");
26909            self.generate_expression(expression)?;
26910        }
26911        self.write(")");
26912        Ok(())
26913    }
26914
26915    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
26916        // APPROX_TOP_K_COMBINE(this[, expression])
26917        self.write_keyword("APPROX_TOP_K_COMBINE");
26918        self.write("(");
26919        self.generate_expression(&e.this)?;
26920        if let Some(expression) = &e.expression {
26921            self.write(", ");
26922            self.generate_expression(expression)?;
26923        }
26924        self.write(")");
26925        Ok(())
26926    }
26927
26928    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
26929        // APPROX_TOP_K_ESTIMATE(this[, expression])
26930        self.write_keyword("APPROX_TOP_K_ESTIMATE");
26931        self.write("(");
26932        self.generate_expression(&e.this)?;
26933        if let Some(expression) = &e.expression {
26934            self.write(", ");
26935            self.generate_expression(expression)?;
26936        }
26937        self.write(")");
26938        Ok(())
26939    }
26940
26941    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
26942        // APPROX_TOP_SUM(this, expression[, count])
26943        self.write_keyword("APPROX_TOP_SUM");
26944        self.write("(");
26945        self.generate_expression(&e.this)?;
26946        self.write(", ");
26947        self.generate_expression(&e.expression)?;
26948        if let Some(count) = &e.count {
26949            self.write(", ");
26950            self.generate_expression(count)?;
26951        }
26952        self.write(")");
26953        Ok(())
26954    }
26955
26956    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
26957        // ARG_MAX(this, expression[, count])
26958        self.write_keyword("ARG_MAX");
26959        self.write("(");
26960        self.generate_expression(&e.this)?;
26961        self.write(", ");
26962        self.generate_expression(&e.expression)?;
26963        if let Some(count) = &e.count {
26964            self.write(", ");
26965            self.generate_expression(count)?;
26966        }
26967        self.write(")");
26968        Ok(())
26969    }
26970
26971    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
26972        // ARG_MIN(this, expression[, count])
26973        self.write_keyword("ARG_MIN");
26974        self.write("(");
26975        self.generate_expression(&e.this)?;
26976        self.write(", ");
26977        self.generate_expression(&e.expression)?;
26978        if let Some(count) = &e.count {
26979            self.write(", ");
26980            self.generate_expression(count)?;
26981        }
26982        self.write(")");
26983        Ok(())
26984    }
26985
26986    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
26987        // ARRAY_ALL(this, expression)
26988        self.write_keyword("ARRAY_ALL");
26989        self.write("(");
26990        self.generate_expression(&e.this)?;
26991        self.write(", ");
26992        self.generate_expression(&e.expression)?;
26993        self.write(")");
26994        Ok(())
26995    }
26996
26997    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
26998        // ARRAY_ANY(this, expression) - fallback implementation
26999        self.write_keyword("ARRAY_ANY");
27000        self.write("(");
27001        self.generate_expression(&e.this)?;
27002        self.write(", ");
27003        self.generate_expression(&e.expression)?;
27004        self.write(")");
27005        Ok(())
27006    }
27007
27008    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
27009        // ARRAY_CONSTRUCT_COMPACT(expressions...)
27010        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
27011        self.write("(");
27012        for (i, expr) in e.expressions.iter().enumerate() {
27013            if i > 0 {
27014                self.write(", ");
27015            }
27016            self.generate_expression(expr)?;
27017        }
27018        self.write(")");
27019        Ok(())
27020    }
27021
27022    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
27023        // ARRAY_SUM(this[, expression])
27024        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
27025            self.write("arraySum");
27026        } else {
27027            self.write_keyword("ARRAY_SUM");
27028        }
27029        self.write("(");
27030        self.generate_expression(&e.this)?;
27031        if let Some(expression) = &e.expression {
27032            self.write(", ");
27033            self.generate_expression(expression)?;
27034        }
27035        self.write(")");
27036        Ok(())
27037    }
27038
27039    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
27040        // Python: return f"{this} AT {index}"
27041        self.generate_expression(&e.this)?;
27042        self.write_space();
27043        self.write_keyword("AT");
27044        self.write_space();
27045        self.generate_expression(&e.expression)?;
27046        Ok(())
27047    }
27048
27049    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
27050        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
27051        self.write_keyword("ATTACH");
27052        if e.exists {
27053            self.write_space();
27054            self.write_keyword("IF NOT EXISTS");
27055        }
27056        self.write_space();
27057        self.generate_expression(&e.this)?;
27058        if !e.expressions.is_empty() {
27059            self.write(" (");
27060            for (i, expr) in e.expressions.iter().enumerate() {
27061                if i > 0 {
27062                    self.write(", ");
27063                }
27064                self.generate_expression(expr)?;
27065            }
27066            self.write(")");
27067        }
27068        Ok(())
27069    }
27070
27071    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
27072        // AttachOption: this [expression]
27073        // Python sqlglot: no equals sign, just space-separated
27074        self.generate_expression(&e.this)?;
27075        if let Some(expression) = &e.expression {
27076            self.write_space();
27077            self.generate_expression(expression)?;
27078        }
27079        Ok(())
27080    }
27081
27082    /// Generate the auto_increment keyword and options for a column definition.
27083    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
27084    /// GENERATED AS IDENTITY, etc.
27085    fn generate_auto_increment_keyword(
27086        &mut self,
27087        col: &crate::expressions::ColumnDef,
27088    ) -> Result<()> {
27089        use crate::dialects::DialectType;
27090        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
27091            self.write_keyword("IDENTITY");
27092            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
27093                self.write("(");
27094                if let Some(ref start) = col.auto_increment_start {
27095                    self.generate_expression(start)?;
27096                } else {
27097                    self.write("0");
27098                }
27099                self.write(", ");
27100                if let Some(ref inc) = col.auto_increment_increment {
27101                    self.generate_expression(inc)?;
27102                } else {
27103                    self.write("1");
27104                }
27105                self.write(")");
27106            }
27107        } else if matches!(
27108            self.config.dialect,
27109            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
27110        ) {
27111            self.write_keyword("AUTOINCREMENT");
27112            if let Some(ref start) = col.auto_increment_start {
27113                self.write_space();
27114                self.write_keyword("START");
27115                self.write_space();
27116                self.generate_expression(start)?;
27117            }
27118            if let Some(ref inc) = col.auto_increment_increment {
27119                self.write_space();
27120                self.write_keyword("INCREMENT");
27121                self.write_space();
27122                self.generate_expression(inc)?;
27123            }
27124            if let Some(order) = col.auto_increment_order {
27125                self.write_space();
27126                if order {
27127                    self.write_keyword("ORDER");
27128                } else {
27129                    self.write_keyword("NOORDER");
27130                }
27131            }
27132        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
27133            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
27134            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
27135                self.write(" (");
27136                let mut first = true;
27137                if let Some(ref start) = col.auto_increment_start {
27138                    self.write_keyword("START WITH");
27139                    self.write_space();
27140                    self.generate_expression(start)?;
27141                    first = false;
27142                }
27143                if let Some(ref inc) = col.auto_increment_increment {
27144                    if !first {
27145                        self.write_space();
27146                    }
27147                    self.write_keyword("INCREMENT BY");
27148                    self.write_space();
27149                    self.generate_expression(inc)?;
27150                }
27151                self.write(")");
27152            }
27153        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
27154            // IDENTITY(start, increment) -> GENERATED BY DEFAULT AS IDENTITY
27155            // Plain IDENTITY/AUTO_INCREMENT -> GENERATED ALWAYS AS IDENTITY
27156            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
27157                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
27158            } else {
27159                self.write_keyword("GENERATED ALWAYS AS IDENTITY");
27160            }
27161            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
27162                self.write(" (");
27163                let mut first = true;
27164                if let Some(ref start) = col.auto_increment_start {
27165                    self.write_keyword("START WITH");
27166                    self.write_space();
27167                    self.generate_expression(start)?;
27168                    first = false;
27169                }
27170                if let Some(ref inc) = col.auto_increment_increment {
27171                    if !first {
27172                        self.write_space();
27173                    }
27174                    self.write_keyword("INCREMENT BY");
27175                    self.write_space();
27176                    self.generate_expression(inc)?;
27177                }
27178                self.write(")");
27179            }
27180        } else if matches!(
27181            self.config.dialect,
27182            Some(DialectType::TSQL) | Some(DialectType::Fabric)
27183        ) {
27184            self.write_keyword("IDENTITY");
27185            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
27186                self.write("(");
27187                if let Some(ref start) = col.auto_increment_start {
27188                    self.generate_expression(start)?;
27189                } else {
27190                    self.write("0");
27191                }
27192                self.write(", ");
27193                if let Some(ref inc) = col.auto_increment_increment {
27194                    self.generate_expression(inc)?;
27195                } else {
27196                    self.write("1");
27197                }
27198                self.write(")");
27199            }
27200        } else {
27201            self.write_keyword("AUTO_INCREMENT");
27202            if let Some(ref start) = col.auto_increment_start {
27203                self.write_space();
27204                self.write_keyword("START");
27205                self.write_space();
27206                self.generate_expression(start)?;
27207            }
27208            if let Some(ref inc) = col.auto_increment_increment {
27209                self.write_space();
27210                self.write_keyword("INCREMENT");
27211                self.write_space();
27212                self.generate_expression(inc)?;
27213            }
27214            if let Some(order) = col.auto_increment_order {
27215                self.write_space();
27216                if order {
27217                    self.write_keyword("ORDER");
27218                } else {
27219                    self.write_keyword("NOORDER");
27220                }
27221            }
27222        }
27223        Ok(())
27224    }
27225
27226    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
27227        // AUTO_INCREMENT=value
27228        self.write_keyword("AUTO_INCREMENT");
27229        self.write("=");
27230        self.generate_expression(&e.this)?;
27231        Ok(())
27232    }
27233
27234    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
27235        // AUTO_REFRESH=value
27236        self.write_keyword("AUTO_REFRESH");
27237        self.write("=");
27238        self.generate_expression(&e.this)?;
27239        Ok(())
27240    }
27241
27242    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
27243        // BACKUP YES|NO (Redshift syntax uses space, not equals)
27244        self.write_keyword("BACKUP");
27245        self.write_space();
27246        self.generate_expression(&e.this)?;
27247        Ok(())
27248    }
27249
27250    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
27251        // BASE64_DECODE_BINARY(this[, alphabet])
27252        self.write_keyword("BASE64_DECODE_BINARY");
27253        self.write("(");
27254        self.generate_expression(&e.this)?;
27255        if let Some(alphabet) = &e.alphabet {
27256            self.write(", ");
27257            self.generate_expression(alphabet)?;
27258        }
27259        self.write(")");
27260        Ok(())
27261    }
27262
27263    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
27264        // BASE64_DECODE_STRING(this[, alphabet])
27265        self.write_keyword("BASE64_DECODE_STRING");
27266        self.write("(");
27267        self.generate_expression(&e.this)?;
27268        if let Some(alphabet) = &e.alphabet {
27269            self.write(", ");
27270            self.generate_expression(alphabet)?;
27271        }
27272        self.write(")");
27273        Ok(())
27274    }
27275
27276    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
27277        // BASE64_ENCODE(this[, max_line_length][, alphabet])
27278        self.write_keyword("BASE64_ENCODE");
27279        self.write("(");
27280        self.generate_expression(&e.this)?;
27281        if let Some(max_line_length) = &e.max_line_length {
27282            self.write(", ");
27283            self.generate_expression(max_line_length)?;
27284        }
27285        if let Some(alphabet) = &e.alphabet {
27286            self.write(", ");
27287            self.generate_expression(alphabet)?;
27288        }
27289        self.write(")");
27290        Ok(())
27291    }
27292
27293    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
27294        // BLOCKCOMPRESSION=... (complex Teradata property)
27295        self.write_keyword("BLOCKCOMPRESSION");
27296        self.write("=");
27297        if let Some(autotemp) = &e.autotemp {
27298            self.write_keyword("AUTOTEMP");
27299            self.write("(");
27300            self.generate_expression(autotemp)?;
27301            self.write(")");
27302        }
27303        if let Some(always) = &e.always {
27304            self.generate_expression(always)?;
27305        }
27306        if let Some(default) = &e.default {
27307            self.generate_expression(default)?;
27308        }
27309        if let Some(manual) = &e.manual {
27310            self.generate_expression(manual)?;
27311        }
27312        if let Some(never) = &e.never {
27313            self.generate_expression(never)?;
27314        }
27315        Ok(())
27316    }
27317
27318    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
27319        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
27320        self.write("((");
27321        self.generate_expression(&e.this)?;
27322        self.write(") ");
27323        self.write_keyword("AND");
27324        self.write(" (");
27325        self.generate_expression(&e.expression)?;
27326        self.write("))");
27327        Ok(())
27328    }
27329
27330    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
27331        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
27332        self.write("((");
27333        self.generate_expression(&e.this)?;
27334        self.write(") ");
27335        self.write_keyword("OR");
27336        self.write(" (");
27337        self.generate_expression(&e.expression)?;
27338        self.write("))");
27339        Ok(())
27340    }
27341
27342    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
27343        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
27344        self.write_keyword("BUILD");
27345        self.write_space();
27346        self.generate_expression(&e.this)?;
27347        Ok(())
27348    }
27349
27350    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
27351        // Byte string literal like B'...' or X'...'
27352        self.generate_expression(&e.this)?;
27353        Ok(())
27354    }
27355
27356    fn generate_case_specific_column_constraint(
27357        &mut self,
27358        e: &CaseSpecificColumnConstraint,
27359    ) -> Result<()> {
27360        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
27361        if e.not_.is_some() {
27362            self.write_keyword("NOT");
27363            self.write_space();
27364        }
27365        self.write_keyword("CASESPECIFIC");
27366        Ok(())
27367    }
27368
27369    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
27370        // Cast to string type (dialect-specific)
27371        self.write_keyword("CAST");
27372        self.write("(");
27373        self.generate_expression(&e.this)?;
27374        if self.config.dialect == Some(DialectType::ClickHouse) {
27375            // ClickHouse: CAST(expr, 'type_string')
27376            self.write(", ");
27377        } else {
27378            self.write_space();
27379            self.write_keyword("AS");
27380            self.write_space();
27381        }
27382        if let Some(to) = &e.to {
27383            self.generate_expression(to)?;
27384        }
27385        self.write(")");
27386        Ok(())
27387    }
27388
27389    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
27390        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
27391        // Python: f"CHANGES ({information}){at_before}{end}"
27392        self.write_keyword("CHANGES");
27393        self.write(" (");
27394        if let Some(information) = &e.information {
27395            self.write_keyword("INFORMATION");
27396            self.write(" => ");
27397            self.generate_expression(information)?;
27398        }
27399        self.write(")");
27400        // at_before and end are HistoricalData expressions that generate their own keywords
27401        if let Some(at_before) = &e.at_before {
27402            self.write(" ");
27403            self.generate_expression(at_before)?;
27404        }
27405        if let Some(end) = &e.end {
27406            self.write(" ");
27407            self.generate_expression(end)?;
27408        }
27409        Ok(())
27410    }
27411
27412    fn generate_character_set_column_constraint(
27413        &mut self,
27414        e: &CharacterSetColumnConstraint,
27415    ) -> Result<()> {
27416        // CHARACTER SET charset_name
27417        self.write_keyword("CHARACTER SET");
27418        self.write_space();
27419        self.generate_expression(&e.this)?;
27420        Ok(())
27421    }
27422
27423    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
27424        // [DEFAULT] CHARACTER SET=value
27425        if e.default.is_some() {
27426            self.write_keyword("DEFAULT");
27427            self.write_space();
27428        }
27429        self.write_keyword("CHARACTER SET");
27430        self.write("=");
27431        self.generate_expression(&e.this)?;
27432        Ok(())
27433    }
27434
27435    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
27436        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
27437        self.write_keyword("CHECK");
27438        self.write(" (");
27439        self.generate_expression(&e.this)?;
27440        self.write(")");
27441        if e.enforced.is_some() {
27442            self.write_space();
27443            self.write_keyword("ENFORCED");
27444        }
27445        Ok(())
27446    }
27447
27448    fn generate_assume_column_constraint(&mut self, e: &AssumeColumnConstraint) -> Result<()> {
27449        // Python: return f"ASSUME ({self.sql(e, 'this')})"
27450        self.write_keyword("ASSUME");
27451        self.write(" (");
27452        self.generate_expression(&e.this)?;
27453        self.write(")");
27454        Ok(())
27455    }
27456
27457    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
27458        // CHECK_JSON(this)
27459        self.write_keyword("CHECK_JSON");
27460        self.write("(");
27461        self.generate_expression(&e.this)?;
27462        self.write(")");
27463        Ok(())
27464    }
27465
27466    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
27467        // CHECK_XML(this)
27468        self.write_keyword("CHECK_XML");
27469        self.write("(");
27470        self.generate_expression(&e.this)?;
27471        self.write(")");
27472        Ok(())
27473    }
27474
27475    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
27476        // CHECKSUM=[ON|OFF|DEFAULT]
27477        self.write_keyword("CHECKSUM");
27478        self.write("=");
27479        if e.on.is_some() {
27480            self.write_keyword("ON");
27481        } else if e.default.is_some() {
27482            self.write_keyword("DEFAULT");
27483        } else {
27484            self.write_keyword("OFF");
27485        }
27486        Ok(())
27487    }
27488
27489    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
27490        // Python: return f"{shallow}{keyword} {this}"
27491        if e.shallow.is_some() {
27492            self.write_keyword("SHALLOW");
27493            self.write_space();
27494        }
27495        if e.copy.is_some() {
27496            self.write_keyword("COPY");
27497        } else {
27498            self.write_keyword("CLONE");
27499        }
27500        self.write_space();
27501        self.generate_expression(&e.this)?;
27502        Ok(())
27503    }
27504
27505    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
27506        // CLUSTER BY (expressions)
27507        self.write_keyword("CLUSTER BY");
27508        self.write(" (");
27509        for (i, ord) in e.expressions.iter().enumerate() {
27510            if i > 0 {
27511                self.write(", ");
27512            }
27513            self.generate_ordered(ord)?;
27514        }
27515        self.write(")");
27516        Ok(())
27517    }
27518
27519    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
27520        // BigQuery table property: CLUSTER BY col1, col2
27521        self.write_keyword("CLUSTER BY");
27522        self.write_space();
27523        for (i, col) in e.columns.iter().enumerate() {
27524            if i > 0 {
27525                self.write(", ");
27526            }
27527            self.generate_identifier(col)?;
27528        }
27529        Ok(())
27530    }
27531
27532    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
27533        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
27534        self.write_keyword("CLUSTERED BY");
27535        self.write(" (");
27536        for (i, expr) in e.expressions.iter().enumerate() {
27537            if i > 0 {
27538                self.write(", ");
27539            }
27540            self.generate_expression(expr)?;
27541        }
27542        self.write(")");
27543        if let Some(sorted_by) = &e.sorted_by {
27544            self.write_space();
27545            self.write_keyword("SORTED BY");
27546            self.write(" (");
27547            // Unwrap Tuple to avoid double parentheses
27548            if let Expression::Tuple(t) = sorted_by.as_ref() {
27549                for (i, expr) in t.expressions.iter().enumerate() {
27550                    if i > 0 {
27551                        self.write(", ");
27552                    }
27553                    self.generate_expression(expr)?;
27554                }
27555            } else {
27556                self.generate_expression(sorted_by)?;
27557            }
27558            self.write(")");
27559        }
27560        if let Some(buckets) = &e.buckets {
27561            self.write_space();
27562            self.write_keyword("INTO");
27563            self.write_space();
27564            self.generate_expression(buckets)?;
27565            self.write_space();
27566            self.write_keyword("BUCKETS");
27567        }
27568        Ok(())
27569    }
27570
27571    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
27572        // [DEFAULT] COLLATE [=] value
27573        // BigQuery uses space: DEFAULT COLLATE 'en'
27574        // Others use equals: COLLATE='en'
27575        if e.default.is_some() {
27576            self.write_keyword("DEFAULT");
27577            self.write_space();
27578        }
27579        self.write_keyword("COLLATE");
27580        // BigQuery uses space between COLLATE and value
27581        match self.config.dialect {
27582            Some(DialectType::BigQuery) => self.write_space(),
27583            _ => self.write("="),
27584        }
27585        self.generate_expression(&e.this)?;
27586        Ok(())
27587    }
27588
27589    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
27590        // ColumnConstraint is an enum
27591        match e {
27592            ColumnConstraint::NotNull => {
27593                self.write_keyword("NOT NULL");
27594            }
27595            ColumnConstraint::Null => {
27596                self.write_keyword("NULL");
27597            }
27598            ColumnConstraint::Unique => {
27599                self.write_keyword("UNIQUE");
27600            }
27601            ColumnConstraint::PrimaryKey => {
27602                self.write_keyword("PRIMARY KEY");
27603            }
27604            ColumnConstraint::Default(expr) => {
27605                self.write_keyword("DEFAULT");
27606                self.write_space();
27607                self.generate_expression(expr)?;
27608            }
27609            ColumnConstraint::Check(expr) => {
27610                self.write_keyword("CHECK");
27611                self.write(" (");
27612                self.generate_expression(expr)?;
27613                self.write(")");
27614            }
27615            ColumnConstraint::References(fk_ref) => {
27616                if fk_ref.has_foreign_key_keywords {
27617                    self.write_keyword("FOREIGN KEY");
27618                    self.write_space();
27619                }
27620                self.write_keyword("REFERENCES");
27621                self.write_space();
27622                self.generate_table(&fk_ref.table)?;
27623                if !fk_ref.columns.is_empty() {
27624                    self.write(" (");
27625                    for (i, col) in fk_ref.columns.iter().enumerate() {
27626                        if i > 0 {
27627                            self.write(", ");
27628                        }
27629                        self.generate_identifier(col)?;
27630                    }
27631                    self.write(")");
27632                }
27633            }
27634            ColumnConstraint::GeneratedAsIdentity(gen) => {
27635                self.write_keyword("GENERATED");
27636                self.write_space();
27637                if gen.always {
27638                    self.write_keyword("ALWAYS");
27639                } else {
27640                    self.write_keyword("BY DEFAULT");
27641                    if gen.on_null {
27642                        self.write_space();
27643                        self.write_keyword("ON NULL");
27644                    }
27645                }
27646                self.write_space();
27647                self.write_keyword("AS IDENTITY");
27648            }
27649            ColumnConstraint::Collate(collation) => {
27650                self.write_keyword("COLLATE");
27651                self.write_space();
27652                self.generate_identifier(collation)?;
27653            }
27654            ColumnConstraint::Comment(comment) => {
27655                self.write_keyword("COMMENT");
27656                self.write(" '");
27657                self.write(comment);
27658                self.write("'");
27659            }
27660            ColumnConstraint::ComputedColumn(cc) => {
27661                self.generate_computed_column_inline(cc)?;
27662            }
27663            ColumnConstraint::GeneratedAsRow(gar) => {
27664                self.generate_generated_as_row_inline(gar)?;
27665            }
27666            ColumnConstraint::Tags(tags) => {
27667                self.write_keyword("TAG");
27668                self.write(" (");
27669                for (i, expr) in tags.expressions.iter().enumerate() {
27670                    if i > 0 {
27671                        self.write(", ");
27672                    }
27673                    self.generate_expression(expr)?;
27674                }
27675                self.write(")");
27676            }
27677            ColumnConstraint::Path(path_expr) => {
27678                self.write_keyword("PATH");
27679                self.write_space();
27680                self.generate_expression(path_expr)?;
27681            }
27682        }
27683        Ok(())
27684    }
27685
27686    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
27687        // ColumnPosition is an enum
27688        match e {
27689            ColumnPosition::First => {
27690                self.write_keyword("FIRST");
27691            }
27692            ColumnPosition::After(ident) => {
27693                self.write_keyword("AFTER");
27694                self.write_space();
27695                self.generate_identifier(ident)?;
27696            }
27697        }
27698        Ok(())
27699    }
27700
27701    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
27702        // column(prefix)
27703        self.generate_expression(&e.this)?;
27704        self.write("(");
27705        self.generate_expression(&e.expression)?;
27706        self.write(")");
27707        Ok(())
27708    }
27709
27710    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
27711        // If unpack is true, this came from * COLUMNS(pattern)
27712        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
27713        if let Some(ref unpack) = e.unpack {
27714            if let Expression::Boolean(b) = unpack.as_ref() {
27715                if b.value {
27716                    self.write("*");
27717                }
27718            }
27719        }
27720        self.write_keyword("COLUMNS");
27721        self.write("(");
27722        self.generate_expression(&e.this)?;
27723        self.write(")");
27724        Ok(())
27725    }
27726
27727    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
27728        // Combined aggregate: FUNC(args) combined
27729        self.generate_expression(&e.this)?;
27730        self.write("(");
27731        for (i, expr) in e.expressions.iter().enumerate() {
27732            if i > 0 {
27733                self.write(", ");
27734            }
27735            self.generate_expression(expr)?;
27736        }
27737        self.write(")");
27738        Ok(())
27739    }
27740
27741    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
27742        // Combined parameterized aggregate: FUNC(params)(expressions)
27743        self.generate_expression(&e.this)?;
27744        self.write("(");
27745        for (i, param) in e.params.iter().enumerate() {
27746            if i > 0 {
27747                self.write(", ");
27748            }
27749            self.generate_expression(param)?;
27750        }
27751        self.write(")(");
27752        for (i, expr) in e.expressions.iter().enumerate() {
27753            if i > 0 {
27754                self.write(", ");
27755            }
27756            self.generate_expression(expr)?;
27757        }
27758        self.write(")");
27759        Ok(())
27760    }
27761
27762    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
27763        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
27764        self.write_keyword("COMMIT");
27765
27766        // TSQL always uses COMMIT TRANSACTION
27767        if e.this.is_none()
27768            && matches!(
27769                self.config.dialect,
27770                Some(DialectType::TSQL) | Some(DialectType::Fabric)
27771            )
27772        {
27773            self.write_space();
27774            self.write_keyword("TRANSACTION");
27775        }
27776
27777        // Check if this has TRANSACTION keyword or transaction name
27778        if let Some(this) = &e.this {
27779            // Check if it's just the "TRANSACTION" marker or an actual transaction name
27780            let is_transaction_marker = matches!(
27781                this.as_ref(),
27782                Expression::Identifier(id) if id.name == "TRANSACTION"
27783            );
27784
27785            self.write_space();
27786            self.write_keyword("TRANSACTION");
27787
27788            // If it's a real transaction name, output it
27789            if !is_transaction_marker {
27790                self.write_space();
27791                self.generate_expression(this)?;
27792            }
27793        }
27794
27795        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
27796        if let Some(durability) = &e.durability {
27797            self.write_space();
27798            self.write_keyword("WITH");
27799            self.write(" (");
27800            self.write_keyword("DELAYED_DURABILITY");
27801            self.write(" = ");
27802            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
27803                self.write_keyword("ON");
27804            } else {
27805                self.write_keyword("OFF");
27806            }
27807            self.write(")");
27808        }
27809
27810        // Output AND [NO] CHAIN
27811        if let Some(chain) = &e.chain {
27812            self.write_space();
27813            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
27814                self.write_keyword("AND NO CHAIN");
27815            } else {
27816                self.write_keyword("AND CHAIN");
27817            }
27818        }
27819        Ok(())
27820    }
27821
27822    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
27823        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
27824        self.write("[");
27825        self.generate_expression(&e.this)?;
27826        self.write_space();
27827        self.write_keyword("FOR");
27828        self.write_space();
27829        self.generate_expression(&e.expression)?;
27830        // Handle optional position variable (for enumerate-like syntax)
27831        if let Some(pos) = &e.position {
27832            self.write(", ");
27833            self.generate_expression(pos)?;
27834        }
27835        if let Some(iterator) = &e.iterator {
27836            self.write_space();
27837            self.write_keyword("IN");
27838            self.write_space();
27839            self.generate_expression(iterator)?;
27840        }
27841        if let Some(condition) = &e.condition {
27842            self.write_space();
27843            self.write_keyword("IF");
27844            self.write_space();
27845            self.generate_expression(condition)?;
27846        }
27847        self.write("]");
27848        Ok(())
27849    }
27850
27851    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
27852        // COMPRESS(this[, method])
27853        self.write_keyword("COMPRESS");
27854        self.write("(");
27855        self.generate_expression(&e.this)?;
27856        if let Some(method) = &e.method {
27857            self.write(", '");
27858            self.write(method);
27859            self.write("'");
27860        }
27861        self.write(")");
27862        Ok(())
27863    }
27864
27865    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
27866        // Python: return f"COMPRESS {this}"
27867        self.write_keyword("COMPRESS");
27868        if let Some(this) = &e.this {
27869            self.write_space();
27870            self.generate_expression(this)?;
27871        }
27872        Ok(())
27873    }
27874
27875    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
27876        // Python: return f"AS {this}{persisted}"
27877        self.write_keyword("AS");
27878        self.write_space();
27879        self.generate_expression(&e.this)?;
27880        if e.not_null.is_some() {
27881            self.write_space();
27882            self.write_keyword("PERSISTED NOT NULL");
27883        } else if e.persisted.is_some() {
27884            self.write_space();
27885            self.write_keyword("PERSISTED");
27886        }
27887        Ok(())
27888    }
27889
27890    /// Generate a ComputedColumn constraint inline within a column definition.
27891    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
27892    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
27893    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
27894        let computed_expr = if matches!(
27895            self.config.dialect,
27896            Some(DialectType::TSQL) | Some(DialectType::Fabric)
27897        ) {
27898            match &*cc.expression {
27899                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
27900                {
27901                    let wrapped = Expression::Cast(Box::new(Cast {
27902                        this: y.this.clone(),
27903                        to: DataType::Date,
27904                        trailing_comments: Vec::new(),
27905                        double_colon_syntax: false,
27906                        format: None,
27907                        default: None,
27908                        inferred_type: None,
27909                    }));
27910                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
27911                }
27912                Expression::Function(f)
27913                    if f.name.eq_ignore_ascii_case("YEAR")
27914                        && f.args.len() == 1
27915                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
27916                {
27917                    let wrapped = Expression::Cast(Box::new(Cast {
27918                        this: f.args[0].clone(),
27919                        to: DataType::Date,
27920                        trailing_comments: Vec::new(),
27921                        double_colon_syntax: false,
27922                        format: None,
27923                        default: None,
27924                        inferred_type: None,
27925                    }));
27926                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
27927                }
27928                _ => *cc.expression.clone(),
27929            }
27930        } else {
27931            *cc.expression.clone()
27932        };
27933
27934        match cc.persistence_kind.as_deref() {
27935            Some("STORED") | Some("VIRTUAL") => {
27936                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
27937                self.write_keyword("GENERATED ALWAYS AS");
27938                self.write(" (");
27939                self.generate_expression(&computed_expr)?;
27940                self.write(")");
27941                self.write_space();
27942                if cc.persisted {
27943                    self.write_keyword("STORED");
27944                } else {
27945                    self.write_keyword("VIRTUAL");
27946                }
27947            }
27948            Some("PERSISTED") => {
27949                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
27950                self.write_keyword("AS");
27951                self.write(" (");
27952                self.generate_expression(&computed_expr)?;
27953                self.write(")");
27954                self.write_space();
27955                self.write_keyword("PERSISTED");
27956                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
27957                if let Some(ref dt) = cc.data_type {
27958                    self.write_space();
27959                    self.generate_data_type(dt)?;
27960                }
27961                if cc.not_null {
27962                    self.write_space();
27963                    self.write_keyword("NOT NULL");
27964                }
27965            }
27966            _ => {
27967                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
27968                // TSQL computed column without PERSISTED: AS (expr)
27969                if matches!(
27970                    self.config.dialect,
27971                    Some(DialectType::Spark)
27972                        | Some(DialectType::Databricks)
27973                        | Some(DialectType::Hive)
27974                ) {
27975                    self.write_keyword("GENERATED ALWAYS AS");
27976                    self.write(" (");
27977                    self.generate_expression(&computed_expr)?;
27978                    self.write(")");
27979                } else if matches!(
27980                    self.config.dialect,
27981                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
27982                ) {
27983                    self.write_keyword("AS");
27984                    let omit_parens = matches!(computed_expr, Expression::Year(_))
27985                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
27986                    if omit_parens {
27987                        self.write_space();
27988                        self.generate_expression(&computed_expr)?;
27989                    } else {
27990                        self.write(" (");
27991                        self.generate_expression(&computed_expr)?;
27992                        self.write(")");
27993                    }
27994                } else {
27995                    self.write_keyword("AS");
27996                    self.write(" (");
27997                    self.generate_expression(&computed_expr)?;
27998                    self.write(")");
27999                }
28000            }
28001        }
28002        Ok(())
28003    }
28004
28005    /// Generate a GeneratedAsRow constraint inline within a column definition.
28006    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
28007    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
28008        self.write_keyword("GENERATED ALWAYS AS ROW ");
28009        if gar.start {
28010            self.write_keyword("START");
28011        } else {
28012            self.write_keyword("END");
28013        }
28014        if gar.hidden {
28015            self.write_space();
28016            self.write_keyword("HIDDEN");
28017        }
28018        Ok(())
28019    }
28020
28021    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
28022    fn generate_system_versioning_content(
28023        &mut self,
28024        e: &WithSystemVersioningProperty,
28025    ) -> Result<()> {
28026        let mut parts = Vec::new();
28027
28028        if let Some(this) = &e.this {
28029            let mut s = String::from("HISTORY_TABLE=");
28030            let mut gen = Generator::with_arc_config(self.config.clone());
28031            gen.generate_expression(this)?;
28032            s.push_str(&gen.output);
28033            parts.push(s);
28034        }
28035
28036        if let Some(data_consistency) = &e.data_consistency {
28037            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
28038            let mut gen = Generator::with_arc_config(self.config.clone());
28039            gen.generate_expression(data_consistency)?;
28040            s.push_str(&gen.output);
28041            parts.push(s);
28042        }
28043
28044        if let Some(retention_period) = &e.retention_period {
28045            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
28046            let mut gen = Generator::with_arc_config(self.config.clone());
28047            gen.generate_expression(retention_period)?;
28048            s.push_str(&gen.output);
28049            parts.push(s);
28050        }
28051
28052        self.write_keyword("SYSTEM_VERSIONING");
28053        self.write("=");
28054
28055        if !parts.is_empty() {
28056            self.write_keyword("ON");
28057            self.write("(");
28058            self.write(&parts.join(", "));
28059            self.write(")");
28060        } else if e.on.is_some() {
28061            self.write_keyword("ON");
28062        } else {
28063            self.write_keyword("OFF");
28064        }
28065
28066        Ok(())
28067    }
28068
28069    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
28070        // Conditional INSERT for multi-table inserts
28071        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
28072        if e.else_.is_some() {
28073            self.write_keyword("ELSE");
28074            self.write_space();
28075        } else if let Some(expression) = &e.expression {
28076            self.write_keyword("WHEN");
28077            self.write_space();
28078            self.generate_expression(expression)?;
28079            self.write_space();
28080            self.write_keyword("THEN");
28081            self.write_space();
28082        }
28083
28084        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
28085        // without the "INSERT " prefix
28086        if let Expression::Insert(insert) = e.this.as_ref() {
28087            self.write_keyword("INTO");
28088            self.write_space();
28089            self.generate_table(&insert.table)?;
28090
28091            // Optional column list
28092            if !insert.columns.is_empty() {
28093                self.write(" (");
28094                for (i, col) in insert.columns.iter().enumerate() {
28095                    if i > 0 {
28096                        self.write(", ");
28097                    }
28098                    self.generate_identifier(col)?;
28099                }
28100                self.write(")");
28101            }
28102
28103            // Optional VALUES clause
28104            if !insert.values.is_empty() {
28105                self.write_space();
28106                self.write_keyword("VALUES");
28107                for (row_idx, row) in insert.values.iter().enumerate() {
28108                    if row_idx > 0 {
28109                        self.write(", ");
28110                    }
28111                    self.write(" (");
28112                    for (i, val) in row.iter().enumerate() {
28113                        if i > 0 {
28114                            self.write(", ");
28115                        }
28116                        self.generate_expression(val)?;
28117                    }
28118                    self.write(")");
28119                }
28120            }
28121        } else {
28122            // Fallback for non-Insert expressions
28123            self.generate_expression(&e.this)?;
28124        }
28125        Ok(())
28126    }
28127
28128    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
28129        // Python: return f"CONSTRAINT {this} {expressions}"
28130        self.write_keyword("CONSTRAINT");
28131        self.write_space();
28132        self.generate_expression(&e.this)?;
28133        if !e.expressions.is_empty() {
28134            self.write_space();
28135            for (i, expr) in e.expressions.iter().enumerate() {
28136                if i > 0 {
28137                    self.write_space();
28138                }
28139                self.generate_expression(expr)?;
28140            }
28141        }
28142        Ok(())
28143    }
28144
28145    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
28146        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
28147        self.write_keyword("CONVERT_TIMEZONE");
28148        self.write("(");
28149        let mut first = true;
28150        if let Some(source_tz) = &e.source_tz {
28151            self.generate_expression(source_tz)?;
28152            first = false;
28153        }
28154        if let Some(target_tz) = &e.target_tz {
28155            if !first {
28156                self.write(", ");
28157            }
28158            self.generate_expression(target_tz)?;
28159            first = false;
28160        }
28161        if let Some(timestamp) = &e.timestamp {
28162            if !first {
28163                self.write(", ");
28164            }
28165            self.generate_expression(timestamp)?;
28166        }
28167        self.write(")");
28168        Ok(())
28169    }
28170
28171    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
28172        // CONVERT(this USING dest)
28173        self.write_keyword("CONVERT");
28174        self.write("(");
28175        self.generate_expression(&e.this)?;
28176        if let Some(dest) = &e.dest {
28177            self.write_space();
28178            self.write_keyword("USING");
28179            self.write_space();
28180            self.generate_expression(dest)?;
28181        }
28182        self.write(")");
28183        Ok(())
28184    }
28185
28186    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
28187        self.write_keyword("COPY");
28188        if e.is_into {
28189            self.write_space();
28190            self.write_keyword("INTO");
28191        }
28192        self.write_space();
28193
28194        // Generate target table or query (or stage for COPY INTO @stage)
28195        if let Expression::Literal(lit) = &e.this {
28196            if let Literal::String(s) = lit.as_ref() {
28197                if s.starts_with('@') {
28198                    self.write(s);
28199                } else {
28200                    self.generate_expression(&e.this)?;
28201                }
28202            }
28203        } else {
28204            self.generate_expression(&e.this)?;
28205        }
28206
28207        // FROM or TO based on kind
28208        if e.kind {
28209            // kind=true means FROM (loading into table)
28210            if self.config.pretty {
28211                self.write_newline();
28212            } else {
28213                self.write_space();
28214            }
28215            self.write_keyword("FROM");
28216            self.write_space();
28217        } else if !e.files.is_empty() {
28218            // kind=false means TO (exporting)
28219            if self.config.pretty {
28220                self.write_newline();
28221            } else {
28222                self.write_space();
28223            }
28224            self.write_keyword("TO");
28225            self.write_space();
28226        }
28227
28228        // Generate source/destination files
28229        for (i, file) in e.files.iter().enumerate() {
28230            if i > 0 {
28231                self.write_space();
28232            }
28233            // For stage references (strings starting with @), output without quotes
28234            if let Expression::Literal(lit) = file {
28235                if let Literal::String(s) = lit.as_ref() {
28236                    if s.starts_with('@') {
28237                        self.write(s);
28238                    } else {
28239                        self.generate_expression(file)?;
28240                    }
28241                }
28242            } else if let Expression::Identifier(id) = file {
28243                // Backtick-quoted file path (Databricks style: `s3://link`)
28244                if id.quoted {
28245                    self.write("`");
28246                    self.write(&id.name);
28247                    self.write("`");
28248                } else {
28249                    self.generate_expression(file)?;
28250                }
28251            } else {
28252                self.generate_expression(file)?;
28253            }
28254        }
28255
28256        // Generate credentials if present (Snowflake style - not wrapped in WITH)
28257        if !e.with_wrapped {
28258            if let Some(ref creds) = e.credentials {
28259                if let Some(ref storage) = creds.storage {
28260                    if self.config.pretty {
28261                        self.write_newline();
28262                    } else {
28263                        self.write_space();
28264                    }
28265                    self.write_keyword("STORAGE_INTEGRATION");
28266                    self.write(" = ");
28267                    self.write(storage);
28268                }
28269                if creds.credentials.is_empty() {
28270                    // Empty credentials: CREDENTIALS = ()
28271                    if self.config.pretty {
28272                        self.write_newline();
28273                    } else {
28274                        self.write_space();
28275                    }
28276                    self.write_keyword("CREDENTIALS");
28277                    self.write(" = ()");
28278                } else {
28279                    if self.config.pretty {
28280                        self.write_newline();
28281                    } else {
28282                        self.write_space();
28283                    }
28284                    self.write_keyword("CREDENTIALS");
28285                    // Check if this is Redshift-style (single value with empty key)
28286                    // vs Snowflake-style (multiple key=value pairs)
28287                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
28288                        // Redshift style: CREDENTIALS 'value'
28289                        self.write(" '");
28290                        self.write(&creds.credentials[0].1);
28291                        self.write("'");
28292                    } else {
28293                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
28294                        self.write(" = (");
28295                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
28296                            if i > 0 {
28297                                self.write_space();
28298                            }
28299                            self.write(k);
28300                            self.write("='");
28301                            self.write(v);
28302                            self.write("'");
28303                        }
28304                        self.write(")");
28305                    }
28306                }
28307                if let Some(ref encryption) = creds.encryption {
28308                    self.write_space();
28309                    self.write_keyword("ENCRYPTION");
28310                    self.write(" = ");
28311                    self.write(encryption);
28312                }
28313            }
28314        }
28315
28316        // Generate parameters
28317        if !e.params.is_empty() {
28318            if e.with_wrapped {
28319                // DuckDB/PostgreSQL/TSQL WITH (...) format
28320                self.write_space();
28321                self.write_keyword("WITH");
28322                self.write(" (");
28323                for (i, param) in e.params.iter().enumerate() {
28324                    if i > 0 {
28325                        self.write(", ");
28326                    }
28327                    self.generate_copy_param_with_format(param)?;
28328                }
28329                self.write(")");
28330            } else {
28331                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
28332                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
28333                // For Snowflake: KEY = VALUE
28334                for param in &e.params {
28335                    if self.config.pretty {
28336                        self.write_newline();
28337                    } else {
28338                        self.write_space();
28339                    }
28340                    // Preserve original case of parameter name (important for Redshift COPY options)
28341                    self.write(&param.name);
28342                    if let Some(ref value) = param.value {
28343                        // Use = only if it was present in the original (param.eq)
28344                        if param.eq {
28345                            self.write(" = ");
28346                        } else {
28347                            self.write(" ");
28348                        }
28349                        if !param.values.is_empty() {
28350                            self.write("(");
28351                            for (i, v) in param.values.iter().enumerate() {
28352                                if i > 0 {
28353                                    self.write_space();
28354                                }
28355                                self.generate_copy_nested_param(v)?;
28356                            }
28357                            self.write(")");
28358                        } else {
28359                            // For COPY parameter values, output identifiers without quoting
28360                            self.generate_copy_param_value(value)?;
28361                        }
28362                    } else if !param.values.is_empty() {
28363                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
28364                        if param.eq {
28365                            self.write(" = (");
28366                        } else {
28367                            self.write(" (");
28368                        }
28369                        // Determine separator for values inside parentheses:
28370                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
28371                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
28372                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
28373                        let is_key_value_pairs = param
28374                            .values
28375                            .first()
28376                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
28377                        let sep = if is_key_value_pairs && param.eq {
28378                            " "
28379                        } else {
28380                            ", "
28381                        };
28382                        for (i, v) in param.values.iter().enumerate() {
28383                            if i > 0 {
28384                                self.write(sep);
28385                            }
28386                            self.generate_copy_nested_param(v)?;
28387                        }
28388                        self.write(")");
28389                    }
28390                }
28391            }
28392        }
28393
28394        Ok(())
28395    }
28396
28397    /// Generate a COPY parameter in WITH (...) format
28398    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
28399    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
28400        self.write_keyword(&param.name);
28401        if !param.values.is_empty() {
28402            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
28403            self.write(" = (");
28404            for (i, v) in param.values.iter().enumerate() {
28405                if i > 0 {
28406                    self.write(", ");
28407                }
28408                self.generate_copy_nested_param(v)?;
28409            }
28410            self.write(")");
28411        } else if let Some(ref value) = param.value {
28412            if param.eq {
28413                self.write(" = ");
28414            } else {
28415                self.write(" ");
28416            }
28417            self.generate_expression(value)?;
28418        }
28419        Ok(())
28420    }
28421
28422    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
28423    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
28424        match expr {
28425            Expression::Eq(eq) => {
28426                // Generate key
28427                match &eq.left {
28428                    Expression::Column(c) => self.write(&c.name.name),
28429                    _ => self.generate_expression(&eq.left)?,
28430                }
28431                self.write("=");
28432                // Generate value
28433                match &eq.right {
28434                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
28435                        let Literal::String(s) = lit.as_ref() else {
28436                            unreachable!()
28437                        };
28438                        self.write("'");
28439                        self.write(s);
28440                        self.write("'");
28441                    }
28442                    Expression::Tuple(t) => {
28443                        // For lists like NULL_IF=('', 'str1')
28444                        self.write("(");
28445                        if self.config.pretty {
28446                            self.write_newline();
28447                            self.indent_level += 1;
28448                            for (i, item) in t.expressions.iter().enumerate() {
28449                                if i > 0 {
28450                                    self.write(", ");
28451                                }
28452                                self.write_indent();
28453                                self.generate_expression(item)?;
28454                            }
28455                            self.write_newline();
28456                            self.indent_level -= 1;
28457                        } else {
28458                            for (i, item) in t.expressions.iter().enumerate() {
28459                                if i > 0 {
28460                                    self.write(", ");
28461                                }
28462                                self.generate_expression(item)?;
28463                            }
28464                        }
28465                        self.write(")");
28466                    }
28467                    _ => self.generate_expression(&eq.right)?,
28468                }
28469                Ok(())
28470            }
28471            Expression::Column(c) => {
28472                // Standalone keyword like COMPRESSION
28473                self.write(&c.name.name);
28474                Ok(())
28475            }
28476            _ => self.generate_expression(expr),
28477        }
28478    }
28479
28480    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
28481    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
28482    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
28483        match expr {
28484            Expression::Column(c) => {
28485                // Output identifier, preserving quotes if originally quoted
28486                if c.name.quoted {
28487                    self.write("\"");
28488                    self.write(&c.name.name);
28489                    self.write("\"");
28490                } else {
28491                    self.write(&c.name.name);
28492                }
28493                Ok(())
28494            }
28495            Expression::Identifier(id) => {
28496                // Output identifier, preserving quotes if originally quoted
28497                if id.quoted {
28498                    self.write("\"");
28499                    self.write(&id.name);
28500                    self.write("\"");
28501                } else {
28502                    self.write(&id.name);
28503                }
28504                Ok(())
28505            }
28506            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
28507                let Literal::String(s) = lit.as_ref() else {
28508                    unreachable!()
28509                };
28510                // Output string with quotes
28511                self.write("'");
28512                self.write(s);
28513                self.write("'");
28514                Ok(())
28515            }
28516            _ => self.generate_expression(expr),
28517        }
28518    }
28519
28520    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
28521        self.write_keyword(&e.name);
28522        if let Some(ref value) = e.value {
28523            if e.eq {
28524                self.write(" = ");
28525            } else {
28526                self.write(" ");
28527            }
28528            self.generate_expression(value)?;
28529        }
28530        if !e.values.is_empty() {
28531            if e.eq {
28532                self.write(" = ");
28533            } else {
28534                self.write(" ");
28535            }
28536            self.write("(");
28537            for (i, v) in e.values.iter().enumerate() {
28538                if i > 0 {
28539                    self.write(", ");
28540                }
28541                self.generate_expression(v)?;
28542            }
28543            self.write(")");
28544        }
28545        Ok(())
28546    }
28547
28548    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
28549        // CORR(this, expression)
28550        self.write_keyword("CORR");
28551        self.write("(");
28552        self.generate_expression(&e.this)?;
28553        self.write(", ");
28554        self.generate_expression(&e.expression)?;
28555        self.write(")");
28556        Ok(())
28557    }
28558
28559    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
28560        // COSINE_DISTANCE(this, expression)
28561        self.write_keyword("COSINE_DISTANCE");
28562        self.write("(");
28563        self.generate_expression(&e.this)?;
28564        self.write(", ");
28565        self.generate_expression(&e.expression)?;
28566        self.write(")");
28567        Ok(())
28568    }
28569
28570    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
28571        // COVAR_POP(this, expression)
28572        self.write_keyword("COVAR_POP");
28573        self.write("(");
28574        self.generate_expression(&e.this)?;
28575        self.write(", ");
28576        self.generate_expression(&e.expression)?;
28577        self.write(")");
28578        Ok(())
28579    }
28580
28581    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
28582        // COVAR_SAMP(this, expression)
28583        self.write_keyword("COVAR_SAMP");
28584        self.write("(");
28585        self.generate_expression(&e.this)?;
28586        self.write(", ");
28587        self.generate_expression(&e.expression)?;
28588        self.write(")");
28589        Ok(())
28590    }
28591
28592    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
28593        // CREDENTIALS (key1='value1', key2='value2')
28594        self.write_keyword("CREDENTIALS");
28595        self.write(" (");
28596        for (i, (key, value)) in e.credentials.iter().enumerate() {
28597            if i > 0 {
28598                self.write(", ");
28599            }
28600            self.write(key);
28601            self.write("='");
28602            self.write(value);
28603            self.write("'");
28604        }
28605        self.write(")");
28606        Ok(())
28607    }
28608
28609    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
28610        // CREDENTIALS=(expressions)
28611        self.write_keyword("CREDENTIALS");
28612        self.write("=(");
28613        for (i, expr) in e.expressions.iter().enumerate() {
28614            if i > 0 {
28615                self.write(", ");
28616            }
28617            self.generate_expression(expr)?;
28618        }
28619        self.write(")");
28620        Ok(())
28621    }
28622
28623    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
28624        use crate::dialects::DialectType;
28625
28626        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
28627        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
28628        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
28629            self.generate_expression(&e.this)?;
28630            self.write_space();
28631            self.write_keyword("AS");
28632            self.write_space();
28633            self.generate_identifier(&e.alias)?;
28634            return Ok(());
28635        }
28636        self.write(&e.alias.name);
28637
28638        // BigQuery doesn't support column aliases in CTE definitions
28639        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
28640
28641        if !e.columns.is_empty() && !skip_cte_columns {
28642            self.write("(");
28643            for (i, col) in e.columns.iter().enumerate() {
28644                if i > 0 {
28645                    self.write(", ");
28646                }
28647                self.write(&col.name);
28648            }
28649            self.write(")");
28650        }
28651        // USING KEY (columns) for DuckDB recursive CTEs
28652        if !e.key_expressions.is_empty() {
28653            self.write_space();
28654            self.write_keyword("USING KEY");
28655            self.write(" (");
28656            for (i, key) in e.key_expressions.iter().enumerate() {
28657                if i > 0 {
28658                    self.write(", ");
28659                }
28660                self.write(&key.name);
28661            }
28662            self.write(")");
28663        }
28664        self.write_space();
28665        self.write_keyword("AS");
28666        self.write_space();
28667        if let Some(materialized) = e.materialized {
28668            if materialized {
28669                self.write_keyword("MATERIALIZED");
28670            } else {
28671                self.write_keyword("NOT MATERIALIZED");
28672            }
28673            self.write_space();
28674        }
28675        self.write("(");
28676        self.generate_expression(&e.this)?;
28677        self.write(")");
28678        Ok(())
28679    }
28680
28681    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
28682        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
28683        if e.expressions.is_empty() {
28684            self.write_keyword("WITH CUBE");
28685        } else {
28686            self.write_keyword("CUBE");
28687            self.write("(");
28688            for (i, expr) in e.expressions.iter().enumerate() {
28689                if i > 0 {
28690                    self.write(", ");
28691                }
28692                self.generate_expression(expr)?;
28693            }
28694            self.write(")");
28695        }
28696        Ok(())
28697    }
28698
28699    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
28700        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
28701        self.write_keyword("CURRENT_DATETIME");
28702        if let Some(this) = &e.this {
28703            self.write("(");
28704            self.generate_expression(this)?;
28705            self.write(")");
28706        }
28707        Ok(())
28708    }
28709
28710    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
28711        // CURRENT_SCHEMA - no arguments
28712        self.write_keyword("CURRENT_SCHEMA");
28713        Ok(())
28714    }
28715
28716    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
28717        // CURRENT_SCHEMAS(include_implicit)
28718        self.write_keyword("CURRENT_SCHEMAS");
28719        self.write("(");
28720        // Snowflake: drop the argument (CURRENT_SCHEMAS() takes no args)
28721        if !matches!(
28722            self.config.dialect,
28723            Some(crate::dialects::DialectType::Snowflake)
28724        ) {
28725            if let Some(this) = &e.this {
28726                self.generate_expression(this)?;
28727            }
28728        }
28729        self.write(")");
28730        Ok(())
28731    }
28732
28733    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
28734        // CURRENT_USER or CURRENT_USER()
28735        self.write_keyword("CURRENT_USER");
28736        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
28737        let needs_parens = e.this.is_some()
28738            || matches!(
28739                self.config.dialect,
28740                Some(DialectType::Snowflake)
28741                    | Some(DialectType::Spark)
28742                    | Some(DialectType::Hive)
28743                    | Some(DialectType::DuckDB)
28744                    | Some(DialectType::BigQuery)
28745                    | Some(DialectType::MySQL)
28746                    | Some(DialectType::Databricks)
28747            );
28748        if needs_parens {
28749            self.write("()");
28750        }
28751        Ok(())
28752    }
28753
28754    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
28755        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
28756        if self.config.dialect == Some(DialectType::Solr) {
28757            self.generate_expression(&e.this)?;
28758            self.write(" ");
28759            self.write_keyword("OR");
28760            self.write(" ");
28761            self.generate_expression(&e.expression)?;
28762        } else if self.config.dialect == Some(DialectType::MySQL) {
28763            self.generate_mysql_concat_from_dpipe(e)?;
28764        } else {
28765            // String concatenation: this || expression
28766            self.generate_expression(&e.this)?;
28767            self.write(" || ");
28768            self.generate_expression(&e.expression)?;
28769        }
28770        Ok(())
28771    }
28772
28773    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
28774        // DATABLOCKSIZE=... (Teradata)
28775        self.write_keyword("DATABLOCKSIZE");
28776        self.write("=");
28777        if let Some(size) = e.size {
28778            self.write(&size.to_string());
28779            if let Some(units) = &e.units {
28780                self.write_space();
28781                self.generate_expression(units)?;
28782            }
28783        } else if e.minimum.is_some() {
28784            self.write_keyword("MINIMUM");
28785        } else if e.maximum.is_some() {
28786            self.write_keyword("MAXIMUM");
28787        } else if e.default.is_some() {
28788            self.write_keyword("DEFAULT");
28789        }
28790        Ok(())
28791    }
28792
28793    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
28794        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
28795        self.write_keyword("DATA_DELETION");
28796        self.write("=");
28797
28798        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
28799        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
28800
28801        if is_on {
28802            self.write_keyword("ON");
28803            if has_options {
28804                self.write("(");
28805                let mut first = true;
28806                if let Some(filter_column) = &e.filter_column {
28807                    self.write_keyword("FILTER_COLUMN");
28808                    self.write("=");
28809                    self.generate_expression(filter_column)?;
28810                    first = false;
28811                }
28812                if let Some(retention_period) = &e.retention_period {
28813                    if !first {
28814                        self.write(", ");
28815                    }
28816                    self.write_keyword("RETENTION_PERIOD");
28817                    self.write("=");
28818                    self.generate_expression(retention_period)?;
28819                }
28820                self.write(")");
28821            }
28822        } else {
28823            self.write_keyword("OFF");
28824        }
28825        Ok(())
28826    }
28827
28828    /// Generate a Date function expression
28829    /// For Exasol: {d'value'} -> TO_DATE('value')
28830    /// For other dialects: DATE('value')
28831    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
28832        use crate::dialects::DialectType;
28833        use crate::expressions::Literal;
28834
28835        match self.config.dialect {
28836            // Exasol uses TO_DATE for Date expressions
28837            Some(DialectType::Exasol) => {
28838                self.write_keyword("TO_DATE");
28839                self.write("(");
28840                // Extract the string value from the expression if it's a string literal
28841                match &e.this {
28842                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
28843                        let Literal::String(s) = lit.as_ref() else {
28844                            unreachable!()
28845                        };
28846                        self.write("'");
28847                        self.write(s);
28848                        self.write("'");
28849                    }
28850                    _ => {
28851                        self.generate_expression(&e.this)?;
28852                    }
28853                }
28854                self.write(")");
28855            }
28856            // Standard: DATE(value)
28857            _ => {
28858                self.write_keyword("DATE");
28859                self.write("(");
28860                self.generate_expression(&e.this)?;
28861                self.write(")");
28862            }
28863        }
28864        Ok(())
28865    }
28866
28867    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
28868        // DATE_BIN(interval, timestamp[, origin])
28869        self.write_keyword("DATE_BIN");
28870        self.write("(");
28871        self.generate_expression(&e.this)?;
28872        self.write(", ");
28873        self.generate_expression(&e.expression)?;
28874        if let Some(origin) = &e.origin {
28875            self.write(", ");
28876            self.generate_expression(origin)?;
28877        }
28878        self.write(")");
28879        Ok(())
28880    }
28881
28882    fn generate_date_format_column_constraint(
28883        &mut self,
28884        e: &DateFormatColumnConstraint,
28885    ) -> Result<()> {
28886        // FORMAT 'format_string' (Teradata)
28887        self.write_keyword("FORMAT");
28888        self.write_space();
28889        self.generate_expression(&e.this)?;
28890        Ok(())
28891    }
28892
28893    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
28894        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
28895        self.write_keyword("DATE_FROM_PARTS");
28896        self.write("(");
28897        let mut first = true;
28898        if let Some(year) = &e.year {
28899            self.generate_expression(year)?;
28900            first = false;
28901        }
28902        if let Some(month) = &e.month {
28903            if !first {
28904                self.write(", ");
28905            }
28906            self.generate_expression(month)?;
28907            first = false;
28908        }
28909        if let Some(day) = &e.day {
28910            if !first {
28911                self.write(", ");
28912            }
28913            self.generate_expression(day)?;
28914        }
28915        self.write(")");
28916        Ok(())
28917    }
28918
28919    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
28920        // DATETIME(this) or DATETIME(this, expression)
28921        self.write_keyword("DATETIME");
28922        self.write("(");
28923        self.generate_expression(&e.this)?;
28924        if let Some(expr) = &e.expression {
28925            self.write(", ");
28926            self.generate_expression(expr)?;
28927        }
28928        self.write(")");
28929        Ok(())
28930    }
28931
28932    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
28933        // DATETIME_ADD(this, expression, unit)
28934        self.write_keyword("DATETIME_ADD");
28935        self.write("(");
28936        self.generate_expression(&e.this)?;
28937        self.write(", ");
28938        self.generate_expression(&e.expression)?;
28939        if let Some(unit) = &e.unit {
28940            self.write(", ");
28941            self.write_keyword(unit);
28942        }
28943        self.write(")");
28944        Ok(())
28945    }
28946
28947    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
28948        // DATETIME_DIFF(this, expression, unit)
28949        self.write_keyword("DATETIME_DIFF");
28950        self.write("(");
28951        self.generate_expression(&e.this)?;
28952        self.write(", ");
28953        self.generate_expression(&e.expression)?;
28954        if let Some(unit) = &e.unit {
28955            self.write(", ");
28956            self.write_keyword(unit);
28957        }
28958        self.write(")");
28959        Ok(())
28960    }
28961
28962    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
28963        // DATETIME_SUB(this, expression, unit)
28964        self.write_keyword("DATETIME_SUB");
28965        self.write("(");
28966        self.generate_expression(&e.this)?;
28967        self.write(", ");
28968        self.generate_expression(&e.expression)?;
28969        if let Some(unit) = &e.unit {
28970            self.write(", ");
28971            self.write_keyword(unit);
28972        }
28973        self.write(")");
28974        Ok(())
28975    }
28976
28977    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
28978        // DATETIME_TRUNC(this, unit, zone)
28979        self.write_keyword("DATETIME_TRUNC");
28980        self.write("(");
28981        self.generate_expression(&e.this)?;
28982        self.write(", ");
28983        self.write_keyword(&e.unit);
28984        if let Some(zone) = &e.zone {
28985            self.write(", ");
28986            self.generate_expression(zone)?;
28987        }
28988        self.write(")");
28989        Ok(())
28990    }
28991
28992    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
28993        // DAYNAME(this)
28994        self.write_keyword("DAYNAME");
28995        self.write("(");
28996        self.generate_expression(&e.this)?;
28997        self.write(")");
28998        Ok(())
28999    }
29000
29001    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
29002        // DECLARE [OR REPLACE] var1 AS type1, var2 AS type2, ...
29003        self.write_keyword("DECLARE");
29004        self.write_space();
29005        if e.replace {
29006            self.write_keyword("OR");
29007            self.write_space();
29008            self.write_keyword("REPLACE");
29009            self.write_space();
29010        }
29011        for (i, expr) in e.expressions.iter().enumerate() {
29012            if i > 0 {
29013                self.write(", ");
29014            }
29015            self.generate_expression(expr)?;
29016        }
29017        Ok(())
29018    }
29019
29020    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
29021        use crate::dialects::DialectType;
29022
29023        // variable TYPE [DEFAULT default]
29024        self.generate_expression(&e.this)?;
29025        // BigQuery multi-variable: DECLARE X, Y, Z INT64
29026        for name in &e.additional_names {
29027            self.write(", ");
29028            self.generate_expression(name)?;
29029        }
29030        if let Some(kind) = &e.kind {
29031            self.write_space();
29032            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
29033            // TSQL: Always includes AS (normalization)
29034            // Others: Include AS if present in original
29035            match self.config.dialect {
29036                Some(DialectType::BigQuery) => {
29037                    self.write(kind);
29038                }
29039                Some(DialectType::TSQL) => {
29040                    // TSQL DECLARE: no AS keyword (sqlglot convention)
29041                    // Normalize INT to INTEGER for simple declarations
29042                    // Complex TABLE declarations (with CLUSTERED/INDEX) are preserved as-is
29043                    let is_complex_table = kind.starts_with("TABLE")
29044                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
29045                    if is_complex_table {
29046                        self.write(kind);
29047                    } else if kind == "INT" {
29048                        self.write("INTEGER");
29049                    } else if kind.starts_with("TABLE") {
29050                        // Normalize INT to INTEGER inside simple TABLE column definitions
29051                        let normalized = kind
29052                            .replace(" INT ", " INTEGER ")
29053                            .replace(" INT,", " INTEGER,")
29054                            .replace(" INT)", " INTEGER)")
29055                            .replace("(INT ", "(INTEGER ");
29056                        self.write(&normalized);
29057                    } else {
29058                        self.write(kind);
29059                    }
29060                }
29061                _ => {
29062                    if e.has_as {
29063                        self.write_keyword("AS");
29064                        self.write_space();
29065                    }
29066                    self.write(kind);
29067                }
29068            }
29069        }
29070        if let Some(default) = &e.default {
29071            // BigQuery uses DEFAULT, others use =
29072            match self.config.dialect {
29073                Some(DialectType::BigQuery) => {
29074                    self.write_space();
29075                    self.write_keyword("DEFAULT");
29076                    self.write_space();
29077                }
29078                _ => {
29079                    self.write(" = ");
29080                }
29081            }
29082            self.generate_expression(default)?;
29083        }
29084        Ok(())
29085    }
29086
29087    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
29088        // DECODE(expr, search1, result1, search2, result2, ..., default)
29089        self.write_keyword("DECODE");
29090        self.write("(");
29091        for (i, expr) in e.expressions.iter().enumerate() {
29092            if i > 0 {
29093                self.write(", ");
29094            }
29095            self.generate_expression(expr)?;
29096        }
29097        self.write(")");
29098        Ok(())
29099    }
29100
29101    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
29102        // DECOMPRESS(expr, 'method')
29103        self.write_keyword("DECOMPRESS");
29104        self.write("(");
29105        self.generate_expression(&e.this)?;
29106        self.write(", '");
29107        self.write(&e.method);
29108        self.write("')");
29109        Ok(())
29110    }
29111
29112    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
29113        // DECOMPRESS(expr, 'method')
29114        self.write_keyword("DECOMPRESS");
29115        self.write("(");
29116        self.generate_expression(&e.this)?;
29117        self.write(", '");
29118        self.write(&e.method);
29119        self.write("')");
29120        Ok(())
29121    }
29122
29123    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
29124        // DECRYPT(value, passphrase [, aad [, algorithm]])
29125        self.write_keyword("DECRYPT");
29126        self.write("(");
29127        self.generate_expression(&e.this)?;
29128        if let Some(passphrase) = &e.passphrase {
29129            self.write(", ");
29130            self.generate_expression(passphrase)?;
29131        }
29132        if let Some(aad) = &e.aad {
29133            self.write(", ");
29134            self.generate_expression(aad)?;
29135        }
29136        if let Some(method) = &e.encryption_method {
29137            self.write(", ");
29138            self.generate_expression(method)?;
29139        }
29140        self.write(")");
29141        Ok(())
29142    }
29143
29144    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
29145        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
29146        self.write_keyword("DECRYPT_RAW");
29147        self.write("(");
29148        self.generate_expression(&e.this)?;
29149        if let Some(key) = &e.key {
29150            self.write(", ");
29151            self.generate_expression(key)?;
29152        }
29153        if let Some(iv) = &e.iv {
29154            self.write(", ");
29155            self.generate_expression(iv)?;
29156        }
29157        if let Some(aad) = &e.aad {
29158            self.write(", ");
29159            self.generate_expression(aad)?;
29160        }
29161        if let Some(method) = &e.encryption_method {
29162            self.write(", ");
29163            self.generate_expression(method)?;
29164        }
29165        self.write(")");
29166        Ok(())
29167    }
29168
29169    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
29170        // DEFINER = user
29171        self.write_keyword("DEFINER");
29172        self.write(" = ");
29173        self.generate_expression(&e.this)?;
29174        Ok(())
29175    }
29176
29177    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
29178        // Python: DETACH[DATABASE IF EXISTS] this
29179        self.write_keyword("DETACH");
29180        if e.exists {
29181            self.write_keyword(" DATABASE IF EXISTS");
29182        }
29183        self.write_space();
29184        self.generate_expression(&e.this)?;
29185        Ok(())
29186    }
29187
29188    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
29189        let property_name = match e.this.as_ref() {
29190            Expression::Identifier(id) => id.name.as_str(),
29191            Expression::Var(v) => v.this.as_str(),
29192            _ => "DICTIONARY",
29193        };
29194        self.write_keyword(property_name);
29195        self.write("(");
29196        self.write(&e.kind);
29197        if let Some(settings) = &e.settings {
29198            self.write("(");
29199            if let Expression::Tuple(t) = settings.as_ref() {
29200                if self.config.pretty && !t.expressions.is_empty() {
29201                    self.write_newline();
29202                    self.indent_level += 1;
29203                    for (i, pair) in t.expressions.iter().enumerate() {
29204                        if i > 0 {
29205                            self.write(",");
29206                            self.write_newline();
29207                        }
29208                        self.write_indent();
29209                        if let Expression::Tuple(pair_tuple) = pair {
29210                            if let Some(k) = pair_tuple.expressions.first() {
29211                                self.generate_expression(k)?;
29212                            }
29213                            if let Some(v) = pair_tuple.expressions.get(1) {
29214                                self.write(" ");
29215                                self.generate_expression(v)?;
29216                            }
29217                        } else {
29218                            self.generate_expression(pair)?;
29219                        }
29220                    }
29221                    self.indent_level -= 1;
29222                    self.write_newline();
29223                    self.write_indent();
29224                } else {
29225                    for (i, pair) in t.expressions.iter().enumerate() {
29226                        if i > 0 {
29227                            // ClickHouse dict properties are space-separated, not comma-separated
29228                            self.write(" ");
29229                        }
29230                        if let Expression::Tuple(pair_tuple) = pair {
29231                            if let Some(k) = pair_tuple.expressions.first() {
29232                                self.generate_expression(k)?;
29233                            }
29234                            if let Some(v) = pair_tuple.expressions.get(1) {
29235                                self.write(" ");
29236                                self.generate_expression(v)?;
29237                            }
29238                        } else {
29239                            self.generate_expression(pair)?;
29240                        }
29241                    }
29242                }
29243            } else {
29244                self.generate_expression(settings)?;
29245            }
29246            self.write(")");
29247        } else {
29248            // No settings but kind had parens (e.g., SOURCE(NULL()), LAYOUT(FLAT()))
29249            self.write("()");
29250        }
29251        self.write(")");
29252        Ok(())
29253    }
29254
29255    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
29256        let property_name = match e.this.as_ref() {
29257            Expression::Identifier(id) => id.name.as_str(),
29258            Expression::Var(v) => v.this.as_str(),
29259            _ => "RANGE",
29260        };
29261        self.write_keyword(property_name);
29262        self.write("(");
29263        if let Some(min) = &e.min {
29264            self.write_keyword("MIN");
29265            self.write_space();
29266            self.generate_expression(min)?;
29267        }
29268        if let Some(max) = &e.max {
29269            self.write_space();
29270            self.write_keyword("MAX");
29271            self.write_space();
29272            self.generate_expression(max)?;
29273        }
29274        self.write(")");
29275        Ok(())
29276    }
29277
29278    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
29279        // Python: {local}DIRECTORY {this}{row_format}
29280        if e.local.is_some() {
29281            self.write_keyword("LOCAL ");
29282        }
29283        self.write_keyword("DIRECTORY");
29284        self.write_space();
29285        self.generate_expression(&e.this)?;
29286        if let Some(row_format) = &e.row_format {
29287            self.write_space();
29288            self.generate_expression(row_format)?;
29289        }
29290        Ok(())
29291    }
29292
29293    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
29294        // Redshift: DISTKEY(column)
29295        self.write_keyword("DISTKEY");
29296        self.write("(");
29297        self.generate_expression(&e.this)?;
29298        self.write(")");
29299        Ok(())
29300    }
29301
29302    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
29303        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
29304        self.write_keyword("DISTSTYLE");
29305        self.write_space();
29306        self.generate_expression(&e.this)?;
29307        Ok(())
29308    }
29309
29310    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
29311        // Python: "DISTRIBUTE BY" expressions
29312        self.write_keyword("DISTRIBUTE BY");
29313        self.write_space();
29314        for (i, expr) in e.expressions.iter().enumerate() {
29315            if i > 0 {
29316                self.write(", ");
29317            }
29318            self.generate_expression(expr)?;
29319        }
29320        Ok(())
29321    }
29322
29323    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
29324        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
29325        self.write_keyword("DISTRIBUTED BY");
29326        self.write_space();
29327        self.write(&e.kind);
29328        if !e.expressions.is_empty() {
29329            self.write(" (");
29330            for (i, expr) in e.expressions.iter().enumerate() {
29331                if i > 0 {
29332                    self.write(", ");
29333                }
29334                self.generate_expression(expr)?;
29335            }
29336            self.write(")");
29337        }
29338        if let Some(buckets) = &e.buckets {
29339            self.write_space();
29340            self.write_keyword("BUCKETS");
29341            self.write_space();
29342            self.generate_expression(buckets)?;
29343        }
29344        if let Some(order) = &e.order {
29345            self.write_space();
29346            self.generate_expression(order)?;
29347        }
29348        Ok(())
29349    }
29350
29351    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
29352        // DOT_PRODUCT(vector1, vector2)
29353        self.write_keyword("DOT_PRODUCT");
29354        self.write("(");
29355        self.generate_expression(&e.this)?;
29356        self.write(", ");
29357        self.generate_expression(&e.expression)?;
29358        self.write(")");
29359        Ok(())
29360    }
29361
29362    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
29363        // Python: DROP{IF EXISTS }expressions
29364        self.write_keyword("DROP");
29365        if e.exists {
29366            self.write_keyword(" IF EXISTS ");
29367        } else {
29368            self.write_space();
29369        }
29370        for (i, expr) in e.expressions.iter().enumerate() {
29371            if i > 0 {
29372                self.write(", ");
29373            }
29374            self.generate_expression(expr)?;
29375        }
29376        Ok(())
29377    }
29378
29379    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
29380        // Python: DUPLICATE KEY (expressions)
29381        self.write_keyword("DUPLICATE KEY");
29382        self.write(" (");
29383        for (i, expr) in e.expressions.iter().enumerate() {
29384            if i > 0 {
29385                self.write(", ");
29386            }
29387            self.generate_expression(expr)?;
29388        }
29389        self.write(")");
29390        Ok(())
29391    }
29392
29393    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
29394        // ELT(index, str1, str2, ...)
29395        self.write_keyword("ELT");
29396        self.write("(");
29397        self.generate_expression(&e.this)?;
29398        for expr in &e.expressions {
29399            self.write(", ");
29400            self.generate_expression(expr)?;
29401        }
29402        self.write(")");
29403        Ok(())
29404    }
29405
29406    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
29407        // ENCODE(string, charset)
29408        self.write_keyword("ENCODE");
29409        self.write("(");
29410        self.generate_expression(&e.this)?;
29411        if let Some(charset) = &e.charset {
29412            self.write(", ");
29413            self.generate_expression(charset)?;
29414        }
29415        self.write(")");
29416        Ok(())
29417    }
29418
29419    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
29420        // Python: [KEY ]ENCODE this [properties]
29421        if e.key.is_some() {
29422            self.write_keyword("KEY ");
29423        }
29424        self.write_keyword("ENCODE");
29425        self.write_space();
29426        self.generate_expression(&e.this)?;
29427        if !e.properties.is_empty() {
29428            self.write(" (");
29429            for (i, prop) in e.properties.iter().enumerate() {
29430                if i > 0 {
29431                    self.write(", ");
29432                }
29433                self.generate_expression(prop)?;
29434            }
29435            self.write(")");
29436        }
29437        Ok(())
29438    }
29439
29440    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
29441        // ENCRYPT(value, passphrase [, aad [, algorithm]])
29442        self.write_keyword("ENCRYPT");
29443        self.write("(");
29444        self.generate_expression(&e.this)?;
29445        if let Some(passphrase) = &e.passphrase {
29446            self.write(", ");
29447            self.generate_expression(passphrase)?;
29448        }
29449        if let Some(aad) = &e.aad {
29450            self.write(", ");
29451            self.generate_expression(aad)?;
29452        }
29453        if let Some(method) = &e.encryption_method {
29454            self.write(", ");
29455            self.generate_expression(method)?;
29456        }
29457        self.write(")");
29458        Ok(())
29459    }
29460
29461    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
29462        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
29463        self.write_keyword("ENCRYPT_RAW");
29464        self.write("(");
29465        self.generate_expression(&e.this)?;
29466        if let Some(key) = &e.key {
29467            self.write(", ");
29468            self.generate_expression(key)?;
29469        }
29470        if let Some(iv) = &e.iv {
29471            self.write(", ");
29472            self.generate_expression(iv)?;
29473        }
29474        if let Some(aad) = &e.aad {
29475            self.write(", ");
29476            self.generate_expression(aad)?;
29477        }
29478        if let Some(method) = &e.encryption_method {
29479            self.write(", ");
29480            self.generate_expression(method)?;
29481        }
29482        self.write(")");
29483        Ok(())
29484    }
29485
29486    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
29487        // MySQL: ENGINE = InnoDB
29488        self.write_keyword("ENGINE");
29489        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
29490            self.write("=");
29491        } else {
29492            self.write(" = ");
29493        }
29494        self.generate_expression(&e.this)?;
29495        Ok(())
29496    }
29497
29498    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
29499        // ENVIRONMENT (expressions)
29500        self.write_keyword("ENVIRONMENT");
29501        self.write(" (");
29502        for (i, expr) in e.expressions.iter().enumerate() {
29503            if i > 0 {
29504                self.write(", ");
29505            }
29506            self.generate_expression(expr)?;
29507        }
29508        self.write(")");
29509        Ok(())
29510    }
29511
29512    fn generate_ephemeral_column_constraint(
29513        &mut self,
29514        e: &EphemeralColumnConstraint,
29515    ) -> Result<()> {
29516        // MySQL: EPHEMERAL [expr]
29517        self.write_keyword("EPHEMERAL");
29518        if let Some(this) = &e.this {
29519            self.write_space();
29520            self.generate_expression(this)?;
29521        }
29522        Ok(())
29523    }
29524
29525    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
29526        // Snowflake: EQUAL_NULL(a, b)
29527        self.write_keyword("EQUAL_NULL");
29528        self.write("(");
29529        self.generate_expression(&e.this)?;
29530        self.write(", ");
29531        self.generate_expression(&e.expression)?;
29532        self.write(")");
29533        Ok(())
29534    }
29535
29536    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
29537        use crate::dialects::DialectType;
29538
29539        // PostgreSQL uses <-> operator syntax
29540        match self.config.dialect {
29541            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
29542                self.generate_expression(&e.this)?;
29543                self.write(" <-> ");
29544                self.generate_expression(&e.expression)?;
29545            }
29546            _ => {
29547                // Other dialects use EUCLIDEAN_DISTANCE function
29548                self.write_keyword("EUCLIDEAN_DISTANCE");
29549                self.write("(");
29550                self.generate_expression(&e.this)?;
29551                self.write(", ");
29552                self.generate_expression(&e.expression)?;
29553                self.write(")");
29554            }
29555        }
29556        Ok(())
29557    }
29558
29559    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
29560        // EXECUTE AS CALLER|OWNER|user
29561        self.write_keyword("EXECUTE AS");
29562        self.write_space();
29563        self.generate_expression(&e.this)?;
29564        Ok(())
29565    }
29566
29567    fn generate_export(&mut self, e: &Export) -> Result<()> {
29568        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
29569        self.write_keyword("EXPORT DATA");
29570        if let Some(connection) = &e.connection {
29571            self.write_space();
29572            self.write_keyword("WITH CONNECTION");
29573            self.write_space();
29574            self.generate_expression(connection)?;
29575        }
29576        if !e.options.is_empty() {
29577            self.write_space();
29578            self.generate_options_clause(&e.options)?;
29579        }
29580        self.write_space();
29581        self.write_keyword("AS");
29582        self.write_space();
29583        self.generate_expression(&e.this)?;
29584        Ok(())
29585    }
29586
29587    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
29588        // EXTERNAL [this]
29589        self.write_keyword("EXTERNAL");
29590        if let Some(this) = &e.this {
29591            self.write_space();
29592            self.generate_expression(this)?;
29593        }
29594        Ok(())
29595    }
29596
29597    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
29598        // Python: {no}FALLBACK{protection}
29599        if e.no.is_some() {
29600            self.write_keyword("NO ");
29601        }
29602        self.write_keyword("FALLBACK");
29603        if e.protection.is_some() {
29604            self.write_keyword(" PROTECTION");
29605        }
29606        Ok(())
29607    }
29608
29609    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
29610        // BigQuery: FARM_FINGERPRINT(value)
29611        self.write_keyword("FARM_FINGERPRINT");
29612        self.write("(");
29613        for (i, expr) in e.expressions.iter().enumerate() {
29614            if i > 0 {
29615                self.write(", ");
29616            }
29617            self.generate_expression(expr)?;
29618        }
29619        self.write(")");
29620        Ok(())
29621    }
29622
29623    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
29624        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
29625        self.write_keyword("FEATURES_AT_TIME");
29626        self.write("(");
29627        self.generate_expression(&e.this)?;
29628        if let Some(time) = &e.time {
29629            self.write(", ");
29630            self.generate_expression(time)?;
29631        }
29632        if let Some(num_rows) = &e.num_rows {
29633            self.write(", ");
29634            self.generate_expression(num_rows)?;
29635        }
29636        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
29637            self.write(", ");
29638            self.generate_expression(ignore_nulls)?;
29639        }
29640        self.write(")");
29641        Ok(())
29642    }
29643
29644    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
29645        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
29646        let use_limit = !e.percent
29647            && !e.with_ties
29648            && e.count.is_some()
29649            && matches!(
29650                self.config.dialect,
29651                Some(DialectType::Spark)
29652                    | Some(DialectType::Hive)
29653                    | Some(DialectType::DuckDB)
29654                    | Some(DialectType::SQLite)
29655                    | Some(DialectType::MySQL)
29656                    | Some(DialectType::BigQuery)
29657                    | Some(DialectType::Databricks)
29658                    | Some(DialectType::StarRocks)
29659                    | Some(DialectType::Doris)
29660                    | Some(DialectType::Athena)
29661                    | Some(DialectType::ClickHouse)
29662            );
29663
29664        if use_limit {
29665            self.write_keyword("LIMIT");
29666            self.write_space();
29667            self.generate_expression(e.count.as_ref().unwrap())?;
29668            return Ok(());
29669        }
29670
29671        // Python: FETCH direction count limit_options
29672        self.write_keyword("FETCH");
29673        if !e.direction.is_empty() {
29674            self.write_space();
29675            self.write_keyword(&e.direction);
29676        }
29677        if let Some(count) = &e.count {
29678            self.write_space();
29679            self.generate_expression(count)?;
29680        }
29681        // Generate PERCENT, ROWS, WITH TIES/ONLY
29682        if e.percent {
29683            self.write_keyword(" PERCENT");
29684        }
29685        if e.rows {
29686            self.write_keyword(" ROWS");
29687        }
29688        if e.with_ties {
29689            self.write_keyword(" WITH TIES");
29690        } else if e.rows {
29691            self.write_keyword(" ONLY");
29692        } else {
29693            self.write_keyword(" ROWS ONLY");
29694        }
29695        Ok(())
29696    }
29697
29698    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
29699        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
29700        // For Spark/Databricks without hive_format: USING this
29701        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
29702        if e.hive_format.is_some() {
29703            // Hive format: STORED AS ...
29704            self.write_keyword("STORED AS");
29705            self.write_space();
29706            if let Some(this) = &e.this {
29707                // Uppercase the format name (e.g., parquet -> PARQUET)
29708                if let Expression::Identifier(id) = this.as_ref() {
29709                    self.write_keyword(&id.name.to_ascii_uppercase());
29710                } else {
29711                    self.generate_expression(this)?;
29712                }
29713            }
29714        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
29715            // Hive: STORED AS format
29716            self.write_keyword("STORED AS");
29717            self.write_space();
29718            if let Some(this) = &e.this {
29719                if let Expression::Identifier(id) = this.as_ref() {
29720                    self.write_keyword(&id.name.to_ascii_uppercase());
29721                } else {
29722                    self.generate_expression(this)?;
29723                }
29724            }
29725        } else if matches!(
29726            self.config.dialect,
29727            Some(DialectType::Spark) | Some(DialectType::Databricks)
29728        ) {
29729            // Spark/Databricks: USING format (e.g., USING DELTA)
29730            self.write_keyword("USING");
29731            self.write_space();
29732            if let Some(this) = &e.this {
29733                self.generate_expression(this)?;
29734            }
29735        } else {
29736            // Snowflake/standard format
29737            self.write_keyword("FILE_FORMAT");
29738            self.write(" = ");
29739            if let Some(this) = &e.this {
29740                self.generate_expression(this)?;
29741            } else if !e.expressions.is_empty() {
29742                self.write("(");
29743                for (i, expr) in e.expressions.iter().enumerate() {
29744                    if i > 0 {
29745                        self.write(", ");
29746                    }
29747                    self.generate_expression(expr)?;
29748                }
29749                self.write(")");
29750            }
29751        }
29752        Ok(())
29753    }
29754
29755    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
29756        // agg_func FILTER(WHERE condition)
29757        self.generate_expression(&e.this)?;
29758        self.write_space();
29759        self.write_keyword("FILTER");
29760        self.write("(");
29761        self.write_keyword("WHERE");
29762        self.write_space();
29763        self.generate_expression(&e.expression)?;
29764        self.write(")");
29765        Ok(())
29766    }
29767
29768    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
29769        // FLOAT64(this) or FLOAT64(this, expression)
29770        self.write_keyword("FLOAT64");
29771        self.write("(");
29772        self.generate_expression(&e.this)?;
29773        if let Some(expr) = &e.expression {
29774            self.write(", ");
29775            self.generate_expression(expr)?;
29776        }
29777        self.write(")");
29778        Ok(())
29779    }
29780
29781    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
29782        // FOR this DO expression
29783        self.write_keyword("FOR");
29784        self.write_space();
29785        self.generate_expression(&e.this)?;
29786        self.write_space();
29787        self.write_keyword("DO");
29788        self.write_space();
29789        self.generate_expression(&e.expression)?;
29790        Ok(())
29791    }
29792
29793    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
29794        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
29795        self.write_keyword("FOREIGN KEY");
29796        if !e.expressions.is_empty() {
29797            self.write(" (");
29798            for (i, expr) in e.expressions.iter().enumerate() {
29799                if i > 0 {
29800                    self.write(", ");
29801                }
29802                self.generate_expression(expr)?;
29803            }
29804            self.write(")");
29805        }
29806        if let Some(reference) = &e.reference {
29807            self.write_space();
29808            self.generate_expression(reference)?;
29809        }
29810        if let Some(delete) = &e.delete {
29811            self.write_space();
29812            self.write_keyword("ON DELETE");
29813            self.write_space();
29814            self.generate_expression(delete)?;
29815        }
29816        if let Some(update) = &e.update {
29817            self.write_space();
29818            self.write_keyword("ON UPDATE");
29819            self.write_space();
29820            self.generate_expression(update)?;
29821        }
29822        if !e.options.is_empty() {
29823            self.write_space();
29824            for (i, opt) in e.options.iter().enumerate() {
29825                if i > 0 {
29826                    self.write_space();
29827                }
29828                self.generate_expression(opt)?;
29829            }
29830        }
29831        Ok(())
29832    }
29833
29834    fn generate_format(&mut self, e: &Format) -> Result<()> {
29835        // FORMAT(this, expressions...)
29836        self.write_keyword("FORMAT");
29837        self.write("(");
29838        self.generate_expression(&e.this)?;
29839        for expr in &e.expressions {
29840            self.write(", ");
29841            self.generate_expression(expr)?;
29842        }
29843        self.write(")");
29844        Ok(())
29845    }
29846
29847    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
29848        // Teradata: column (FORMAT 'format_string')
29849        self.generate_expression(&e.this)?;
29850        self.write(" (");
29851        self.write_keyword("FORMAT");
29852        self.write(" '");
29853        self.write(&e.format);
29854        self.write("')");
29855        Ok(())
29856    }
29857
29858    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
29859        // Python: FREESPACE=this[PERCENT]
29860        self.write_keyword("FREESPACE");
29861        self.write("=");
29862        self.generate_expression(&e.this)?;
29863        if e.percent.is_some() {
29864            self.write_keyword(" PERCENT");
29865        }
29866        Ok(())
29867    }
29868
29869    fn generate_from(&mut self, e: &From) -> Result<()> {
29870        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
29871        self.write_keyword("FROM");
29872        self.write_space();
29873
29874        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
29875        // But keep commas when TABLESAMPLE is present
29876        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
29877        use crate::dialects::DialectType;
29878        let has_tablesample = e
29879            .expressions
29880            .iter()
29881            .any(|expr| matches!(expr, Expression::TableSample(_)));
29882        let is_cross_join_dialect = matches!(
29883            self.config.dialect,
29884            Some(DialectType::BigQuery)
29885                | Some(DialectType::Hive)
29886                | Some(DialectType::Spark)
29887                | Some(DialectType::Databricks)
29888                | Some(DialectType::SQLite)
29889                | Some(DialectType::ClickHouse)
29890        );
29891        let source_is_same_as_target2 = self.config.source_dialect.is_some()
29892            && self.config.source_dialect == self.config.dialect;
29893        let source_is_cross_join_dialect2 = matches!(
29894            self.config.source_dialect,
29895            Some(DialectType::BigQuery)
29896                | Some(DialectType::Hive)
29897                | Some(DialectType::Spark)
29898                | Some(DialectType::Databricks)
29899                | Some(DialectType::SQLite)
29900                | Some(DialectType::ClickHouse)
29901        );
29902        let use_cross_join = !has_tablesample
29903            && is_cross_join_dialect
29904            && (source_is_same_as_target2
29905                || source_is_cross_join_dialect2
29906                || self.config.source_dialect.is_none());
29907
29908        // Snowflake wraps standalone VALUES in FROM clause with parentheses
29909        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
29910
29911        for (i, expr) in e.expressions.iter().enumerate() {
29912            if i > 0 {
29913                if use_cross_join {
29914                    self.write(" CROSS JOIN ");
29915                } else {
29916                    self.write(", ");
29917                }
29918            }
29919            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
29920                self.write("(");
29921                self.generate_expression(expr)?;
29922                self.write(")");
29923            } else {
29924                self.generate_expression(expr)?;
29925            }
29926            // Output leading comments that were on the table name before FROM
29927            // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
29928            let leading = Self::extract_table_leading_comments(expr);
29929            for comment in &leading {
29930                self.write_space();
29931                self.write_formatted_comment(comment);
29932            }
29933        }
29934        Ok(())
29935    }
29936
29937    /// Extract leading_comments from a table expression (possibly wrapped in PIVOT/UNPIVOT)
29938    fn extract_table_leading_comments(expr: &Expression) -> Vec<String> {
29939        match expr {
29940            Expression::Table(t) => t.leading_comments.clone(),
29941            Expression::Pivot(p) => {
29942                if let Expression::Table(t) = &p.this {
29943                    t.leading_comments.clone()
29944                } else {
29945                    Vec::new()
29946                }
29947            }
29948            _ => Vec::new(),
29949        }
29950    }
29951
29952    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
29953        // FROM_BASE(this, expression) - convert from base N
29954        self.write_keyword("FROM_BASE");
29955        self.write("(");
29956        self.generate_expression(&e.this)?;
29957        self.write(", ");
29958        self.generate_expression(&e.expression)?;
29959        self.write(")");
29960        Ok(())
29961    }
29962
29963    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
29964        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
29965        self.generate_expression(&e.this)?;
29966        if let Some(zone) = &e.zone {
29967            self.write_space();
29968            self.write_keyword("AT TIME ZONE");
29969            self.write_space();
29970            self.generate_expression(zone)?;
29971            self.write_space();
29972            self.write_keyword("AT TIME ZONE");
29973            self.write(" 'UTC'");
29974        }
29975        Ok(())
29976    }
29977
29978    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
29979        // GAP_FILL(this, ts_column, bucket_width, ...)
29980        self.write_keyword("GAP_FILL");
29981        self.write("(");
29982        self.generate_expression(&e.this)?;
29983        if let Some(ts_column) = &e.ts_column {
29984            self.write(", ");
29985            self.generate_expression(ts_column)?;
29986        }
29987        if let Some(bucket_width) = &e.bucket_width {
29988            self.write(", ");
29989            self.generate_expression(bucket_width)?;
29990        }
29991        if let Some(partitioning_columns) = &e.partitioning_columns {
29992            self.write(", ");
29993            self.generate_expression(partitioning_columns)?;
29994        }
29995        if let Some(value_columns) = &e.value_columns {
29996            self.write(", ");
29997            self.generate_expression(value_columns)?;
29998        }
29999        self.write(")");
30000        Ok(())
30001    }
30002
30003    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
30004        // GENERATE_DATE_ARRAY(start, end, step)
30005        self.write_keyword("GENERATE_DATE_ARRAY");
30006        self.write("(");
30007        let mut first = true;
30008        if let Some(start) = &e.start {
30009            self.generate_expression(start)?;
30010            first = false;
30011        }
30012        if let Some(end) = &e.end {
30013            if !first {
30014                self.write(", ");
30015            }
30016            self.generate_expression(end)?;
30017            first = false;
30018        }
30019        if let Some(step) = &e.step {
30020            if !first {
30021                self.write(", ");
30022            }
30023            self.generate_expression(step)?;
30024        }
30025        self.write(")");
30026        Ok(())
30027    }
30028
30029    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
30030        // ML.GENERATE_EMBEDDING(model, content, params)
30031        self.write_keyword("ML.GENERATE_EMBEDDING");
30032        self.write("(");
30033        self.generate_expression(&e.this)?;
30034        self.write(", ");
30035        self.generate_expression(&e.expression)?;
30036        if let Some(params) = &e.params_struct {
30037            self.write(", ");
30038            self.generate_expression(params)?;
30039        }
30040        self.write(")");
30041        Ok(())
30042    }
30043
30044    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
30045        // Dialect-specific function name
30046        let fn_name = match self.config.dialect {
30047            Some(DialectType::Presto)
30048            | Some(DialectType::Trino)
30049            | Some(DialectType::Athena)
30050            | Some(DialectType::Spark)
30051            | Some(DialectType::Databricks)
30052            | Some(DialectType::Hive) => "SEQUENCE",
30053            _ => "GENERATE_SERIES",
30054        };
30055        self.write_keyword(fn_name);
30056        self.write("(");
30057        let mut first = true;
30058        if let Some(start) = &e.start {
30059            self.generate_expression(start)?;
30060            first = false;
30061        }
30062        if let Some(end) = &e.end {
30063            if !first {
30064                self.write(", ");
30065            }
30066            self.generate_expression(end)?;
30067            first = false;
30068        }
30069        if let Some(step) = &e.step {
30070            if !first {
30071                self.write(", ");
30072            }
30073            // For Presto/Trino: convert WEEK intervals to DAY multiples
30074            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
30075            if matches!(
30076                self.config.dialect,
30077                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
30078            ) {
30079                if let Some(converted) = self.convert_week_interval_to_day(step) {
30080                    self.generate_expression(&converted)?;
30081                } else {
30082                    self.generate_expression(step)?;
30083                }
30084            } else {
30085                self.generate_expression(step)?;
30086            }
30087        }
30088        self.write(")");
30089        Ok(())
30090    }
30091
30092    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
30093    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
30094    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
30095        use crate::expressions::*;
30096        if let Expression::Interval(ref iv) = expr {
30097            // Check for structured WEEK unit
30098            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
30099                unit: IntervalUnit::Week,
30100                ..
30101            }) = &iv.unit
30102            {
30103                // Value is in iv.this
30104                let count = match &iv.this {
30105                    Some(Expression::Literal(lit)) => match lit.as_ref() {
30106                        Literal::String(s) | Literal::Number(s) => s.clone(),
30107                        _ => return None,
30108                    },
30109                    _ => return None,
30110                };
30111                (true, count)
30112            } else if iv.unit.is_none() {
30113                // Check for string-encoded interval like "1 WEEK"
30114                if let Some(Expression::Literal(lit)) = &iv.this {
30115                    if let Literal::String(s) = lit.as_ref() {
30116                        let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
30117                        if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
30118                            (true, parts[0].to_string())
30119                        } else {
30120                            (false, String::new())
30121                        }
30122                    } else {
30123                        (false, String::new())
30124                    }
30125                } else {
30126                    (false, String::new())
30127                }
30128            } else {
30129                (false, String::new())
30130            };
30131
30132            if is_week {
30133                // Build: (N * INTERVAL '7' DAY)
30134                let count_expr = Expression::Literal(Box::new(Literal::Number(count_str)));
30135                let day_interval = Expression::Interval(Box::new(Interval {
30136                    this: Some(Expression::Literal(Box::new(Literal::String(
30137                        "7".to_string(),
30138                    )))),
30139                    unit: Some(IntervalUnitSpec::Simple {
30140                        unit: IntervalUnit::Day,
30141                        use_plural: false,
30142                    }),
30143                }));
30144                let mul = Expression::Mul(Box::new(BinaryOp {
30145                    left: count_expr,
30146                    right: day_interval,
30147                    left_comments: vec![],
30148                    operator_comments: vec![],
30149                    trailing_comments: vec![],
30150                    inferred_type: None,
30151                }));
30152                return Some(Expression::Paren(Box::new(Paren {
30153                    this: mul,
30154                    trailing_comments: vec![],
30155                })));
30156            }
30157        }
30158        None
30159    }
30160
30161    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
30162        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
30163        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
30164        self.write("(");
30165        let mut first = true;
30166        if let Some(start) = &e.start {
30167            self.generate_expression(start)?;
30168            first = false;
30169        }
30170        if let Some(end) = &e.end {
30171            if !first {
30172                self.write(", ");
30173            }
30174            self.generate_expression(end)?;
30175            first = false;
30176        }
30177        if let Some(step) = &e.step {
30178            if !first {
30179                self.write(", ");
30180            }
30181            self.generate_expression(step)?;
30182        }
30183        self.write(")");
30184        Ok(())
30185    }
30186
30187    fn generate_generated_as_identity_column_constraint(
30188        &mut self,
30189        e: &GeneratedAsIdentityColumnConstraint,
30190    ) -> Result<()> {
30191        use crate::dialects::DialectType;
30192
30193        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
30194        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
30195            self.write_keyword("AUTOINCREMENT");
30196            if let Some(start) = &e.start {
30197                self.write_keyword(" START ");
30198                self.generate_expression(start)?;
30199            }
30200            if let Some(increment) = &e.increment {
30201                self.write_keyword(" INCREMENT ");
30202                self.generate_expression(increment)?;
30203            }
30204            return Ok(());
30205        }
30206
30207        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
30208        self.write_keyword("GENERATED");
30209        if let Some(this) = &e.this {
30210            // Check if it's a truthy boolean expression
30211            if let Expression::Boolean(b) = this.as_ref() {
30212                if b.value {
30213                    self.write_keyword(" ALWAYS");
30214                } else {
30215                    self.write_keyword(" BY DEFAULT");
30216                    if e.on_null.is_some() {
30217                        self.write_keyword(" ON NULL");
30218                    }
30219                }
30220            } else {
30221                self.write_keyword(" ALWAYS");
30222            }
30223        }
30224        self.write_keyword(" AS IDENTITY");
30225        // Add sequence options if any
30226        let has_options = e.start.is_some()
30227            || e.increment.is_some()
30228            || e.minvalue.is_some()
30229            || e.maxvalue.is_some();
30230        if has_options {
30231            self.write(" (");
30232            let mut first = true;
30233            if let Some(start) = &e.start {
30234                self.write_keyword("START WITH ");
30235                self.generate_expression(start)?;
30236                first = false;
30237            }
30238            if let Some(increment) = &e.increment {
30239                if !first {
30240                    self.write(" ");
30241                }
30242                self.write_keyword("INCREMENT BY ");
30243                self.generate_expression(increment)?;
30244                first = false;
30245            }
30246            if let Some(minvalue) = &e.minvalue {
30247                if !first {
30248                    self.write(" ");
30249                }
30250                self.write_keyword("MINVALUE ");
30251                self.generate_expression(minvalue)?;
30252                first = false;
30253            }
30254            if let Some(maxvalue) = &e.maxvalue {
30255                if !first {
30256                    self.write(" ");
30257                }
30258                self.write_keyword("MAXVALUE ");
30259                self.generate_expression(maxvalue)?;
30260            }
30261            self.write(")");
30262        }
30263        Ok(())
30264    }
30265
30266    fn generate_generated_as_row_column_constraint(
30267        &mut self,
30268        e: &GeneratedAsRowColumnConstraint,
30269    ) -> Result<()> {
30270        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
30271        self.write_keyword("GENERATED ALWAYS AS ROW ");
30272        if e.start.is_some() {
30273            self.write_keyword("START");
30274        } else {
30275            self.write_keyword("END");
30276        }
30277        if e.hidden.is_some() {
30278            self.write_keyword(" HIDDEN");
30279        }
30280        Ok(())
30281    }
30282
30283    fn generate_get(&mut self, e: &Get) -> Result<()> {
30284        // GET this target properties
30285        self.write_keyword("GET");
30286        self.write_space();
30287        self.generate_expression(&e.this)?;
30288        if let Some(target) = &e.target {
30289            self.write_space();
30290            self.generate_expression(target)?;
30291        }
30292        for prop in &e.properties {
30293            self.write_space();
30294            self.generate_expression(prop)?;
30295        }
30296        Ok(())
30297    }
30298
30299    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
30300        // GetExtract generates bracket access: this[expression]
30301        self.generate_expression(&e.this)?;
30302        self.write("[");
30303        self.generate_expression(&e.expression)?;
30304        self.write("]");
30305        Ok(())
30306    }
30307
30308    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
30309        // GETBIT(this, expression) or GET_BIT(this, expression)
30310        self.write_keyword("GETBIT");
30311        self.write("(");
30312        self.generate_expression(&e.this)?;
30313        self.write(", ");
30314        self.generate_expression(&e.expression)?;
30315        self.write(")");
30316        Ok(())
30317    }
30318
30319    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
30320        // [ROLE|GROUP|SHARE] name (e.g., "ROLE admin", "GROUP qa_users", "SHARE s1", or just "user1")
30321        if e.is_role {
30322            self.write_keyword("ROLE");
30323            self.write_space();
30324        } else if e.is_group {
30325            self.write_keyword("GROUP");
30326            self.write_space();
30327        } else if e.is_share {
30328            self.write_keyword("SHARE");
30329            self.write_space();
30330        }
30331        self.write(&e.name.name);
30332        Ok(())
30333    }
30334
30335    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
30336        // privilege(columns) or just privilege
30337        self.generate_expression(&e.this)?;
30338        if !e.expressions.is_empty() {
30339            self.write("(");
30340            for (i, expr) in e.expressions.iter().enumerate() {
30341                if i > 0 {
30342                    self.write(", ");
30343                }
30344                self.generate_expression(expr)?;
30345            }
30346            self.write(")");
30347        }
30348        Ok(())
30349    }
30350
30351    fn generate_group(&mut self, e: &Group) -> Result<()> {
30352        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
30353        self.write_keyword("GROUP BY");
30354        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
30355        match e.all {
30356            Some(true) => {
30357                self.write_space();
30358                self.write_keyword("ALL");
30359            }
30360            Some(false) => {
30361                self.write_space();
30362                self.write_keyword("DISTINCT");
30363            }
30364            None => {}
30365        }
30366        if !e.expressions.is_empty() {
30367            self.write_space();
30368            for (i, expr) in e.expressions.iter().enumerate() {
30369                if i > 0 {
30370                    self.write(", ");
30371                }
30372                self.generate_expression(expr)?;
30373            }
30374        }
30375        // Handle CUBE, ROLLUP, GROUPING SETS
30376        if let Some(cube) = &e.cube {
30377            if !e.expressions.is_empty() {
30378                self.write(", ");
30379            } else {
30380                self.write_space();
30381            }
30382            self.generate_expression(cube)?;
30383        }
30384        if let Some(rollup) = &e.rollup {
30385            if !e.expressions.is_empty() || e.cube.is_some() {
30386                self.write(", ");
30387            } else {
30388                self.write_space();
30389            }
30390            self.generate_expression(rollup)?;
30391        }
30392        if let Some(grouping_sets) = &e.grouping_sets {
30393            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
30394                self.write(", ");
30395            } else {
30396                self.write_space();
30397            }
30398            self.generate_expression(grouping_sets)?;
30399        }
30400        if let Some(totals) = &e.totals {
30401            self.write_space();
30402            self.write_keyword("WITH TOTALS");
30403            self.generate_expression(totals)?;
30404        }
30405        Ok(())
30406    }
30407
30408    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
30409        // GROUP BY expressions
30410        self.write_keyword("GROUP BY");
30411        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
30412        match e.all {
30413            Some(true) => {
30414                self.write_space();
30415                self.write_keyword("ALL");
30416            }
30417            Some(false) => {
30418                self.write_space();
30419                self.write_keyword("DISTINCT");
30420            }
30421            None => {}
30422        }
30423
30424        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
30425        // These are represented as Cube/Rollup expressions with empty expressions at the end
30426        let mut trailing_cube = false;
30427        let mut trailing_rollup = false;
30428        let mut regular_expressions: Vec<&Expression> = Vec::new();
30429
30430        for expr in &e.expressions {
30431            match expr {
30432                Expression::Cube(c) if c.expressions.is_empty() => {
30433                    trailing_cube = true;
30434                }
30435                Expression::Rollup(r) if r.expressions.is_empty() => {
30436                    trailing_rollup = true;
30437                }
30438                _ => {
30439                    regular_expressions.push(expr);
30440                }
30441            }
30442        }
30443
30444        // In pretty mode, put columns on separate lines
30445        if self.config.pretty {
30446            self.write_newline();
30447            self.indent_level += 1;
30448            for (i, expr) in regular_expressions.iter().enumerate() {
30449                if i > 0 {
30450                    self.write(",");
30451                    self.write_newline();
30452                }
30453                self.write_indent();
30454                self.generate_expression(expr)?;
30455            }
30456            self.indent_level -= 1;
30457        } else {
30458            self.write_space();
30459            for (i, expr) in regular_expressions.iter().enumerate() {
30460                if i > 0 {
30461                    self.write(", ");
30462                }
30463                self.generate_expression(expr)?;
30464            }
30465        }
30466
30467        // Output trailing WITH CUBE or WITH ROLLUP
30468        if trailing_cube {
30469            self.write_space();
30470            self.write_keyword("WITH CUBE");
30471        } else if trailing_rollup {
30472            self.write_space();
30473            self.write_keyword("WITH ROLLUP");
30474        }
30475
30476        // ClickHouse: WITH TOTALS
30477        if e.totals {
30478            self.write_space();
30479            self.write_keyword("WITH TOTALS");
30480        }
30481
30482        Ok(())
30483    }
30484
30485    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
30486        // GROUPING(col1, col2, ...)
30487        self.write_keyword("GROUPING");
30488        self.write("(");
30489        for (i, expr) in e.expressions.iter().enumerate() {
30490            if i > 0 {
30491                self.write(", ");
30492            }
30493            self.generate_expression(expr)?;
30494        }
30495        self.write(")");
30496        Ok(())
30497    }
30498
30499    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
30500        // GROUPING_ID(col1, col2, ...)
30501        self.write_keyword("GROUPING_ID");
30502        self.write("(");
30503        for (i, expr) in e.expressions.iter().enumerate() {
30504            if i > 0 {
30505                self.write(", ");
30506            }
30507            self.generate_expression(expr)?;
30508        }
30509        self.write(")");
30510        Ok(())
30511    }
30512
30513    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
30514        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
30515        self.write_keyword("GROUPING SETS");
30516        self.write(" (");
30517        for (i, expr) in e.expressions.iter().enumerate() {
30518            if i > 0 {
30519                self.write(", ");
30520            }
30521            self.generate_expression(expr)?;
30522        }
30523        self.write(")");
30524        Ok(())
30525    }
30526
30527    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
30528        // HASH_AGG(this, expressions...)
30529        self.write_keyword("HASH_AGG");
30530        self.write("(");
30531        self.generate_expression(&e.this)?;
30532        for expr in &e.expressions {
30533            self.write(", ");
30534            self.generate_expression(expr)?;
30535        }
30536        self.write(")");
30537        Ok(())
30538    }
30539
30540    fn generate_having(&mut self, e: &Having) -> Result<()> {
30541        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
30542        self.write_keyword("HAVING");
30543        self.write_space();
30544        self.generate_expression(&e.this)?;
30545        Ok(())
30546    }
30547
30548    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
30549        // Python: this HAVING MAX|MIN expression
30550        self.generate_expression(&e.this)?;
30551        self.write_space();
30552        self.write_keyword("HAVING");
30553        self.write_space();
30554        if e.max.is_some() {
30555            self.write_keyword("MAX");
30556        } else {
30557            self.write_keyword("MIN");
30558        }
30559        self.write_space();
30560        self.generate_expression(&e.expression)?;
30561        Ok(())
30562    }
30563
30564    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
30565        use crate::dialects::DialectType;
30566        // DuckDB: convert dollar-tagged strings to single-quoted
30567        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
30568            // Extract the string content and output as single-quoted
30569            if let Expression::Literal(ref lit) = *e.this {
30570                if let Literal::String(ref s) = lit.as_ref() {
30571                    return self.generate_string_literal(s);
30572                }
30573            }
30574        }
30575        // PostgreSQL: preserve dollar-quoting
30576        if matches!(
30577            self.config.dialect,
30578            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
30579        ) {
30580            self.write("$");
30581            if let Some(tag) = &e.tag {
30582                self.generate_expression(tag)?;
30583            }
30584            self.write("$");
30585            self.generate_expression(&e.this)?;
30586            self.write("$");
30587            if let Some(tag) = &e.tag {
30588                self.generate_expression(tag)?;
30589            }
30590            self.write("$");
30591            return Ok(());
30592        }
30593        // Default: output as dollar-tagged
30594        self.write("$");
30595        if let Some(tag) = &e.tag {
30596            self.generate_expression(tag)?;
30597        }
30598        self.write("$");
30599        self.generate_expression(&e.this)?;
30600        self.write("$");
30601        if let Some(tag) = &e.tag {
30602            self.generate_expression(tag)?;
30603        }
30604        self.write("$");
30605        Ok(())
30606    }
30607
30608    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
30609        // HEX_ENCODE(this)
30610        self.write_keyword("HEX_ENCODE");
30611        self.write("(");
30612        self.generate_expression(&e.this)?;
30613        self.write(")");
30614        Ok(())
30615    }
30616
30617    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
30618        // Python: this (kind => expression)
30619        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
30620        match e.this.as_ref() {
30621            Expression::Identifier(id) => self.write(&id.name),
30622            other => self.generate_expression(other)?,
30623        }
30624        self.write(" (");
30625        self.write(&e.kind);
30626        self.write(" => ");
30627        self.generate_expression(&e.expression)?;
30628        self.write(")");
30629        Ok(())
30630    }
30631
30632    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
30633        // HLL(this, expressions...)
30634        self.write_keyword("HLL");
30635        self.write("(");
30636        self.generate_expression(&e.this)?;
30637        for expr in &e.expressions {
30638            self.write(", ");
30639            self.generate_expression(expr)?;
30640        }
30641        self.write(")");
30642        Ok(())
30643    }
30644
30645    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
30646        // Python: IN|OUT|IN OUT
30647        if e.input_.is_some() && e.output.is_some() {
30648            self.write_keyword("IN OUT");
30649        } else if e.input_.is_some() {
30650            self.write_keyword("IN");
30651        } else if e.output.is_some() {
30652            self.write_keyword("OUT");
30653        }
30654        Ok(())
30655    }
30656
30657    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
30658        // Python: INCLUDE this [column_def] [AS alias]
30659        self.write_keyword("INCLUDE");
30660        self.write_space();
30661        self.generate_expression(&e.this)?;
30662        if let Some(column_def) = &e.column_def {
30663            self.write_space();
30664            self.generate_expression(column_def)?;
30665        }
30666        if let Some(alias) = &e.alias {
30667            self.write_space();
30668            self.write_keyword("AS");
30669            self.write_space();
30670            self.write(alias);
30671        }
30672        Ok(())
30673    }
30674
30675    fn generate_index(&mut self, e: &Index) -> Result<()> {
30676        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
30677        if e.unique {
30678            self.write_keyword("UNIQUE");
30679            self.write_space();
30680        }
30681        if e.primary.is_some() {
30682            self.write_keyword("PRIMARY");
30683            self.write_space();
30684        }
30685        if e.amp.is_some() {
30686            self.write_keyword("AMP");
30687            self.write_space();
30688        }
30689        if e.table.is_none() {
30690            self.write_keyword("INDEX");
30691            self.write_space();
30692        }
30693        if let Some(name) = &e.this {
30694            self.generate_expression(name)?;
30695            self.write_space();
30696        }
30697        if let Some(table) = &e.table {
30698            self.write_keyword("ON");
30699            self.write_space();
30700            self.generate_expression(table)?;
30701        }
30702        if !e.params.is_empty() {
30703            self.write("(");
30704            for (i, param) in e.params.iter().enumerate() {
30705                if i > 0 {
30706                    self.write(", ");
30707                }
30708                self.generate_expression(param)?;
30709            }
30710            self.write(")");
30711        }
30712        Ok(())
30713    }
30714
30715    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
30716        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
30717        if let Some(kind) = &e.kind {
30718            self.write(kind);
30719            self.write_space();
30720        }
30721        self.write_keyword("INDEX");
30722        if let Some(this) = &e.this {
30723            self.write_space();
30724            self.generate_expression(this)?;
30725        }
30726        if let Some(index_type) = &e.index_type {
30727            self.write_space();
30728            self.write_keyword("USING");
30729            self.write_space();
30730            self.generate_expression(index_type)?;
30731        }
30732        if !e.expressions.is_empty() {
30733            self.write(" (");
30734            for (i, expr) in e.expressions.iter().enumerate() {
30735                if i > 0 {
30736                    self.write(", ");
30737                }
30738                self.generate_expression(expr)?;
30739            }
30740            self.write(")");
30741        }
30742        for opt in &e.options {
30743            self.write_space();
30744            self.generate_expression(opt)?;
30745        }
30746        Ok(())
30747    }
30748
30749    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
30750        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
30751        if let Some(key_block_size) = &e.key_block_size {
30752            self.write_keyword("KEY_BLOCK_SIZE");
30753            self.write(" = ");
30754            self.generate_expression(key_block_size)?;
30755        } else if let Some(using) = &e.using {
30756            self.write_keyword("USING");
30757            self.write_space();
30758            self.generate_expression(using)?;
30759        } else if let Some(parser) = &e.parser {
30760            self.write_keyword("WITH PARSER");
30761            self.write_space();
30762            self.generate_expression(parser)?;
30763        } else if let Some(comment) = &e.comment {
30764            self.write_keyword("COMMENT");
30765            self.write_space();
30766            self.generate_expression(comment)?;
30767        } else if let Some(visible) = &e.visible {
30768            self.generate_expression(visible)?;
30769        } else if let Some(engine_attr) = &e.engine_attr {
30770            self.write_keyword("ENGINE_ATTRIBUTE");
30771            self.write(" = ");
30772            self.generate_expression(engine_attr)?;
30773        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
30774            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
30775            self.write(" = ");
30776            self.generate_expression(secondary_engine_attr)?;
30777        }
30778        Ok(())
30779    }
30780
30781    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
30782        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
30783        if let Some(using) = &e.using {
30784            self.write_keyword("USING");
30785            self.write_space();
30786            self.generate_expression(using)?;
30787        }
30788        if !e.columns.is_empty() {
30789            self.write("(");
30790            for (i, col) in e.columns.iter().enumerate() {
30791                if i > 0 {
30792                    self.write(", ");
30793                }
30794                self.generate_expression(col)?;
30795            }
30796            self.write(")");
30797        }
30798        if let Some(partition_by) = &e.partition_by {
30799            self.write_space();
30800            self.write_keyword("PARTITION BY");
30801            self.write_space();
30802            self.generate_expression(partition_by)?;
30803        }
30804        if let Some(where_) = &e.where_ {
30805            self.write_space();
30806            self.generate_expression(where_)?;
30807        }
30808        if let Some(include) = &e.include {
30809            self.write_space();
30810            self.write_keyword("INCLUDE");
30811            self.write(" (");
30812            self.generate_expression(include)?;
30813            self.write(")");
30814        }
30815        if let Some(with_storage) = &e.with_storage {
30816            self.write_space();
30817            self.write_keyword("WITH");
30818            self.write(" (");
30819            self.generate_expression(with_storage)?;
30820            self.write(")");
30821        }
30822        if let Some(tablespace) = &e.tablespace {
30823            self.write_space();
30824            self.write_keyword("USING INDEX TABLESPACE");
30825            self.write_space();
30826            self.generate_expression(tablespace)?;
30827        }
30828        Ok(())
30829    }
30830
30831    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
30832        // Python: this INDEX [FOR target] (expressions)
30833        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
30834        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
30835        if let Expression::Identifier(id) = &*e.this {
30836            self.write_keyword(&id.name);
30837        } else {
30838            self.generate_expression(&e.this)?;
30839        }
30840        self.write_space();
30841        self.write_keyword("INDEX");
30842        if let Some(target) = &e.target {
30843            self.write_space();
30844            self.write_keyword("FOR");
30845            self.write_space();
30846            if let Expression::Identifier(id) = &**target {
30847                self.write_keyword(&id.name);
30848            } else {
30849                self.generate_expression(target)?;
30850            }
30851        }
30852        // Always output parentheses (even if empty, e.g. USE INDEX ())
30853        self.write(" (");
30854        for (i, expr) in e.expressions.iter().enumerate() {
30855            if i > 0 {
30856                self.write(", ");
30857            }
30858            self.generate_expression(expr)?;
30859        }
30860        self.write(")");
30861        Ok(())
30862    }
30863
30864    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
30865        // INHERITS (table1, table2, ...)
30866        self.write_keyword("INHERITS");
30867        self.write(" (");
30868        for (i, expr) in e.expressions.iter().enumerate() {
30869            if i > 0 {
30870                self.write(", ");
30871            }
30872            self.generate_expression(expr)?;
30873        }
30874        self.write(")");
30875        Ok(())
30876    }
30877
30878    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
30879        // INPUT(model)
30880        self.write_keyword("INPUT");
30881        self.write("(");
30882        self.generate_expression(&e.this)?;
30883        self.write(")");
30884        Ok(())
30885    }
30886
30887    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
30888        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
30889        if let Some(input_format) = &e.input_format {
30890            self.write_keyword("INPUTFORMAT");
30891            self.write_space();
30892            self.generate_expression(input_format)?;
30893        }
30894        if let Some(output_format) = &e.output_format {
30895            if e.input_format.is_some() {
30896                self.write(" ");
30897            }
30898            self.write_keyword("OUTPUTFORMAT");
30899            self.write_space();
30900            self.generate_expression(output_format)?;
30901        }
30902        Ok(())
30903    }
30904
30905    fn generate_install(&mut self, e: &Install) -> Result<()> {
30906        // [FORCE] INSTALL extension [FROM source]
30907        if e.force.is_some() {
30908            self.write_keyword("FORCE");
30909            self.write_space();
30910        }
30911        self.write_keyword("INSTALL");
30912        self.write_space();
30913        self.generate_expression(&e.this)?;
30914        if let Some(from) = &e.from_ {
30915            self.write_space();
30916            self.write_keyword("FROM");
30917            self.write_space();
30918            self.generate_expression(from)?;
30919        }
30920        Ok(())
30921    }
30922
30923    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
30924        // INTERVAL 'expression' unit
30925        self.write_keyword("INTERVAL");
30926        self.write_space();
30927        // When a unit is specified and the expression is a number,
30928        self.generate_expression(&e.expression)?;
30929        if let Some(unit) = &e.unit {
30930            self.write_space();
30931            self.write(unit);
30932        }
30933        Ok(())
30934    }
30935
30936    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
30937        // unit TO unit (e.g., HOUR TO SECOND)
30938        self.write(&format!("{:?}", e.this).to_ascii_uppercase());
30939        self.write_space();
30940        self.write_keyword("TO");
30941        self.write_space();
30942        self.write(&format!("{:?}", e.expression).to_ascii_uppercase());
30943        Ok(())
30944    }
30945
30946    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
30947        // INTO [TEMPORARY|UNLOGGED] table
30948        self.write_keyword("INTO");
30949        if e.temporary {
30950            self.write_keyword(" TEMPORARY");
30951        }
30952        if e.unlogged.is_some() {
30953            self.write_keyword(" UNLOGGED");
30954        }
30955        if let Some(this) = &e.this {
30956            self.write_space();
30957            self.generate_expression(this)?;
30958        }
30959        if !e.expressions.is_empty() {
30960            self.write(" (");
30961            for (i, expr) in e.expressions.iter().enumerate() {
30962                if i > 0 {
30963                    self.write(", ");
30964                }
30965                self.generate_expression(expr)?;
30966            }
30967            self.write(")");
30968        }
30969        Ok(())
30970    }
30971
30972    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
30973        // Python: this expression (e.g., _utf8 'string')
30974        self.generate_expression(&e.this)?;
30975        self.write_space();
30976        self.generate_expression(&e.expression)?;
30977        Ok(())
30978    }
30979
30980    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
30981        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
30982        self.write_keyword("WITH");
30983        if e.no.is_some() {
30984            self.write_keyword(" NO");
30985        }
30986        if e.concurrent.is_some() {
30987            self.write_keyword(" CONCURRENT");
30988        }
30989        self.write_keyword(" ISOLATED LOADING");
30990        if let Some(target) = &e.target {
30991            self.write_space();
30992            self.generate_expression(target)?;
30993        }
30994        Ok(())
30995    }
30996
30997    fn generate_json(&mut self, e: &JSON) -> Result<()> {
30998        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
30999        self.write_keyword("JSON");
31000        if let Some(this) = &e.this {
31001            self.write_space();
31002            self.generate_expression(this)?;
31003        }
31004        if let Some(with_) = &e.with_ {
31005            // Check if it's a truthy boolean
31006            if let Expression::Boolean(b) = with_.as_ref() {
31007                if b.value {
31008                    self.write_keyword(" WITH");
31009                } else {
31010                    self.write_keyword(" WITHOUT");
31011                }
31012            }
31013        }
31014        if e.unique {
31015            self.write_keyword(" UNIQUE KEYS");
31016        }
31017        Ok(())
31018    }
31019
31020    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
31021        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
31022        self.write_keyword("JSON_ARRAY");
31023        self.write("(");
31024        for (i, expr) in e.expressions.iter().enumerate() {
31025            if i > 0 {
31026                self.write(", ");
31027            }
31028            self.generate_expression(expr)?;
31029        }
31030        if let Some(null_handling) = &e.null_handling {
31031            self.write_space();
31032            self.generate_expression(null_handling)?;
31033        }
31034        if let Some(return_type) = &e.return_type {
31035            self.write_space();
31036            self.write_keyword("RETURNING");
31037            self.write_space();
31038            self.generate_expression(return_type)?;
31039        }
31040        if e.strict.is_some() {
31041            self.write_space();
31042            self.write_keyword("STRICT");
31043        }
31044        self.write(")");
31045        Ok(())
31046    }
31047
31048    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
31049        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
31050        self.write_keyword("JSON_ARRAYAGG");
31051        self.write("(");
31052        self.generate_expression(&e.this)?;
31053        if let Some(order) = &e.order {
31054            self.write_space();
31055            // Order is stored as an OrderBy expression
31056            if let Expression::OrderBy(ob) = order.as_ref() {
31057                self.write_keyword("ORDER BY");
31058                self.write_space();
31059                for (i, ord) in ob.expressions.iter().enumerate() {
31060                    if i > 0 {
31061                        self.write(", ");
31062                    }
31063                    self.generate_ordered(ord)?;
31064                }
31065            } else {
31066                // Fallback: generate the expression directly
31067                self.generate_expression(order)?;
31068            }
31069        }
31070        if let Some(null_handling) = &e.null_handling {
31071            self.write_space();
31072            self.generate_expression(null_handling)?;
31073        }
31074        if let Some(return_type) = &e.return_type {
31075            self.write_space();
31076            self.write_keyword("RETURNING");
31077            self.write_space();
31078            self.generate_expression(return_type)?;
31079        }
31080        if e.strict.is_some() {
31081            self.write_space();
31082            self.write_keyword("STRICT");
31083        }
31084        self.write(")");
31085        Ok(())
31086    }
31087
31088    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
31089        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
31090        self.write_keyword("JSON_OBJECTAGG");
31091        self.write("(");
31092        for (i, expr) in e.expressions.iter().enumerate() {
31093            if i > 0 {
31094                self.write(", ");
31095            }
31096            self.generate_expression(expr)?;
31097        }
31098        if let Some(null_handling) = &e.null_handling {
31099            self.write_space();
31100            self.generate_expression(null_handling)?;
31101        }
31102        if let Some(unique_keys) = &e.unique_keys {
31103            self.write_space();
31104            if let Expression::Boolean(b) = unique_keys.as_ref() {
31105                if b.value {
31106                    self.write_keyword("WITH UNIQUE KEYS");
31107                } else {
31108                    self.write_keyword("WITHOUT UNIQUE KEYS");
31109                }
31110            }
31111        }
31112        if let Some(return_type) = &e.return_type {
31113            self.write_space();
31114            self.write_keyword("RETURNING");
31115            self.write_space();
31116            self.generate_expression(return_type)?;
31117        }
31118        self.write(")");
31119        Ok(())
31120    }
31121
31122    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
31123        // JSON_ARRAY_APPEND(this, path, value, ...)
31124        self.write_keyword("JSON_ARRAY_APPEND");
31125        self.write("(");
31126        self.generate_expression(&e.this)?;
31127        for expr in &e.expressions {
31128            self.write(", ");
31129            self.generate_expression(expr)?;
31130        }
31131        self.write(")");
31132        Ok(())
31133    }
31134
31135    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
31136        // JSON_ARRAY_CONTAINS(this, expression)
31137        self.write_keyword("JSON_ARRAY_CONTAINS");
31138        self.write("(");
31139        self.generate_expression(&e.this)?;
31140        self.write(", ");
31141        self.generate_expression(&e.expression)?;
31142        self.write(")");
31143        Ok(())
31144    }
31145
31146    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
31147        // JSON_ARRAY_INSERT(this, path, value, ...)
31148        self.write_keyword("JSON_ARRAY_INSERT");
31149        self.write("(");
31150        self.generate_expression(&e.this)?;
31151        for expr in &e.expressions {
31152            self.write(", ");
31153            self.generate_expression(expr)?;
31154        }
31155        self.write(")");
31156        Ok(())
31157    }
31158
31159    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
31160        // JSONB_EXISTS(this, path)
31161        self.write_keyword("JSONB_EXISTS");
31162        self.write("(");
31163        self.generate_expression(&e.this)?;
31164        if let Some(path) = &e.path {
31165            self.write(", ");
31166            self.generate_expression(path)?;
31167        }
31168        self.write(")");
31169        Ok(())
31170    }
31171
31172    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
31173        // JSONB_EXTRACT_SCALAR(this, expression)
31174        self.write_keyword("JSONB_EXTRACT_SCALAR");
31175        self.write("(");
31176        self.generate_expression(&e.this)?;
31177        self.write(", ");
31178        self.generate_expression(&e.expression)?;
31179        self.write(")");
31180        Ok(())
31181    }
31182
31183    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
31184        // JSONB_OBJECT_AGG(this, expression)
31185        self.write_keyword("JSONB_OBJECT_AGG");
31186        self.write("(");
31187        self.generate_expression(&e.this)?;
31188        self.write(", ");
31189        self.generate_expression(&e.expression)?;
31190        self.write(")");
31191        Ok(())
31192    }
31193
31194    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
31195        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
31196        if let Some(nested_schema) = &e.nested_schema {
31197            self.write_keyword("NESTED");
31198            if let Some(path) = &e.path {
31199                self.write_space();
31200                self.write_keyword("PATH");
31201                self.write_space();
31202                self.generate_expression(path)?;
31203            }
31204            self.write_space();
31205            self.generate_expression(nested_schema)?;
31206        } else {
31207            if let Some(this) = &e.this {
31208                self.generate_expression(this)?;
31209            }
31210            if let Some(kind) = &e.kind {
31211                self.write_space();
31212                self.write(kind);
31213            }
31214            if e.format_json {
31215                self.write_space();
31216                self.write_keyword("FORMAT JSON");
31217            }
31218            if let Some(path) = &e.path {
31219                self.write_space();
31220                self.write_keyword("PATH");
31221                self.write_space();
31222                self.generate_expression(path)?;
31223            }
31224            if e.ordinality.is_some() {
31225                self.write_keyword(" FOR ORDINALITY");
31226            }
31227        }
31228        Ok(())
31229    }
31230
31231    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
31232        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
31233        self.write_keyword("JSON_EXISTS");
31234        self.write("(");
31235        self.generate_expression(&e.this)?;
31236        if let Some(path) = &e.path {
31237            self.write(", ");
31238            self.generate_expression(path)?;
31239        }
31240        if let Some(passing) = &e.passing {
31241            self.write_space();
31242            self.write_keyword("PASSING");
31243            self.write_space();
31244            self.generate_expression(passing)?;
31245        }
31246        if let Some(on_condition) = &e.on_condition {
31247            self.write_space();
31248            self.generate_expression(on_condition)?;
31249        }
31250        self.write(")");
31251        Ok(())
31252    }
31253
31254    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
31255        self.generate_expression(&e.this)?;
31256        self.write(".:");
31257        // If the data type has nested type parameters (like Array(JSON), Map(String, Int)),
31258        // wrap the entire type string in double quotes.
31259        // This matches Python sqlglot's ClickHouse _json_cast_sql behavior.
31260        if Self::data_type_has_nested_expressions(&e.to) {
31261            // Generate the data type to a temporary string buffer, then wrap in quotes
31262            let saved = std::mem::take(&mut self.output);
31263            self.generate_data_type(&e.to)?;
31264            let type_sql = std::mem::replace(&mut self.output, saved);
31265            self.write("\"");
31266            self.write(&type_sql);
31267            self.write("\"");
31268        } else {
31269            self.generate_data_type(&e.to)?;
31270        }
31271        Ok(())
31272    }
31273
31274    /// Check if a DataType has nested type expressions (sub-types).
31275    /// This corresponds to Python sqlglot's `to.expressions` being non-empty.
31276    fn data_type_has_nested_expressions(dt: &DataType) -> bool {
31277        matches!(
31278            dt,
31279            DataType::Array { .. } | DataType::Map { .. } | DataType::Struct { .. }
31280        )
31281    }
31282
31283    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
31284        // JSON_EXTRACT_ARRAY(this, expression)
31285        self.write_keyword("JSON_EXTRACT_ARRAY");
31286        self.write("(");
31287        self.generate_expression(&e.this)?;
31288        if let Some(expr) = &e.expression {
31289            self.write(", ");
31290            self.generate_expression(expr)?;
31291        }
31292        self.write(")");
31293        Ok(())
31294    }
31295
31296    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
31297        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
31298        if let Some(option) = &e.option {
31299            self.generate_expression(option)?;
31300            self.write_space();
31301        }
31302        self.write_keyword("QUOTES");
31303        if e.scalar.is_some() {
31304            self.write_keyword(" SCALAR_ONLY");
31305        }
31306        Ok(())
31307    }
31308
31309    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
31310        // JSON_EXTRACT_SCALAR(this, expression)
31311        self.write_keyword("JSON_EXTRACT_SCALAR");
31312        self.write("(");
31313        self.generate_expression(&e.this)?;
31314        self.write(", ");
31315        self.generate_expression(&e.expression)?;
31316        self.write(")");
31317        Ok(())
31318    }
31319
31320    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
31321        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
31322        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
31323        // Otherwise output JSON_EXTRACT(this, expression)
31324        if e.variant_extract.is_some() {
31325            use crate::dialects::DialectType;
31326            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
31327                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
31328                // Keys that are not safe identifiers (contain hyphens, spaces, etc.) must use
31329                // bracket notation: c:["x-y"] instead of c:x-y
31330                self.generate_expression(&e.this)?;
31331                self.write(":");
31332                match e.expression.as_ref() {
31333                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
31334                        let Literal::String(s) = lit.as_ref() else {
31335                            unreachable!()
31336                        };
31337                        self.write_databricks_json_path(s);
31338                    }
31339                    _ => {
31340                        // Fallback: generate as-is (shouldn't happen in typical cases)
31341                        self.generate_expression(&e.expression)?;
31342                    }
31343                }
31344            } else {
31345                // Snowflake and others: use GET_PATH(col, 'path')
31346                self.write_keyword("GET_PATH");
31347                self.write("(");
31348                self.generate_expression(&e.this)?;
31349                self.write(", ");
31350                self.generate_expression(&e.expression)?;
31351                self.write(")");
31352            }
31353        } else {
31354            self.write_keyword("JSON_EXTRACT");
31355            self.write("(");
31356            self.generate_expression(&e.this)?;
31357            self.write(", ");
31358            self.generate_expression(&e.expression)?;
31359            for expr in &e.expressions {
31360                self.write(", ");
31361                self.generate_expression(expr)?;
31362            }
31363            self.write(")");
31364        }
31365        Ok(())
31366    }
31367
31368    /// Write a Databricks JSON colon-path, using bracket notation for keys
31369    /// that are not safe identifiers (e.g., contain hyphens, spaces, etc.)
31370    /// Safe identifier regex: ^[_a-zA-Z]\w*$
31371    fn write_databricks_json_path(&mut self, path: &str) {
31372        // If the path already starts with bracket notation (e.g., '["fr\'uit"]'),
31373        // it was already formatted by the parser - output as-is
31374        if path.starts_with("[\"") || path.starts_with("['") {
31375            self.write(path);
31376            return;
31377        }
31378        // Split the path into segments at '.' boundaries, but preserve bracket subscripts
31379        // e.g., "price.items[0].name" -> ["price", "items[0]", "name"]
31380        // e.g., "x-y" -> ["x-y"]
31381        let mut first = true;
31382        for segment in path.split('.') {
31383            if !first {
31384                self.write(".");
31385            }
31386            first = false;
31387            // Check if there's a bracket subscript in this segment: "items[0]"
31388            if let Some(bracket_pos) = segment.find('[') {
31389                let key = &segment[..bracket_pos];
31390                let subscript = &segment[bracket_pos..];
31391                if key.is_empty() {
31392                    // Bracket notation at start of segment (e.g., already formatted)
31393                    self.write(segment);
31394                } else if Self::is_safe_json_path_key(key) {
31395                    self.write(key);
31396                    self.write(subscript);
31397                } else {
31398                    self.write("[\"");
31399                    self.write(key);
31400                    self.write("\"]");
31401                    self.write(subscript);
31402                }
31403            } else if Self::is_safe_json_path_key(segment) {
31404                self.write(segment);
31405            } else {
31406                self.write("[\"");
31407                self.write(segment);
31408                self.write("\"]");
31409            }
31410        }
31411    }
31412
31413    /// Check if a JSON path key is a safe identifier that doesn't need bracket quoting.
31414    /// Matches Python sqlglot's SAFE_IDENTIFIER_RE: ^[_a-zA-Z]\w*$
31415    fn is_safe_json_path_key(key: &str) -> bool {
31416        if key.is_empty() {
31417            return false;
31418        }
31419        let mut chars = key.chars();
31420        let first = chars.next().unwrap();
31421        if first != '_' && !first.is_ascii_alphabetic() {
31422            return false;
31423        }
31424        chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
31425    }
31426
31427    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
31428        // Output: {expr} FORMAT JSON
31429        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
31430        if let Some(this) = &e.this {
31431            self.generate_expression(this)?;
31432            self.write_space();
31433        }
31434        self.write_keyword("FORMAT JSON");
31435        Ok(())
31436    }
31437
31438    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
31439        // key: value (for JSON objects)
31440        self.generate_expression(&e.this)?;
31441        self.write(": ");
31442        self.generate_expression(&e.expression)?;
31443        Ok(())
31444    }
31445
31446    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
31447        // JSON_KEYS(this, expression, expressions...)
31448        self.write_keyword("JSON_KEYS");
31449        self.write("(");
31450        self.generate_expression(&e.this)?;
31451        if let Some(expr) = &e.expression {
31452            self.write(", ");
31453            self.generate_expression(expr)?;
31454        }
31455        for expr in &e.expressions {
31456            self.write(", ");
31457            self.generate_expression(expr)?;
31458        }
31459        self.write(")");
31460        Ok(())
31461    }
31462
31463    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
31464        // JSON_KEYS(this, expression)
31465        self.write_keyword("JSON_KEYS");
31466        self.write("(");
31467        self.generate_expression(&e.this)?;
31468        if let Some(expr) = &e.expression {
31469            self.write(", ");
31470            self.generate_expression(expr)?;
31471        }
31472        self.write(")");
31473        Ok(())
31474    }
31475
31476    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
31477        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
31478        // The path components are concatenated without spaces
31479        let mut path_str = String::new();
31480        for expr in &e.expressions {
31481            match expr {
31482                Expression::JSONPathRoot(_) => {
31483                    path_str.push('$');
31484                }
31485                Expression::JSONPathKey(k) => {
31486                    // .key or ."key" (quote if key has special characters)
31487                    if let Expression::Literal(lit) = k.this.as_ref() {
31488                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
31489                            path_str.push('.');
31490                            // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
31491                            let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
31492                            if needs_quoting {
31493                                path_str.push('"');
31494                                path_str.push_str(s);
31495                                path_str.push('"');
31496                            } else {
31497                                path_str.push_str(s);
31498                            }
31499                        }
31500                    }
31501                }
31502                Expression::JSONPathSubscript(s) => {
31503                    // [index]
31504                    if let Expression::Literal(lit) = s.this.as_ref() {
31505                        if let crate::expressions::Literal::Number(n) = lit.as_ref() {
31506                            path_str.push('[');
31507                            path_str.push_str(n);
31508                            path_str.push(']');
31509                        }
31510                    }
31511                }
31512                _ => {
31513                    // For other path parts, try to generate them
31514                    let mut temp_gen = Self::with_arc_config(self.config.clone());
31515                    temp_gen.generate_expression(expr)?;
31516                    path_str.push_str(&temp_gen.output);
31517                }
31518            }
31519        }
31520        // Output as quoted string
31521        self.write("'");
31522        self.write(&path_str);
31523        self.write("'");
31524        Ok(())
31525    }
31526
31527    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
31528        // JSON path filter: ?(predicate)
31529        self.write("?(");
31530        self.generate_expression(&e.this)?;
31531        self.write(")");
31532        Ok(())
31533    }
31534
31535    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
31536        // JSON path key: .key or ["key"]
31537        self.write(".");
31538        self.generate_expression(&e.this)?;
31539        Ok(())
31540    }
31541
31542    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
31543        // JSON path recursive descent: ..
31544        self.write("..");
31545        if let Some(this) = &e.this {
31546            self.generate_expression(this)?;
31547        }
31548        Ok(())
31549    }
31550
31551    fn generate_json_path_root(&mut self) -> Result<()> {
31552        // JSON path root: $
31553        self.write("$");
31554        Ok(())
31555    }
31556
31557    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
31558        // JSON path script: (expression)
31559        self.write("(");
31560        self.generate_expression(&e.this)?;
31561        self.write(")");
31562        Ok(())
31563    }
31564
31565    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
31566        // JSON path selector: *
31567        self.generate_expression(&e.this)?;
31568        Ok(())
31569    }
31570
31571    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
31572        // JSON path slice: [start:end:step]
31573        self.write("[");
31574        if let Some(start) = &e.start {
31575            self.generate_expression(start)?;
31576        }
31577        self.write(":");
31578        if let Some(end) = &e.end {
31579            self.generate_expression(end)?;
31580        }
31581        if let Some(step) = &e.step {
31582            self.write(":");
31583            self.generate_expression(step)?;
31584        }
31585        self.write("]");
31586        Ok(())
31587    }
31588
31589    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
31590        // JSON path subscript: [index] or [*]
31591        self.write("[");
31592        self.generate_expression(&e.this)?;
31593        self.write("]");
31594        Ok(())
31595    }
31596
31597    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
31598        // JSON path union: [key1, key2, ...]
31599        self.write("[");
31600        for (i, expr) in e.expressions.iter().enumerate() {
31601            if i > 0 {
31602                self.write(", ");
31603            }
31604            self.generate_expression(expr)?;
31605        }
31606        self.write("]");
31607        Ok(())
31608    }
31609
31610    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
31611        // JSON_REMOVE(this, path1, path2, ...)
31612        self.write_keyword("JSON_REMOVE");
31613        self.write("(");
31614        self.generate_expression(&e.this)?;
31615        for expr in &e.expressions {
31616            self.write(", ");
31617            self.generate_expression(expr)?;
31618        }
31619        self.write(")");
31620        Ok(())
31621    }
31622
31623    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
31624        // COLUMNS(col1 type, col2 type, ...)
31625        // When pretty printing and content is too wide, format with each column on a separate line
31626        self.write_keyword("COLUMNS");
31627        self.write("(");
31628
31629        if self.config.pretty && !e.expressions.is_empty() {
31630            // First, generate all expressions into strings to check width
31631            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
31632            for expr in &e.expressions {
31633                let mut temp_gen = Generator::with_arc_config(self.config.clone());
31634                temp_gen.generate_expression(expr)?;
31635                expr_strings.push(temp_gen.output);
31636            }
31637
31638            // Check if total width exceeds max_text_width
31639            if self.too_wide(&expr_strings) {
31640                // Pretty print: each column on its own line
31641                self.write_newline();
31642                self.indent_level += 1;
31643                for (i, expr_str) in expr_strings.iter().enumerate() {
31644                    if i > 0 {
31645                        self.write(",");
31646                        self.write_newline();
31647                    }
31648                    self.write_indent();
31649                    self.write(expr_str);
31650                }
31651                self.write_newline();
31652                self.indent_level -= 1;
31653                self.write_indent();
31654            } else {
31655                // Compact: all on one line
31656                for (i, expr_str) in expr_strings.iter().enumerate() {
31657                    if i > 0 {
31658                        self.write(", ");
31659                    }
31660                    self.write(expr_str);
31661                }
31662            }
31663        } else {
31664            // Non-pretty mode: compact format
31665            for (i, expr) in e.expressions.iter().enumerate() {
31666                if i > 0 {
31667                    self.write(", ");
31668                }
31669                self.generate_expression(expr)?;
31670            }
31671        }
31672        self.write(")");
31673        Ok(())
31674    }
31675
31676    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
31677        // JSON_SET(this, path, value, ...)
31678        self.write_keyword("JSON_SET");
31679        self.write("(");
31680        self.generate_expression(&e.this)?;
31681        for expr in &e.expressions {
31682            self.write(", ");
31683            self.generate_expression(expr)?;
31684        }
31685        self.write(")");
31686        Ok(())
31687    }
31688
31689    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
31690        // JSON_STRIP_NULLS(this, expression)
31691        self.write_keyword("JSON_STRIP_NULLS");
31692        self.write("(");
31693        self.generate_expression(&e.this)?;
31694        if let Some(expr) = &e.expression {
31695            self.write(", ");
31696            self.generate_expression(expr)?;
31697        }
31698        self.write(")");
31699        Ok(())
31700    }
31701
31702    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
31703        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
31704        self.write_keyword("JSON_TABLE");
31705        self.write("(");
31706        self.generate_expression(&e.this)?;
31707        if let Some(path) = &e.path {
31708            self.write(", ");
31709            self.generate_expression(path)?;
31710        }
31711        if let Some(error_handling) = &e.error_handling {
31712            self.write_space();
31713            self.generate_expression(error_handling)?;
31714        }
31715        if let Some(empty_handling) = &e.empty_handling {
31716            self.write_space();
31717            self.generate_expression(empty_handling)?;
31718        }
31719        if let Some(schema) = &e.schema {
31720            self.write_space();
31721            self.generate_expression(schema)?;
31722        }
31723        self.write(")");
31724        Ok(())
31725    }
31726
31727    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
31728        // JSON_TYPE(this)
31729        self.write_keyword("JSON_TYPE");
31730        self.write("(");
31731        self.generate_expression(&e.this)?;
31732        self.write(")");
31733        Ok(())
31734    }
31735
31736    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
31737        // JSON_VALUE(this, path RETURNING type ON condition)
31738        self.write_keyword("JSON_VALUE");
31739        self.write("(");
31740        self.generate_expression(&e.this)?;
31741        if let Some(path) = &e.path {
31742            self.write(", ");
31743            self.generate_expression(path)?;
31744        }
31745        if let Some(returning) = &e.returning {
31746            self.write_space();
31747            self.write_keyword("RETURNING");
31748            self.write_space();
31749            self.generate_expression(returning)?;
31750        }
31751        if let Some(on_condition) = &e.on_condition {
31752            self.write_space();
31753            self.generate_expression(on_condition)?;
31754        }
31755        self.write(")");
31756        Ok(())
31757    }
31758
31759    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
31760        // JSON_VALUE_ARRAY(this)
31761        self.write_keyword("JSON_VALUE_ARRAY");
31762        self.write("(");
31763        self.generate_expression(&e.this)?;
31764        self.write(")");
31765        Ok(())
31766    }
31767
31768    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
31769        // JAROWINKLER_SIMILARITY(str1, str2)
31770        self.write_keyword("JAROWINKLER_SIMILARITY");
31771        self.write("(");
31772        self.generate_expression(&e.this)?;
31773        self.write(", ");
31774        self.generate_expression(&e.expression)?;
31775        self.write(")");
31776        Ok(())
31777    }
31778
31779    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
31780        // Python: this(expressions)
31781        self.generate_expression(&e.this)?;
31782        self.write("(");
31783        for (i, expr) in e.expressions.iter().enumerate() {
31784            if i > 0 {
31785                self.write(", ");
31786            }
31787            self.generate_expression(expr)?;
31788        }
31789        self.write(")");
31790        Ok(())
31791    }
31792
31793    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
31794        // Python: {no}{local}{dual}{before}{after}JOURNAL
31795        if e.no.is_some() {
31796            self.write_keyword("NO ");
31797        }
31798        if let Some(local) = &e.local {
31799            self.generate_expression(local)?;
31800            self.write_space();
31801        }
31802        if e.dual.is_some() {
31803            self.write_keyword("DUAL ");
31804        }
31805        if e.before.is_some() {
31806            self.write_keyword("BEFORE ");
31807        }
31808        if e.after.is_some() {
31809            self.write_keyword("AFTER ");
31810        }
31811        self.write_keyword("JOURNAL");
31812        Ok(())
31813    }
31814
31815    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
31816        // LANGUAGE language_name
31817        self.write_keyword("LANGUAGE");
31818        self.write_space();
31819        self.generate_expression(&e.this)?;
31820        Ok(())
31821    }
31822
31823    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
31824        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
31825        if e.view.is_some() {
31826            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
31827            self.write_keyword("LATERAL VIEW");
31828            if e.outer.is_some() {
31829                self.write_space();
31830                self.write_keyword("OUTER");
31831            }
31832            self.write_space();
31833            self.generate_expression(&e.this)?;
31834            if let Some(alias) = &e.alias {
31835                self.write_space();
31836                self.write(alias);
31837            }
31838        } else {
31839            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
31840            self.write_keyword("LATERAL");
31841            self.write_space();
31842            self.generate_expression(&e.this)?;
31843            if e.ordinality.is_some() {
31844                self.write_space();
31845                self.write_keyword("WITH ORDINALITY");
31846            }
31847            if let Some(alias) = &e.alias {
31848                self.write_space();
31849                self.write_keyword("AS");
31850                self.write_space();
31851                self.write(alias);
31852                if !e.column_aliases.is_empty() {
31853                    self.write("(");
31854                    for (i, col) in e.column_aliases.iter().enumerate() {
31855                        if i > 0 {
31856                            self.write(", ");
31857                        }
31858                        self.write(col);
31859                    }
31860                    self.write(")");
31861                }
31862            }
31863        }
31864        Ok(())
31865    }
31866
31867    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
31868        // Python: LIKE this [options]
31869        self.write_keyword("LIKE");
31870        self.write_space();
31871        self.generate_expression(&e.this)?;
31872        for expr in &e.expressions {
31873            self.write_space();
31874            self.generate_expression(expr)?;
31875        }
31876        Ok(())
31877    }
31878
31879    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
31880        self.write_keyword("LIMIT");
31881        self.write_space();
31882        self.write_limit_expr(&e.this)?;
31883        if e.percent {
31884            self.write_space();
31885            self.write_keyword("PERCENT");
31886        }
31887        // Emit any comments that were captured from before the LIMIT keyword
31888        for comment in &e.comments {
31889            self.write(" ");
31890            self.write_formatted_comment(comment);
31891        }
31892        Ok(())
31893    }
31894
31895    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
31896        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
31897        if e.percent.is_some() {
31898            self.write_keyword(" PERCENT");
31899        }
31900        if e.rows.is_some() {
31901            self.write_keyword(" ROWS");
31902        }
31903        if e.with_ties.is_some() {
31904            self.write_keyword(" WITH TIES");
31905        } else if e.rows.is_some() {
31906            self.write_keyword(" ONLY");
31907        }
31908        Ok(())
31909    }
31910
31911    fn generate_list(&mut self, e: &List) -> Result<()> {
31912        use crate::dialects::DialectType;
31913        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
31914
31915        // Check if this is a subquery-based list (LIST(SELECT ...))
31916        if e.expressions.len() == 1 {
31917            if let Expression::Select(_) = &e.expressions[0] {
31918                self.write_keyword("LIST");
31919                self.write("(");
31920                self.generate_expression(&e.expressions[0])?;
31921                self.write(")");
31922                return Ok(());
31923            }
31924        }
31925
31926        // For Materialize, output as LIST[expr, expr, ...]
31927        if is_materialize {
31928            self.write_keyword("LIST");
31929            self.write("[");
31930            for (i, expr) in e.expressions.iter().enumerate() {
31931                if i > 0 {
31932                    self.write(", ");
31933                }
31934                self.generate_expression(expr)?;
31935            }
31936            self.write("]");
31937        } else {
31938            // For other dialects, output as LIST(expr, expr, ...)
31939            self.write_keyword("LIST");
31940            self.write("(");
31941            for (i, expr) in e.expressions.iter().enumerate() {
31942                if i > 0 {
31943                    self.write(", ");
31944                }
31945                self.generate_expression(expr)?;
31946            }
31947            self.write(")");
31948        }
31949        Ok(())
31950    }
31951
31952    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
31953        // Check if this is a subquery-based map (MAP(SELECT ...))
31954        if let Expression::Select(_) = &*e.this {
31955            self.write_keyword("MAP");
31956            self.write("(");
31957            self.generate_expression(&e.this)?;
31958            self.write(")");
31959            return Ok(());
31960        }
31961
31962        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
31963
31964        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
31965        self.write_keyword("MAP");
31966        if is_duckdb {
31967            self.write(" {");
31968        } else {
31969            self.write("[");
31970        }
31971        if let Expression::Struct(s) = &*e.this {
31972            for (i, (_, expr)) in s.fields.iter().enumerate() {
31973                if i > 0 {
31974                    self.write(", ");
31975                }
31976                if let Expression::PropertyEQ(op) = expr {
31977                    self.generate_expression(&op.left)?;
31978                    if is_duckdb {
31979                        self.write(": ");
31980                    } else {
31981                        self.write(" => ");
31982                    }
31983                    self.generate_expression(&op.right)?;
31984                } else {
31985                    self.generate_expression(expr)?;
31986                }
31987            }
31988        }
31989        if is_duckdb {
31990            self.write("}");
31991        } else {
31992            self.write("]");
31993        }
31994        Ok(())
31995    }
31996
31997    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
31998        // Python: LOCALTIME or LOCALTIME(precision)
31999        self.write_keyword("LOCALTIME");
32000        if let Some(precision) = &e.this {
32001            self.write("(");
32002            self.generate_expression(precision)?;
32003            self.write(")");
32004        }
32005        Ok(())
32006    }
32007
32008    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
32009        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
32010        self.write_keyword("LOCALTIMESTAMP");
32011        if let Some(precision) = &e.this {
32012            self.write("(");
32013            self.generate_expression(precision)?;
32014            self.write(")");
32015        }
32016        Ok(())
32017    }
32018
32019    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
32020        // LOCATION 'path'
32021        self.write_keyword("LOCATION");
32022        self.write_space();
32023        self.generate_expression(&e.this)?;
32024        Ok(())
32025    }
32026
32027    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
32028        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
32029        if e.update.is_some() {
32030            if e.key.is_some() {
32031                self.write_keyword("FOR NO KEY UPDATE");
32032            } else {
32033                self.write_keyword("FOR UPDATE");
32034            }
32035        } else {
32036            if e.key.is_some() {
32037                self.write_keyword("FOR KEY SHARE");
32038            } else {
32039                self.write_keyword("FOR SHARE");
32040            }
32041        }
32042        if !e.expressions.is_empty() {
32043            self.write_keyword(" OF ");
32044            for (i, expr) in e.expressions.iter().enumerate() {
32045                if i > 0 {
32046                    self.write(", ");
32047                }
32048                self.generate_expression(expr)?;
32049            }
32050        }
32051        // Handle wait option following Python sqlglot convention:
32052        // - Boolean(true) -> NOWAIT
32053        // - Boolean(false) -> SKIP LOCKED
32054        // - Literal (number) -> WAIT n
32055        if let Some(wait) = &e.wait {
32056            match wait.as_ref() {
32057                Expression::Boolean(b) => {
32058                    if b.value {
32059                        self.write_keyword(" NOWAIT");
32060                    } else {
32061                        self.write_keyword(" SKIP LOCKED");
32062                    }
32063                }
32064                _ => {
32065                    // It's a literal (number), output WAIT n
32066                    self.write_keyword(" WAIT ");
32067                    self.generate_expression(wait)?;
32068                }
32069            }
32070        }
32071        Ok(())
32072    }
32073
32074    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
32075        // LOCK property
32076        self.write_keyword("LOCK");
32077        self.write_space();
32078        self.generate_expression(&e.this)?;
32079        Ok(())
32080    }
32081
32082    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
32083        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
32084        self.write_keyword("LOCKING");
32085        self.write_space();
32086        self.write(&e.kind);
32087        if let Some(this) = &e.this {
32088            self.write_space();
32089            self.generate_expression(this)?;
32090        }
32091        if let Some(for_or_in) = &e.for_or_in {
32092            self.write_space();
32093            self.generate_expression(for_or_in)?;
32094        }
32095        if let Some(lock_type) = &e.lock_type {
32096            self.write_space();
32097            self.generate_expression(lock_type)?;
32098        }
32099        if e.override_.is_some() {
32100            self.write_keyword(" OVERRIDE");
32101        }
32102        Ok(())
32103    }
32104
32105    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
32106        // this expression
32107        self.generate_expression(&e.this)?;
32108        self.write_space();
32109        self.generate_expression(&e.expression)?;
32110        Ok(())
32111    }
32112
32113    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
32114        // [NO] LOG
32115        if e.no.is_some() {
32116            self.write_keyword("NO ");
32117        }
32118        self.write_keyword("LOG");
32119        Ok(())
32120    }
32121
32122    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
32123        // MD5(this, expressions...)
32124        self.write_keyword("MD5");
32125        self.write("(");
32126        self.generate_expression(&e.this)?;
32127        for expr in &e.expressions {
32128            self.write(", ");
32129            self.generate_expression(expr)?;
32130        }
32131        self.write(")");
32132        Ok(())
32133    }
32134
32135    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
32136        // ML.FORECAST(model, [params])
32137        self.write_keyword("ML.FORECAST");
32138        self.write("(");
32139        self.generate_expression(&e.this)?;
32140        if let Some(expression) = &e.expression {
32141            self.write(", ");
32142            self.generate_expression(expression)?;
32143        }
32144        if let Some(params) = &e.params_struct {
32145            self.write(", ");
32146            self.generate_expression(params)?;
32147        }
32148        self.write(")");
32149        Ok(())
32150    }
32151
32152    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
32153        // ML.TRANSLATE(model, input, [params])
32154        self.write_keyword("ML.TRANSLATE");
32155        self.write("(");
32156        self.generate_expression(&e.this)?;
32157        self.write(", ");
32158        self.generate_expression(&e.expression)?;
32159        if let Some(params) = &e.params_struct {
32160            self.write(", ");
32161            self.generate_expression(params)?;
32162        }
32163        self.write(")");
32164        Ok(())
32165    }
32166
32167    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
32168        // MAKE_INTERVAL(years => x, months => y, ...)
32169        self.write_keyword("MAKE_INTERVAL");
32170        self.write("(");
32171        let mut first = true;
32172        if let Some(year) = &e.year {
32173            self.write("years => ");
32174            self.generate_expression(year)?;
32175            first = false;
32176        }
32177        if let Some(month) = &e.month {
32178            if !first {
32179                self.write(", ");
32180            }
32181            self.write("months => ");
32182            self.generate_expression(month)?;
32183            first = false;
32184        }
32185        if let Some(week) = &e.week {
32186            if !first {
32187                self.write(", ");
32188            }
32189            self.write("weeks => ");
32190            self.generate_expression(week)?;
32191            first = false;
32192        }
32193        if let Some(day) = &e.day {
32194            if !first {
32195                self.write(", ");
32196            }
32197            self.write("days => ");
32198            self.generate_expression(day)?;
32199            first = false;
32200        }
32201        if let Some(hour) = &e.hour {
32202            if !first {
32203                self.write(", ");
32204            }
32205            self.write("hours => ");
32206            self.generate_expression(hour)?;
32207            first = false;
32208        }
32209        if let Some(minute) = &e.minute {
32210            if !first {
32211                self.write(", ");
32212            }
32213            self.write("mins => ");
32214            self.generate_expression(minute)?;
32215            first = false;
32216        }
32217        if let Some(second) = &e.second {
32218            if !first {
32219                self.write(", ");
32220            }
32221            self.write("secs => ");
32222            self.generate_expression(second)?;
32223        }
32224        self.write(")");
32225        Ok(())
32226    }
32227
32228    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
32229        // MANHATTAN_DISTANCE(vector1, vector2)
32230        self.write_keyword("MANHATTAN_DISTANCE");
32231        self.write("(");
32232        self.generate_expression(&e.this)?;
32233        self.write(", ");
32234        self.generate_expression(&e.expression)?;
32235        self.write(")");
32236        Ok(())
32237    }
32238
32239    fn generate_map(&mut self, e: &Map) -> Result<()> {
32240        // MAP(key1, value1, key2, value2, ...)
32241        self.write_keyword("MAP");
32242        self.write("(");
32243        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
32244            if i > 0 {
32245                self.write(", ");
32246            }
32247            self.generate_expression(key)?;
32248            self.write(", ");
32249            self.generate_expression(value)?;
32250        }
32251        self.write(")");
32252        Ok(())
32253    }
32254
32255    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
32256        // MAP_CAT(map1, map2)
32257        self.write_keyword("MAP_CAT");
32258        self.write("(");
32259        self.generate_expression(&e.this)?;
32260        self.write(", ");
32261        self.generate_expression(&e.expression)?;
32262        self.write(")");
32263        Ok(())
32264    }
32265
32266    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
32267        // MAP_DELETE(map, key1, key2, ...)
32268        self.write_keyword("MAP_DELETE");
32269        self.write("(");
32270        self.generate_expression(&e.this)?;
32271        for expr in &e.expressions {
32272            self.write(", ");
32273            self.generate_expression(expr)?;
32274        }
32275        self.write(")");
32276        Ok(())
32277    }
32278
32279    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
32280        // MAP_INSERT(map, key, value, [update_flag])
32281        self.write_keyword("MAP_INSERT");
32282        self.write("(");
32283        self.generate_expression(&e.this)?;
32284        if let Some(key) = &e.key {
32285            self.write(", ");
32286            self.generate_expression(key)?;
32287        }
32288        if let Some(value) = &e.value {
32289            self.write(", ");
32290            self.generate_expression(value)?;
32291        }
32292        if let Some(update_flag) = &e.update_flag {
32293            self.write(", ");
32294            self.generate_expression(update_flag)?;
32295        }
32296        self.write(")");
32297        Ok(())
32298    }
32299
32300    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
32301        // MAP_PICK(map, key1, key2, ...)
32302        self.write_keyword("MAP_PICK");
32303        self.write("(");
32304        self.generate_expression(&e.this)?;
32305        for expr in &e.expressions {
32306            self.write(", ");
32307            self.generate_expression(expr)?;
32308        }
32309        self.write(")");
32310        Ok(())
32311    }
32312
32313    fn generate_masking_policy_column_constraint(
32314        &mut self,
32315        e: &MaskingPolicyColumnConstraint,
32316    ) -> Result<()> {
32317        // Python: MASKING POLICY name [USING (cols)]
32318        self.write_keyword("MASKING POLICY");
32319        self.write_space();
32320        self.generate_expression(&e.this)?;
32321        if !e.expressions.is_empty() {
32322            self.write_keyword(" USING");
32323            self.write(" (");
32324            for (i, expr) in e.expressions.iter().enumerate() {
32325                if i > 0 {
32326                    self.write(", ");
32327                }
32328                self.generate_expression(expr)?;
32329            }
32330            self.write(")");
32331        }
32332        Ok(())
32333    }
32334
32335    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
32336        if matches!(
32337            self.config.dialect,
32338            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
32339        ) {
32340            if e.expressions.len() > 1 {
32341                self.write("(");
32342            }
32343            for (i, expr) in e.expressions.iter().enumerate() {
32344                if i > 0 {
32345                    self.write_keyword(" OR ");
32346                }
32347                self.generate_expression(expr)?;
32348                self.write_space();
32349                self.write("@@");
32350                self.write_space();
32351                self.generate_expression(&e.this)?;
32352            }
32353            if e.expressions.len() > 1 {
32354                self.write(")");
32355            }
32356            return Ok(());
32357        }
32358
32359        // MATCH(columns) AGAINST(expr [modifier])
32360        self.write_keyword("MATCH");
32361        self.write("(");
32362        for (i, expr) in e.expressions.iter().enumerate() {
32363            if i > 0 {
32364                self.write(", ");
32365            }
32366            self.generate_expression(expr)?;
32367        }
32368        self.write(")");
32369        self.write_keyword(" AGAINST");
32370        self.write("(");
32371        self.generate_expression(&e.this)?;
32372        if let Some(modifier) = &e.modifier {
32373            self.write_space();
32374            self.generate_expression(modifier)?;
32375        }
32376        self.write(")");
32377        Ok(())
32378    }
32379
32380    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
32381        // Python: [window_frame] this
32382        if let Some(window_frame) = &e.window_frame {
32383            self.write(&format!("{:?}", window_frame).to_ascii_uppercase());
32384            self.write_space();
32385        }
32386        self.generate_expression(&e.this)?;
32387        Ok(())
32388    }
32389
32390    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
32391        // MATERIALIZED [this]
32392        self.write_keyword("MATERIALIZED");
32393        if let Some(this) = &e.this {
32394            self.write_space();
32395            self.generate_expression(this)?;
32396        }
32397        Ok(())
32398    }
32399
32400    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
32401        // MERGE INTO target USING source ON condition WHEN ...
32402        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
32403        if let Some(with_) = &e.with_ {
32404            if let Expression::With(with_clause) = with_.as_ref() {
32405                self.generate_with(with_clause)?;
32406                self.write_space();
32407            } else {
32408                self.generate_expression(with_)?;
32409                self.write_space();
32410            }
32411        }
32412        self.write_keyword("MERGE INTO");
32413        self.write_space();
32414        if matches!(self.config.dialect, Some(crate::DialectType::Oracle)) {
32415            if let Expression::Alias(alias) = e.this.as_ref() {
32416                self.generate_expression(&alias.this)?;
32417                self.write_space();
32418                self.generate_identifier(&alias.alias)?;
32419            } else {
32420                self.generate_expression(&e.this)?;
32421            }
32422        } else {
32423            self.generate_expression(&e.this)?;
32424        }
32425
32426        // USING clause - newline before in pretty mode
32427        if self.config.pretty {
32428            self.write_newline();
32429            self.write_indent();
32430        } else {
32431            self.write_space();
32432        }
32433        self.write_keyword("USING");
32434        self.write_space();
32435        self.generate_expression(&e.using)?;
32436
32437        // ON clause - newline before in pretty mode
32438        if let Some(on) = &e.on {
32439            if self.config.pretty {
32440                self.write_newline();
32441                self.write_indent();
32442            } else {
32443                self.write_space();
32444            }
32445            self.write_keyword("ON");
32446            self.write_space();
32447            self.generate_expression(on)?;
32448        }
32449        // DuckDB USING (key_columns) clause
32450        if let Some(using_cond) = &e.using_cond {
32451            self.write_space();
32452            self.write_keyword("USING");
32453            self.write_space();
32454            self.write("(");
32455            // using_cond is a Tuple containing the column identifiers
32456            if let Expression::Tuple(tuple) = using_cond.as_ref() {
32457                for (i, col) in tuple.expressions.iter().enumerate() {
32458                    if i > 0 {
32459                        self.write(", ");
32460                    }
32461                    self.generate_expression(col)?;
32462                }
32463            } else {
32464                self.generate_expression(using_cond)?;
32465            }
32466            self.write(")");
32467        }
32468        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
32469        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
32470        if matches!(
32471            self.config.dialect,
32472            Some(crate::DialectType::PostgreSQL)
32473                | Some(crate::DialectType::Redshift)
32474                | Some(crate::DialectType::Trino)
32475                | Some(crate::DialectType::Presto)
32476                | Some(crate::DialectType::Athena)
32477        ) {
32478            let mut names = Vec::new();
32479            match e.this.as_ref() {
32480                Expression::Alias(a) => {
32481                    // e.g., "x AS z" -> strip both "x" and "z"
32482                    if let Expression::Table(t) = &a.this {
32483                        names.push(t.name.name.clone());
32484                    } else if let Expression::Identifier(id) = &a.this {
32485                        names.push(id.name.clone());
32486                    }
32487                    names.push(a.alias.name.clone());
32488                }
32489                Expression::Table(t) => {
32490                    names.push(t.name.name.clone());
32491                }
32492                Expression::Identifier(id) => {
32493                    names.push(id.name.clone());
32494                }
32495                _ => {}
32496            }
32497            self.merge_strip_qualifiers = names;
32498        }
32499
32500        // WHEN clauses - newline before each in pretty mode
32501        if let Some(whens) = &e.whens {
32502            if self.config.pretty {
32503                self.write_newline();
32504                self.write_indent();
32505            } else {
32506                self.write_space();
32507            }
32508            self.generate_expression(whens)?;
32509        }
32510
32511        // Restore merge_strip_qualifiers
32512        self.merge_strip_qualifiers = saved_merge_strip;
32513
32514        // OUTPUT/RETURNING clause - newline before in pretty mode
32515        if let Some(returning) = &e.returning {
32516            if self.config.pretty {
32517                self.write_newline();
32518                self.write_indent();
32519            } else {
32520                self.write_space();
32521            }
32522            self.generate_expression(returning)?;
32523        }
32524        Ok(())
32525    }
32526
32527    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
32528        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
32529        if e.no.is_some() {
32530            self.write_keyword("NO MERGEBLOCKRATIO");
32531        } else if e.default.is_some() {
32532            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
32533        } else {
32534            self.write_keyword("MERGEBLOCKRATIO");
32535            self.write("=");
32536            if let Some(this) = &e.this {
32537                self.generate_expression(this)?;
32538            }
32539            if e.percent.is_some() {
32540                self.write_keyword(" PERCENT");
32541            }
32542        }
32543        Ok(())
32544    }
32545
32546    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
32547        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
32548        self.write_keyword("TTL");
32549        let pretty_clickhouse = self.config.pretty
32550            && matches!(
32551                self.config.dialect,
32552                Some(crate::dialects::DialectType::ClickHouse)
32553            );
32554
32555        if pretty_clickhouse {
32556            self.write_newline();
32557            self.indent_level += 1;
32558            for (i, expr) in e.expressions.iter().enumerate() {
32559                if i > 0 {
32560                    self.write(",");
32561                    self.write_newline();
32562                }
32563                self.write_indent();
32564                self.generate_expression(expr)?;
32565            }
32566            self.indent_level -= 1;
32567        } else {
32568            self.write_space();
32569            for (i, expr) in e.expressions.iter().enumerate() {
32570                if i > 0 {
32571                    self.write(", ");
32572                }
32573                self.generate_expression(expr)?;
32574            }
32575        }
32576
32577        if let Some(where_) = &e.where_ {
32578            if pretty_clickhouse {
32579                self.write_newline();
32580                if let Expression::Where(w) = where_.as_ref() {
32581                    self.write_indent();
32582                    self.write_keyword("WHERE");
32583                    self.write_newline();
32584                    self.indent_level += 1;
32585                    self.write_indent();
32586                    self.generate_expression(&w.this)?;
32587                    self.indent_level -= 1;
32588                } else {
32589                    self.write_indent();
32590                    self.generate_expression(where_)?;
32591                }
32592            } else {
32593                self.write_space();
32594                self.generate_expression(where_)?;
32595            }
32596        }
32597        if let Some(group) = &e.group {
32598            if pretty_clickhouse {
32599                self.write_newline();
32600                if let Expression::Group(g) = group.as_ref() {
32601                    self.write_indent();
32602                    self.write_keyword("GROUP BY");
32603                    self.write_newline();
32604                    self.indent_level += 1;
32605                    for (i, expr) in g.expressions.iter().enumerate() {
32606                        if i > 0 {
32607                            self.write(",");
32608                            self.write_newline();
32609                        }
32610                        self.write_indent();
32611                        self.generate_expression(expr)?;
32612                    }
32613                    self.indent_level -= 1;
32614                } else {
32615                    self.write_indent();
32616                    self.generate_expression(group)?;
32617                }
32618            } else {
32619                self.write_space();
32620                self.generate_expression(group)?;
32621            }
32622        }
32623        if let Some(aggregates) = &e.aggregates {
32624            if pretty_clickhouse {
32625                self.write_newline();
32626                self.write_indent();
32627                self.write_keyword("SET");
32628                self.write_newline();
32629                self.indent_level += 1;
32630                if let Expression::Tuple(t) = aggregates.as_ref() {
32631                    for (i, agg) in t.expressions.iter().enumerate() {
32632                        if i > 0 {
32633                            self.write(",");
32634                            self.write_newline();
32635                        }
32636                        self.write_indent();
32637                        self.generate_expression(agg)?;
32638                    }
32639                } else {
32640                    self.write_indent();
32641                    self.generate_expression(aggregates)?;
32642                }
32643                self.indent_level -= 1;
32644            } else {
32645                self.write_space();
32646                self.write_keyword("SET");
32647                self.write_space();
32648                if let Expression::Tuple(t) = aggregates.as_ref() {
32649                    for (i, agg) in t.expressions.iter().enumerate() {
32650                        if i > 0 {
32651                            self.write(", ");
32652                        }
32653                        self.generate_expression(agg)?;
32654                    }
32655                } else {
32656                    self.generate_expression(aggregates)?;
32657                }
32658            }
32659        }
32660        Ok(())
32661    }
32662
32663    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
32664        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
32665        self.generate_expression(&e.this)?;
32666        if e.delete.is_some() {
32667            self.write_keyword(" DELETE");
32668        }
32669        if let Some(recompress) = &e.recompress {
32670            self.write_keyword(" RECOMPRESS ");
32671            self.generate_expression(recompress)?;
32672        }
32673        if let Some(to_disk) = &e.to_disk {
32674            self.write_keyword(" TO DISK ");
32675            self.generate_expression(to_disk)?;
32676        }
32677        if let Some(to_volume) = &e.to_volume {
32678            self.write_keyword(" TO VOLUME ");
32679            self.generate_expression(to_volume)?;
32680        }
32681        Ok(())
32682    }
32683
32684    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
32685        // MINHASH(this, expressions...)
32686        self.write_keyword("MINHASH");
32687        self.write("(");
32688        self.generate_expression(&e.this)?;
32689        for expr in &e.expressions {
32690            self.write(", ");
32691            self.generate_expression(expr)?;
32692        }
32693        self.write(")");
32694        Ok(())
32695    }
32696
32697    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
32698        // model!attribute - Snowflake syntax
32699        self.generate_expression(&e.this)?;
32700        self.write("!");
32701        self.generate_expression(&e.expression)?;
32702        Ok(())
32703    }
32704
32705    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
32706        // MONTHNAME(this)
32707        self.write_keyword("MONTHNAME");
32708        self.write("(");
32709        self.generate_expression(&e.this)?;
32710        self.write(")");
32711        Ok(())
32712    }
32713
32714    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
32715        // Output leading comments
32716        for comment in &e.leading_comments {
32717            self.write_formatted_comment(comment);
32718            if self.config.pretty {
32719                self.write_newline();
32720                self.write_indent();
32721            } else {
32722                self.write_space();
32723            }
32724        }
32725        // Python: INSERT [OVERWRITE] kind expressions source
32726        self.write_keyword("INSERT");
32727        if e.overwrite {
32728            self.write_space();
32729            self.write_keyword("OVERWRITE");
32730        }
32731        self.write_space();
32732        self.write(&e.kind);
32733        if self.config.pretty {
32734            self.indent_level += 1;
32735            for expr in &e.expressions {
32736                self.write_newline();
32737                self.write_indent();
32738                self.generate_expression(expr)?;
32739            }
32740            self.indent_level -= 1;
32741        } else {
32742            for expr in &e.expressions {
32743                self.write_space();
32744                self.generate_expression(expr)?;
32745            }
32746        }
32747        if let Some(source) = &e.source {
32748            if self.config.pretty {
32749                self.write_newline();
32750                self.write_indent();
32751            } else {
32752                self.write_space();
32753            }
32754            self.generate_expression(source)?;
32755        }
32756        Ok(())
32757    }
32758
32759    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
32760        // Python: NEXT VALUE FOR this [OVER (order)]
32761        self.write_keyword("NEXT VALUE FOR");
32762        self.write_space();
32763        self.generate_expression(&e.this)?;
32764        if let Some(order) = &e.order {
32765            self.write_space();
32766            self.write_keyword("OVER");
32767            self.write(" (");
32768            self.generate_expression(order)?;
32769            self.write(")");
32770        }
32771        Ok(())
32772    }
32773
32774    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
32775        // NORMAL(mean, stddev, gen)
32776        self.write_keyword("NORMAL");
32777        self.write("(");
32778        self.generate_expression(&e.this)?;
32779        if let Some(stddev) = &e.stddev {
32780            self.write(", ");
32781            self.generate_expression(stddev)?;
32782        }
32783        if let Some(gen) = &e.gen {
32784            self.write(", ");
32785            self.generate_expression(gen)?;
32786        }
32787        self.write(")");
32788        Ok(())
32789    }
32790
32791    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
32792        // NORMALIZE(this, form) or CASEFOLD version
32793        if e.is_casefold.is_some() {
32794            self.write_keyword("NORMALIZE_AND_CASEFOLD");
32795        } else {
32796            self.write_keyword("NORMALIZE");
32797        }
32798        self.write("(");
32799        self.generate_expression(&e.this)?;
32800        if let Some(form) = &e.form {
32801            self.write(", ");
32802            self.generate_expression(form)?;
32803        }
32804        self.write(")");
32805        Ok(())
32806    }
32807
32808    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
32809        // Python: [NOT ]NULL
32810        if e.allow_null.is_none() {
32811            self.write_keyword("NOT ");
32812        }
32813        self.write_keyword("NULL");
32814        Ok(())
32815    }
32816
32817    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
32818        // NULLIF(this, expression)
32819        self.write_keyword("NULLIF");
32820        self.write("(");
32821        self.generate_expression(&e.this)?;
32822        self.write(", ");
32823        self.generate_expression(&e.expression)?;
32824        self.write(")");
32825        Ok(())
32826    }
32827
32828    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
32829        // FORMAT(this, format, culture)
32830        self.write_keyword("FORMAT");
32831        self.write("(");
32832        self.generate_expression(&e.this)?;
32833        self.write(", '");
32834        self.write(&e.format);
32835        self.write("'");
32836        if let Some(culture) = &e.culture {
32837            self.write(", ");
32838            self.generate_expression(culture)?;
32839        }
32840        self.write(")");
32841        Ok(())
32842    }
32843
32844    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
32845        // OBJECT_AGG(key, value)
32846        self.write_keyword("OBJECT_AGG");
32847        self.write("(");
32848        self.generate_expression(&e.this)?;
32849        self.write(", ");
32850        self.generate_expression(&e.expression)?;
32851        self.write(")");
32852        Ok(())
32853    }
32854
32855    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
32856        // Python: Just returns the name
32857        self.generate_expression(&e.this)?;
32858        Ok(())
32859    }
32860
32861    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
32862        // OBJECT_INSERT(obj, key, value, [update_flag])
32863        self.write_keyword("OBJECT_INSERT");
32864        self.write("(");
32865        self.generate_expression(&e.this)?;
32866        if let Some(key) = &e.key {
32867            self.write(", ");
32868            self.generate_expression(key)?;
32869        }
32870        if let Some(value) = &e.value {
32871            self.write(", ");
32872            self.generate_expression(value)?;
32873        }
32874        if let Some(update_flag) = &e.update_flag {
32875            self.write(", ");
32876            self.generate_expression(update_flag)?;
32877        }
32878        self.write(")");
32879        Ok(())
32880    }
32881
32882    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
32883        // OFFSET value [ROW|ROWS]
32884        self.write_keyword("OFFSET");
32885        self.write_space();
32886        self.generate_expression(&e.this)?;
32887        // Output ROWS keyword only for TSQL/Oracle targets
32888        if e.rows == Some(true)
32889            && matches!(
32890                self.config.dialect,
32891                Some(crate::dialects::DialectType::TSQL)
32892                    | Some(crate::dialects::DialectType::Oracle)
32893            )
32894        {
32895            self.write_space();
32896            self.write_keyword("ROWS");
32897        }
32898        Ok(())
32899    }
32900
32901    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
32902        // QUALIFY condition (Snowflake/BigQuery)
32903        self.write_keyword("QUALIFY");
32904        self.write_space();
32905        self.generate_expression(&e.this)?;
32906        Ok(())
32907    }
32908
32909    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
32910        // ON CLUSTER cluster_name
32911        self.write_keyword("ON CLUSTER");
32912        self.write_space();
32913        self.generate_expression(&e.this)?;
32914        Ok(())
32915    }
32916
32917    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
32918        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
32919        self.write_keyword("ON COMMIT");
32920        if e.delete.is_some() {
32921            self.write_keyword(" DELETE ROWS");
32922        } else {
32923            self.write_keyword(" PRESERVE ROWS");
32924        }
32925        Ok(())
32926    }
32927
32928    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
32929        // Python: error/empty/null handling
32930        if let Some(empty) = &e.empty {
32931            self.generate_expression(empty)?;
32932            self.write_keyword(" ON EMPTY");
32933        }
32934        if let Some(error) = &e.error {
32935            if e.empty.is_some() {
32936                self.write_space();
32937            }
32938            self.generate_expression(error)?;
32939            self.write_keyword(" ON ERROR");
32940        }
32941        if let Some(null) = &e.null {
32942            if e.empty.is_some() || e.error.is_some() {
32943                self.write_space();
32944            }
32945            self.generate_expression(null)?;
32946            self.write_keyword(" ON NULL");
32947        }
32948        Ok(())
32949    }
32950
32951    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
32952        // Materialize doesn't support ON CONFLICT - skip entirely
32953        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
32954            return Ok(());
32955        }
32956        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
32957        if e.duplicate.is_some() {
32958            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
32959            self.write_keyword("ON DUPLICATE KEY UPDATE");
32960            for (i, expr) in e.expressions.iter().enumerate() {
32961                if i > 0 {
32962                    self.write(",");
32963                }
32964                self.write_space();
32965                self.generate_expression(expr)?;
32966            }
32967            return Ok(());
32968        } else {
32969            self.write_keyword("ON CONFLICT");
32970        }
32971        if let Some(constraint) = &e.constraint {
32972            self.write_keyword(" ON CONSTRAINT ");
32973            self.generate_expression(constraint)?;
32974        }
32975        if let Some(conflict_keys) = &e.conflict_keys {
32976            // conflict_keys can be a Tuple containing expressions
32977            if let Expression::Tuple(t) = conflict_keys.as_ref() {
32978                self.write("(");
32979                for (i, expr) in t.expressions.iter().enumerate() {
32980                    if i > 0 {
32981                        self.write(", ");
32982                    }
32983                    self.generate_expression(expr)?;
32984                }
32985                self.write(")");
32986            } else {
32987                self.write("(");
32988                self.generate_expression(conflict_keys)?;
32989                self.write(")");
32990            }
32991        }
32992        if let Some(index_predicate) = &e.index_predicate {
32993            self.write_keyword(" WHERE ");
32994            self.generate_expression(index_predicate)?;
32995        }
32996        if let Some(action) = &e.action {
32997            // Check if action is "NOTHING" or an UPDATE set
32998            if let Expression::Identifier(id) = action.as_ref() {
32999                if id.name.eq_ignore_ascii_case("NOTHING") {
33000                    self.write_keyword(" DO NOTHING");
33001                } else {
33002                    self.write_keyword(" DO ");
33003                    self.generate_expression(action)?;
33004                }
33005            } else if let Expression::Tuple(t) = action.as_ref() {
33006                // DO UPDATE SET col1 = val1, col2 = val2
33007                self.write_keyword(" DO UPDATE SET ");
33008                for (i, expr) in t.expressions.iter().enumerate() {
33009                    if i > 0 {
33010                        self.write(", ");
33011                    }
33012                    self.generate_expression(expr)?;
33013                }
33014            } else {
33015                self.write_keyword(" DO ");
33016                self.generate_expression(action)?;
33017            }
33018        }
33019        // WHERE clause for the UPDATE action
33020        if let Some(where_) = &e.where_ {
33021            self.write_keyword(" WHERE ");
33022            self.generate_expression(where_)?;
33023        }
33024        Ok(())
33025    }
33026
33027    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
33028        // ON property_value
33029        self.write_keyword("ON");
33030        self.write_space();
33031        self.generate_expression(&e.this)?;
33032        Ok(())
33033    }
33034
33035    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
33036        // Python: this expression (e.g., column opclass)
33037        self.generate_expression(&e.this)?;
33038        self.write_space();
33039        self.generate_expression(&e.expression)?;
33040        Ok(())
33041    }
33042
33043    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
33044        // Python: OPENJSON(this[, path]) [WITH (columns)]
33045        self.write_keyword("OPENJSON");
33046        self.write("(");
33047        self.generate_expression(&e.this)?;
33048        if let Some(path) = &e.path {
33049            self.write(", ");
33050            self.generate_expression(path)?;
33051        }
33052        self.write(")");
33053        if !e.expressions.is_empty() {
33054            self.write_keyword(" WITH");
33055            if self.config.pretty {
33056                self.write(" (\n");
33057                self.indent_level += 2;
33058                for (i, expr) in e.expressions.iter().enumerate() {
33059                    if i > 0 {
33060                        self.write(",\n");
33061                    }
33062                    self.write_indent();
33063                    self.generate_expression(expr)?;
33064                }
33065                self.write("\n");
33066                self.indent_level -= 2;
33067                self.write(")");
33068            } else {
33069                self.write(" (");
33070                for (i, expr) in e.expressions.iter().enumerate() {
33071                    if i > 0 {
33072                        self.write(", ");
33073                    }
33074                    self.generate_expression(expr)?;
33075                }
33076                self.write(")");
33077            }
33078        }
33079        Ok(())
33080    }
33081
33082    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
33083        // Python: this kind [path] [AS JSON]
33084        self.generate_expression(&e.this)?;
33085        self.write_space();
33086        // Use parsed data_type if available, otherwise fall back to kind string
33087        if let Some(ref dt) = e.data_type {
33088            self.generate_data_type(dt)?;
33089        } else if !e.kind.is_empty() {
33090            self.write(&e.kind);
33091        }
33092        if let Some(path) = &e.path {
33093            self.write_space();
33094            self.generate_expression(path)?;
33095        }
33096        if e.as_json.is_some() {
33097            self.write_keyword(" AS JSON");
33098        }
33099        Ok(())
33100    }
33101
33102    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
33103        // this OPERATOR(op) expression
33104        self.generate_expression(&e.this)?;
33105        self.write_space();
33106        if let Some(op) = &e.operator {
33107            self.write_keyword("OPERATOR");
33108            self.write("(");
33109            self.generate_expression(op)?;
33110            self.write(")");
33111        }
33112        // Emit inline comments between OPERATOR() and the RHS
33113        for comment in &e.comments {
33114            self.write_space();
33115            self.write_formatted_comment(comment);
33116        }
33117        self.write_space();
33118        self.generate_expression(&e.expression)?;
33119        Ok(())
33120    }
33121
33122    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
33123        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
33124        self.write_keyword("ORDER BY");
33125        let pretty_clickhouse_single_paren = self.config.pretty
33126            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
33127            && e.expressions.len() == 1
33128            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
33129        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
33130            && e.expressions.len() == 1
33131            && matches!(e.expressions[0].this, Expression::Tuple(_))
33132            && !e.expressions[0].desc
33133            && e.expressions[0].nulls_first.is_none();
33134
33135        if pretty_clickhouse_single_paren {
33136            self.write_space();
33137            if let Expression::Paren(p) = &e.expressions[0].this {
33138                self.write("(");
33139                self.write_newline();
33140                self.indent_level += 1;
33141                self.write_indent();
33142                self.generate_expression(&p.this)?;
33143                self.indent_level -= 1;
33144                self.write_newline();
33145                self.write(")");
33146            }
33147            return Ok(());
33148        }
33149
33150        if clickhouse_single_tuple {
33151            self.write_space();
33152            if let Expression::Tuple(t) = &e.expressions[0].this {
33153                self.write("(");
33154                for (i, expr) in t.expressions.iter().enumerate() {
33155                    if i > 0 {
33156                        self.write(", ");
33157                    }
33158                    self.generate_expression(expr)?;
33159                }
33160                self.write(")");
33161            }
33162            return Ok(());
33163        }
33164
33165        self.write_space();
33166        for (i, ordered) in e.expressions.iter().enumerate() {
33167            if i > 0 {
33168                self.write(", ");
33169            }
33170            self.generate_expression(&ordered.this)?;
33171            if ordered.desc {
33172                self.write_space();
33173                self.write_keyword("DESC");
33174            } else if ordered.explicit_asc {
33175                self.write_space();
33176                self.write_keyword("ASC");
33177            }
33178            if let Some(nulls_first) = ordered.nulls_first {
33179                // In Dremio, NULLS LAST is the default, so skip generating it
33180                let skip_nulls_last =
33181                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
33182                if !skip_nulls_last {
33183                    self.write_space();
33184                    self.write_keyword("NULLS");
33185                    self.write_space();
33186                    if nulls_first {
33187                        self.write_keyword("FIRST");
33188                    } else {
33189                        self.write_keyword("LAST");
33190                    }
33191                }
33192            }
33193        }
33194        Ok(())
33195    }
33196
33197    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
33198        // OUTPUT(model)
33199        self.write_keyword("OUTPUT");
33200        self.write("(");
33201        if self.config.pretty {
33202            self.indent_level += 1;
33203            self.write_newline();
33204            self.write_indent();
33205            self.generate_expression(&e.this)?;
33206            self.indent_level -= 1;
33207            self.write_newline();
33208        } else {
33209            self.generate_expression(&e.this)?;
33210        }
33211        self.write(")");
33212        Ok(())
33213    }
33214
33215    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
33216        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
33217        self.write_keyword("TRUNCATE");
33218        if let Some(this) = &e.this {
33219            self.write_space();
33220            self.generate_expression(this)?;
33221        }
33222        if e.with_count.is_some() {
33223            self.write_keyword(" WITH COUNT");
33224        } else {
33225            self.write_keyword(" WITHOUT COUNT");
33226        }
33227        Ok(())
33228    }
33229
33230    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
33231        // Python: name(expressions)(params)
33232        self.generate_expression(&e.this)?;
33233        self.write("(");
33234        for (i, expr) in e.expressions.iter().enumerate() {
33235            if i > 0 {
33236                self.write(", ");
33237            }
33238            self.generate_expression(expr)?;
33239        }
33240        self.write(")(");
33241        for (i, param) in e.params.iter().enumerate() {
33242            if i > 0 {
33243                self.write(", ");
33244            }
33245            self.generate_expression(param)?;
33246        }
33247        self.write(")");
33248        Ok(())
33249    }
33250
33251    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
33252        // PARSE_DATETIME(format, this) or similar
33253        self.write_keyword("PARSE_DATETIME");
33254        self.write("(");
33255        if let Some(format) = &e.format {
33256            self.write("'");
33257            self.write(format);
33258            self.write("', ");
33259        }
33260        self.generate_expression(&e.this)?;
33261        if let Some(zone) = &e.zone {
33262            self.write(", ");
33263            self.generate_expression(zone)?;
33264        }
33265        self.write(")");
33266        Ok(())
33267    }
33268
33269    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
33270        // PARSE_IP(this, type, permissive)
33271        self.write_keyword("PARSE_IP");
33272        self.write("(");
33273        self.generate_expression(&e.this)?;
33274        if let Some(type_) = &e.type_ {
33275            self.write(", ");
33276            self.generate_expression(type_)?;
33277        }
33278        if let Some(permissive) = &e.permissive {
33279            self.write(", ");
33280            self.generate_expression(permissive)?;
33281        }
33282        self.write(")");
33283        Ok(())
33284    }
33285
33286    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
33287        // PARSE_JSON(this, [expression])
33288        self.write_keyword("PARSE_JSON");
33289        self.write("(");
33290        self.generate_expression(&e.this)?;
33291        if let Some(expression) = &e.expression {
33292            self.write(", ");
33293            self.generate_expression(expression)?;
33294        }
33295        self.write(")");
33296        Ok(())
33297    }
33298
33299    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
33300        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
33301        self.write_keyword("PARSE_TIME");
33302        self.write("(");
33303        self.write(&format!("'{}'", e.format));
33304        self.write(", ");
33305        self.generate_expression(&e.this)?;
33306        self.write(")");
33307        Ok(())
33308    }
33309
33310    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
33311        // PARSE_URL(this, [part_to_extract], [key], [permissive])
33312        self.write_keyword("PARSE_URL");
33313        self.write("(");
33314        self.generate_expression(&e.this)?;
33315        if let Some(part) = &e.part_to_extract {
33316            self.write(", ");
33317            self.generate_expression(part)?;
33318        }
33319        if let Some(key) = &e.key {
33320            self.write(", ");
33321            self.generate_expression(key)?;
33322        }
33323        if let Some(permissive) = &e.permissive {
33324            self.write(", ");
33325            self.generate_expression(permissive)?;
33326        }
33327        self.write(")");
33328        Ok(())
33329    }
33330
33331    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
33332        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
33333        if e.subpartition {
33334            self.write_keyword("SUBPARTITION");
33335        } else {
33336            self.write_keyword("PARTITION");
33337        }
33338        self.write("(");
33339        for (i, expr) in e.expressions.iter().enumerate() {
33340            if i > 0 {
33341                self.write(", ");
33342            }
33343            self.generate_expression(expr)?;
33344        }
33345        self.write(")");
33346        Ok(())
33347    }
33348
33349    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
33350        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
33351        if let Some(this) = &e.this {
33352            if let Some(expression) = &e.expression {
33353                // WITH (MODULUS this, REMAINDER expression)
33354                self.write_keyword("WITH");
33355                self.write(" (");
33356                self.write_keyword("MODULUS");
33357                self.write_space();
33358                self.generate_expression(this)?;
33359                self.write(", ");
33360                self.write_keyword("REMAINDER");
33361                self.write_space();
33362                self.generate_expression(expression)?;
33363                self.write(")");
33364            } else {
33365                // IN (this) - this could be a list
33366                self.write_keyword("IN");
33367                self.write(" (");
33368                self.generate_partition_bound_values(this)?;
33369                self.write(")");
33370            }
33371        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
33372            // FROM (from_expressions) TO (to_expressions)
33373            self.write_keyword("FROM");
33374            self.write(" (");
33375            self.generate_partition_bound_values(from)?;
33376            self.write(") ");
33377            self.write_keyword("TO");
33378            self.write(" (");
33379            self.generate_partition_bound_values(to)?;
33380            self.write(")");
33381        }
33382        Ok(())
33383    }
33384
33385    /// Generate partition bound values - handles Tuple expressions by outputting
33386    /// contents without wrapping parens (since caller provides the parens)
33387    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
33388        if let Expression::Tuple(t) = expr {
33389            for (i, e) in t.expressions.iter().enumerate() {
33390                if i > 0 {
33391                    self.write(", ");
33392                }
33393                self.generate_expression(e)?;
33394            }
33395            Ok(())
33396        } else {
33397            self.generate_expression(expr)
33398        }
33399    }
33400
33401    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
33402        // PARTITION BY LIST (partition_expressions) (create_expressions)
33403        self.write_keyword("PARTITION BY LIST");
33404        if let Some(partition_exprs) = &e.partition_expressions {
33405            self.write(" (");
33406            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
33407            self.generate_doris_partition_expressions(partition_exprs)?;
33408            self.write(")");
33409        }
33410        if let Some(create_exprs) = &e.create_expressions {
33411            self.write(" (");
33412            // Unwrap Tuple for partition definitions
33413            self.generate_doris_partition_definitions(create_exprs)?;
33414            self.write(")");
33415        }
33416        Ok(())
33417    }
33418
33419    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
33420        // PARTITION BY RANGE (partition_expressions) (create_expressions)
33421        self.write_keyword("PARTITION BY RANGE");
33422        if let Some(partition_exprs) = &e.partition_expressions {
33423            self.write(" (");
33424            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
33425            self.generate_doris_partition_expressions(partition_exprs)?;
33426            self.write(")");
33427        }
33428        if let Some(create_exprs) = &e.create_expressions {
33429            self.write(" (");
33430            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
33431            self.generate_doris_partition_definitions(create_exprs)?;
33432            self.write(")");
33433        }
33434        Ok(())
33435    }
33436
33437    /// Generate Doris partition column expressions (unwrap Tuple)
33438    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
33439        if let Expression::Tuple(t) = expr {
33440            for (i, e) in t.expressions.iter().enumerate() {
33441                if i > 0 {
33442                    self.write(", ");
33443                }
33444                self.generate_expression(e)?;
33445            }
33446        } else {
33447            self.generate_expression(expr)?;
33448        }
33449        Ok(())
33450    }
33451
33452    /// Generate Doris partition definitions (comma-separated Partition expressions)
33453    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
33454        match expr {
33455            Expression::Tuple(t) => {
33456                // Multiple partitions, comma-separated
33457                for (i, part) in t.expressions.iter().enumerate() {
33458                    if i > 0 {
33459                        self.write(", ");
33460                    }
33461                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
33462                    if let Expression::Partition(p) = part {
33463                        for (j, inner) in p.expressions.iter().enumerate() {
33464                            if j > 0 {
33465                                self.write(", ");
33466                            }
33467                            self.generate_expression(inner)?;
33468                        }
33469                    } else {
33470                        self.generate_expression(part)?;
33471                    }
33472                }
33473            }
33474            Expression::PartitionByRangePropertyDynamic(_) => {
33475                // Dynamic partition - FROM/TO/INTERVAL
33476                self.generate_expression(expr)?;
33477            }
33478            _ => {
33479                self.generate_expression(expr)?;
33480            }
33481        }
33482        Ok(())
33483    }
33484
33485    fn generate_partition_by_range_property_dynamic(
33486        &mut self,
33487        e: &PartitionByRangePropertyDynamic,
33488    ) -> Result<()> {
33489        if e.use_start_end {
33490            // StarRocks: START ('val') END ('val') EVERY (expr)
33491            if let Some(start) = &e.start {
33492                self.write_keyword("START");
33493                self.write(" (");
33494                self.generate_expression(start)?;
33495                self.write(")");
33496            }
33497            if let Some(end) = &e.end {
33498                self.write_space();
33499                self.write_keyword("END");
33500                self.write(" (");
33501                self.generate_expression(end)?;
33502                self.write(")");
33503            }
33504            if let Some(every) = &e.every {
33505                self.write_space();
33506                self.write_keyword("EVERY");
33507                self.write(" (");
33508                // Use unquoted interval format for StarRocks
33509                self.generate_doris_interval(every)?;
33510                self.write(")");
33511            }
33512        } else {
33513            // Doris: FROM (start) TO (end) INTERVAL n UNIT
33514            if let Some(start) = &e.start {
33515                self.write_keyword("FROM");
33516                self.write(" (");
33517                self.generate_expression(start)?;
33518                self.write(")");
33519            }
33520            if let Some(end) = &e.end {
33521                self.write_space();
33522                self.write_keyword("TO");
33523                self.write(" (");
33524                self.generate_expression(end)?;
33525                self.write(")");
33526            }
33527            if let Some(every) = &e.every {
33528                self.write_space();
33529                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
33530                self.generate_doris_interval(every)?;
33531            }
33532        }
33533        Ok(())
33534    }
33535
33536    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
33537    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
33538        if let Expression::Interval(interval) = expr {
33539            self.write_keyword("INTERVAL");
33540            if let Some(ref value) = interval.this {
33541                self.write_space();
33542                // If the value is a string literal that looks like a number,
33543                // output it without quotes (matching Python sqlglot's
33544                // partitionbyrangepropertydynamic_sql which converts back to number)
33545                match value {
33546                    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()) => {
33547                        if let Literal::String(s) = lit.as_ref() {
33548                            self.write(s);
33549                        }
33550                    }
33551                    _ => {
33552                        self.generate_expression(value)?;
33553                    }
33554                }
33555            }
33556            if let Some(ref unit_spec) = interval.unit {
33557                self.write_space();
33558                self.write_interval_unit_spec(unit_spec)?;
33559            }
33560            Ok(())
33561        } else {
33562            self.generate_expression(expr)
33563        }
33564    }
33565
33566    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
33567        // TRUNCATE(expression, this)
33568        self.write_keyword("TRUNCATE");
33569        self.write("(");
33570        self.generate_expression(&e.expression)?;
33571        self.write(", ");
33572        self.generate_expression(&e.this)?;
33573        self.write(")");
33574        Ok(())
33575    }
33576
33577    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
33578        // Doris: PARTITION name VALUES IN (val1, val2)
33579        self.write_keyword("PARTITION");
33580        self.write_space();
33581        self.generate_expression(&e.this)?;
33582        self.write_space();
33583        self.write_keyword("VALUES IN");
33584        self.write(" (");
33585        for (i, expr) in e.expressions.iter().enumerate() {
33586            if i > 0 {
33587                self.write(", ");
33588            }
33589            self.generate_expression(expr)?;
33590        }
33591        self.write(")");
33592        Ok(())
33593    }
33594
33595    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
33596        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
33597        // TSQL ranges have no expressions and just use `this TO expression`
33598        if e.expressions.is_empty() && e.expression.is_some() {
33599            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
33600            self.generate_expression(&e.this)?;
33601            self.write_space();
33602            self.write_keyword("TO");
33603            self.write_space();
33604            self.generate_expression(e.expression.as_ref().unwrap())?;
33605            return Ok(());
33606        }
33607
33608        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
33609        self.write_keyword("PARTITION");
33610        self.write_space();
33611        self.generate_expression(&e.this)?;
33612        self.write_space();
33613
33614        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
33615        if e.expressions.len() == 1 {
33616            // Single value: VALUES LESS THAN (val)
33617            self.write_keyword("VALUES LESS THAN");
33618            self.write(" (");
33619            self.generate_expression(&e.expressions[0])?;
33620            self.write(")");
33621        } else if !e.expressions.is_empty() {
33622            // Multiple values with Tuple: VALUES [(val1), (val2))
33623            self.write_keyword("VALUES");
33624            self.write(" [");
33625            for (i, expr) in e.expressions.iter().enumerate() {
33626                if i > 0 {
33627                    self.write(", ");
33628                }
33629                // If the expr is a Tuple, generate its contents wrapped in parens
33630                if let Expression::Tuple(t) = expr {
33631                    self.write("(");
33632                    for (j, inner) in t.expressions.iter().enumerate() {
33633                        if j > 0 {
33634                            self.write(", ");
33635                        }
33636                        self.generate_expression(inner)?;
33637                    }
33638                    self.write(")");
33639                } else {
33640                    self.write("(");
33641                    self.generate_expression(expr)?;
33642                    self.write(")");
33643                }
33644            }
33645            self.write(")");
33646        }
33647        Ok(())
33648    }
33649
33650    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
33651        // BUCKET(this, expression)
33652        self.write_keyword("BUCKET");
33653        self.write("(");
33654        self.generate_expression(&e.this)?;
33655        self.write(", ");
33656        self.generate_expression(&e.expression)?;
33657        self.write(")");
33658        Ok(())
33659    }
33660
33661    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
33662        // BigQuery table property: PARTITION BY expression [, expression ...]
33663        self.write_keyword("PARTITION BY");
33664        self.write_space();
33665        for (i, expr) in e.expressions.iter().enumerate() {
33666            if i > 0 {
33667                self.write(", ");
33668            }
33669            self.generate_expression(expr)?;
33670        }
33671        Ok(())
33672    }
33673
33674    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
33675        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
33676        if matches!(
33677            self.config.dialect,
33678            Some(crate::dialects::DialectType::Teradata)
33679                | Some(crate::dialects::DialectType::ClickHouse)
33680        ) {
33681            self.write_keyword("PARTITION BY");
33682        } else {
33683            self.write_keyword("PARTITIONED BY");
33684        }
33685        self.write_space();
33686        // In pretty mode, always use multiline tuple format for PARTITIONED BY
33687        if self.config.pretty {
33688            if let Expression::Tuple(ref tuple) = *e.this {
33689                self.write("(");
33690                self.write_newline();
33691                self.indent_level += 1;
33692                for (i, expr) in tuple.expressions.iter().enumerate() {
33693                    if i > 0 {
33694                        self.write(",");
33695                        self.write_newline();
33696                    }
33697                    self.write_indent();
33698                    self.generate_expression(expr)?;
33699                }
33700                self.indent_level -= 1;
33701                self.write_newline();
33702                self.write(")");
33703            } else {
33704                self.generate_expression(&e.this)?;
33705            }
33706        } else {
33707            self.generate_expression(&e.this)?;
33708        }
33709        Ok(())
33710    }
33711
33712    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
33713        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
33714        self.write_keyword("PARTITION OF");
33715        self.write_space();
33716        self.generate_expression(&e.this)?;
33717        // Check if expression is a PartitionBoundSpec
33718        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
33719            self.write_space();
33720            self.write_keyword("FOR VALUES");
33721            self.write_space();
33722            self.generate_expression(&e.expression)?;
33723        } else {
33724            self.write_space();
33725            self.write_keyword("DEFAULT");
33726        }
33727        Ok(())
33728    }
33729
33730    fn generate_period_for_system_time_constraint(
33731        &mut self,
33732        e: &PeriodForSystemTimeConstraint,
33733    ) -> Result<()> {
33734        // PERIOD FOR SYSTEM_TIME (this, expression)
33735        self.write_keyword("PERIOD FOR SYSTEM_TIME");
33736        self.write(" (");
33737        self.generate_expression(&e.this)?;
33738        self.write(", ");
33739        self.generate_expression(&e.expression)?;
33740        self.write(")");
33741        Ok(())
33742    }
33743
33744    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
33745        // value AS alias
33746        // The alias can be an identifier or an expression (e.g., string concatenation)
33747        self.generate_expression(&e.this)?;
33748        self.write_space();
33749        self.write_keyword("AS");
33750        self.write_space();
33751        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
33752        if self.config.unpivot_aliases_are_identifiers {
33753            match &e.alias {
33754                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
33755                    let Literal::String(s) = lit.as_ref() else {
33756                        unreachable!()
33757                    };
33758                    // Convert string literal to identifier
33759                    self.generate_identifier(&Identifier::new(s.clone()))?;
33760                }
33761                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
33762                    let Literal::Number(n) = lit.as_ref() else {
33763                        unreachable!()
33764                    };
33765                    // Convert number literal to quoted identifier
33766                    let mut id = Identifier::new(n.clone());
33767                    id.quoted = true;
33768                    self.generate_identifier(&id)?;
33769                }
33770                other => {
33771                    self.generate_expression(other)?;
33772                }
33773            }
33774        } else {
33775            self.generate_expression(&e.alias)?;
33776        }
33777        Ok(())
33778    }
33779
33780    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
33781        // ANY or ANY [expression]
33782        self.write_keyword("ANY");
33783        if let Some(this) = &e.this {
33784            self.write_space();
33785            self.generate_expression(this)?;
33786        }
33787        Ok(())
33788    }
33789
33790    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
33791        // ML.PREDICT(MODEL this, expression, [params_struct])
33792        self.write_keyword("ML.PREDICT");
33793        self.write("(");
33794        self.write_keyword("MODEL");
33795        self.write_space();
33796        self.generate_expression(&e.this)?;
33797        self.write(", ");
33798        self.generate_expression(&e.expression)?;
33799        if let Some(params) = &e.params_struct {
33800            self.write(", ");
33801            self.generate_expression(params)?;
33802        }
33803        self.write(")");
33804        Ok(())
33805    }
33806
33807    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
33808        // PREVIOUS_DAY(this, expression)
33809        self.write_keyword("PREVIOUS_DAY");
33810        self.write("(");
33811        self.generate_expression(&e.this)?;
33812        self.write(", ");
33813        self.generate_expression(&e.expression)?;
33814        self.write(")");
33815        Ok(())
33816    }
33817
33818    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
33819        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
33820        self.write_keyword("PRIMARY KEY");
33821        if let Some(name) = &e.this {
33822            self.write_space();
33823            self.generate_expression(name)?;
33824        }
33825        if !e.expressions.is_empty() {
33826            self.write(" (");
33827            for (i, expr) in e.expressions.iter().enumerate() {
33828                if i > 0 {
33829                    self.write(", ");
33830                }
33831                self.generate_expression(expr)?;
33832            }
33833            self.write(")");
33834        }
33835        if let Some(include) = &e.include {
33836            self.write_space();
33837            self.generate_expression(include)?;
33838        }
33839        if !e.options.is_empty() {
33840            self.write_space();
33841            for (i, opt) in e.options.iter().enumerate() {
33842                if i > 0 {
33843                    self.write_space();
33844                }
33845                self.generate_expression(opt)?;
33846            }
33847        }
33848        Ok(())
33849    }
33850
33851    fn generate_primary_key_column_constraint(
33852        &mut self,
33853        _e: &PrimaryKeyColumnConstraint,
33854    ) -> Result<()> {
33855        // PRIMARY KEY constraint at column level
33856        self.write_keyword("PRIMARY KEY");
33857        Ok(())
33858    }
33859
33860    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
33861        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
33862        self.write_keyword("PATH");
33863        self.write_space();
33864        self.generate_expression(&e.this)?;
33865        Ok(())
33866    }
33867
33868    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
33869        // PROJECTION this (expression)
33870        self.write_keyword("PROJECTION");
33871        self.write_space();
33872        self.generate_expression(&e.this)?;
33873        self.write(" (");
33874        self.generate_expression(&e.expression)?;
33875        self.write(")");
33876        Ok(())
33877    }
33878
33879    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
33880        // Properties list
33881        for (i, prop) in e.expressions.iter().enumerate() {
33882            if i > 0 {
33883                self.write(", ");
33884            }
33885            self.generate_expression(prop)?;
33886        }
33887        Ok(())
33888    }
33889
33890    fn generate_property(&mut self, e: &Property) -> Result<()> {
33891        // name=value
33892        self.generate_expression(&e.this)?;
33893        if let Some(value) = &e.value {
33894            self.write("=");
33895            self.generate_expression(value)?;
33896        }
33897        Ok(())
33898    }
33899
33900    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
33901        self.write_keyword("OPTIONS");
33902        if e.entries.is_empty() {
33903            self.write(" ()");
33904            return Ok(());
33905        }
33906
33907        if self.config.pretty {
33908            self.write(" (");
33909            self.write_newline();
33910            self.indent_level += 1;
33911            for (i, entry) in e.entries.iter().enumerate() {
33912                if i > 0 {
33913                    self.write(",");
33914                    self.write_newline();
33915                }
33916                self.write_indent();
33917                self.generate_identifier(&entry.key)?;
33918                self.write("=");
33919                self.generate_expression(&entry.value)?;
33920            }
33921            self.indent_level -= 1;
33922            self.write_newline();
33923            self.write(")");
33924        } else {
33925            self.write(" (");
33926            for (i, entry) in e.entries.iter().enumerate() {
33927                if i > 0 {
33928                    self.write(", ");
33929                }
33930                self.generate_identifier(&entry.key)?;
33931                self.write("=");
33932                self.generate_expression(&entry.value)?;
33933            }
33934            self.write(")");
33935        }
33936        Ok(())
33937    }
33938
33939    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
33940    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
33941        self.write_keyword("OPTIONS");
33942        self.write(" (");
33943        for (i, opt) in options.iter().enumerate() {
33944            if i > 0 {
33945                self.write(", ");
33946            }
33947            self.generate_option_expression(opt)?;
33948        }
33949        self.write(")");
33950        Ok(())
33951    }
33952
33953    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
33954    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
33955        self.write_keyword("PROPERTIES");
33956        self.write(" (");
33957        for (i, prop) in properties.iter().enumerate() {
33958            if i > 0 {
33959                self.write(", ");
33960            }
33961            self.generate_option_expression(prop)?;
33962        }
33963        self.write(")");
33964        Ok(())
33965    }
33966
33967    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
33968    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
33969        self.write_keyword("ENVIRONMENT");
33970        self.write(" (");
33971        for (i, env_item) in environment.iter().enumerate() {
33972            if i > 0 {
33973                self.write(", ");
33974            }
33975            self.generate_environment_expression(env_item)?;
33976        }
33977        self.write(")");
33978        Ok(())
33979    }
33980
33981    /// Generate an environment expression with spaces around =
33982    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
33983        match expr {
33984            Expression::Eq(eq) => {
33985                // Generate key = value with spaces (Databricks ENVIRONMENT style)
33986                self.generate_expression(&eq.left)?;
33987                self.write(" = ");
33988                self.generate_expression(&eq.right)?;
33989                Ok(())
33990            }
33991            _ => self.generate_expression(expr),
33992        }
33993    }
33994
33995    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
33996    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
33997        self.write_keyword("TBLPROPERTIES");
33998        if self.config.pretty {
33999            self.write(" (");
34000            self.write_newline();
34001            self.indent_level += 1;
34002            for (i, opt) in options.iter().enumerate() {
34003                if i > 0 {
34004                    self.write(",");
34005                    self.write_newline();
34006                }
34007                self.write_indent();
34008                self.generate_option_expression(opt)?;
34009            }
34010            self.indent_level -= 1;
34011            self.write_newline();
34012            self.write(")");
34013        } else {
34014            self.write(" (");
34015            for (i, opt) in options.iter().enumerate() {
34016                if i > 0 {
34017                    self.write(", ");
34018                }
34019                self.generate_option_expression(opt)?;
34020            }
34021            self.write(")");
34022        }
34023        Ok(())
34024    }
34025
34026    /// Generate an option expression without spaces around =
34027    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
34028        match expr {
34029            Expression::Eq(eq) => {
34030                // Generate key=value without spaces
34031                self.generate_expression(&eq.left)?;
34032                self.write("=");
34033                self.generate_expression(&eq.right)?;
34034                Ok(())
34035            }
34036            _ => self.generate_expression(expr),
34037        }
34038    }
34039
34040    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
34041        // Just output the name
34042        self.generate_expression(&e.this)?;
34043        Ok(())
34044    }
34045
34046    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
34047        // PUT source_file @stage [options]
34048        self.write_keyword("PUT");
34049        self.write_space();
34050
34051        // Source file path - preserve original quoting
34052        if e.source_quoted {
34053            self.write("'");
34054            self.write(&e.source);
34055            self.write("'");
34056        } else {
34057            self.write(&e.source);
34058        }
34059
34060        self.write_space();
34061
34062        // Target stage reference - output the string directly (includes @)
34063        if let Expression::Literal(lit) = &e.target {
34064            if let Literal::String(s) = lit.as_ref() {
34065                self.write(s);
34066            }
34067        } else {
34068            self.generate_expression(&e.target)?;
34069        }
34070
34071        // Optional parameters: KEY=VALUE
34072        for param in &e.params {
34073            self.write_space();
34074            self.write(&param.name);
34075            if let Some(ref value) = param.value {
34076                self.write("=");
34077                self.generate_expression(value)?;
34078            }
34079        }
34080
34081        Ok(())
34082    }
34083
34084    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
34085        // QUANTILE(this, quantile)
34086        self.write_keyword("QUANTILE");
34087        self.write("(");
34088        self.generate_expression(&e.this)?;
34089        if let Some(quantile) = &e.quantile {
34090            self.write(", ");
34091            self.generate_expression(quantile)?;
34092        }
34093        self.write(")");
34094        Ok(())
34095    }
34096
34097    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
34098        // QUERY_BAND = this [UPDATE] [FOR scope]
34099        if matches!(
34100            self.config.dialect,
34101            Some(crate::dialects::DialectType::Teradata)
34102        ) {
34103            self.write_keyword("SET");
34104            self.write_space();
34105        }
34106        self.write_keyword("QUERY_BAND");
34107        self.write(" = ");
34108        self.generate_expression(&e.this)?;
34109        if e.update.is_some() {
34110            self.write_space();
34111            self.write_keyword("UPDATE");
34112        }
34113        if let Some(scope) = &e.scope {
34114            self.write_space();
34115            self.write_keyword("FOR");
34116            self.write_space();
34117            self.generate_expression(scope)?;
34118        }
34119        Ok(())
34120    }
34121
34122    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
34123        // this = expression
34124        self.generate_expression(&e.this)?;
34125        if let Some(expression) = &e.expression {
34126            self.write(" = ");
34127            self.generate_expression(expression)?;
34128        }
34129        Ok(())
34130    }
34131
34132    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
34133        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
34134        self.write_keyword("TRANSFORM");
34135        self.write("(");
34136        for (i, expr) in e.expressions.iter().enumerate() {
34137            if i > 0 {
34138                self.write(", ");
34139            }
34140            self.generate_expression(expr)?;
34141        }
34142        self.write(")");
34143        if let Some(row_format_before) = &e.row_format_before {
34144            self.write_space();
34145            self.generate_expression(row_format_before)?;
34146        }
34147        if let Some(record_writer) = &e.record_writer {
34148            self.write_space();
34149            self.write_keyword("RECORDWRITER");
34150            self.write_space();
34151            self.generate_expression(record_writer)?;
34152        }
34153        if let Some(command_script) = &e.command_script {
34154            self.write_space();
34155            self.write_keyword("USING");
34156            self.write_space();
34157            self.generate_expression(command_script)?;
34158        }
34159        if let Some(schema) = &e.schema {
34160            self.write_space();
34161            self.write_keyword("AS");
34162            self.write_space();
34163            self.generate_expression(schema)?;
34164        }
34165        if let Some(row_format_after) = &e.row_format_after {
34166            self.write_space();
34167            self.generate_expression(row_format_after)?;
34168        }
34169        if let Some(record_reader) = &e.record_reader {
34170            self.write_space();
34171            self.write_keyword("RECORDREADER");
34172            self.write_space();
34173            self.generate_expression(record_reader)?;
34174        }
34175        Ok(())
34176    }
34177
34178    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
34179        // RANDN([seed])
34180        self.write_keyword("RANDN");
34181        self.write("(");
34182        if let Some(this) = &e.this {
34183            self.generate_expression(this)?;
34184        }
34185        self.write(")");
34186        Ok(())
34187    }
34188
34189    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
34190        // RANDSTR(this, [generator])
34191        self.write_keyword("RANDSTR");
34192        self.write("(");
34193        self.generate_expression(&e.this)?;
34194        if let Some(generator) = &e.generator {
34195            self.write(", ");
34196            self.generate_expression(generator)?;
34197        }
34198        self.write(")");
34199        Ok(())
34200    }
34201
34202    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
34203        // RANGE_BUCKET(this, expression)
34204        self.write_keyword("RANGE_BUCKET");
34205        self.write("(");
34206        self.generate_expression(&e.this)?;
34207        self.write(", ");
34208        self.generate_expression(&e.expression)?;
34209        self.write(")");
34210        Ok(())
34211    }
34212
34213    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
34214        // RANGE_N(this BETWEEN expressions [EACH each])
34215        self.write_keyword("RANGE_N");
34216        self.write("(");
34217        self.generate_expression(&e.this)?;
34218        self.write_space();
34219        self.write_keyword("BETWEEN");
34220        self.write_space();
34221        for (i, expr) in e.expressions.iter().enumerate() {
34222            if i > 0 {
34223                self.write(", ");
34224            }
34225            self.generate_expression(expr)?;
34226        }
34227        if let Some(each) = &e.each {
34228            self.write_space();
34229            self.write_keyword("EACH");
34230            self.write_space();
34231            self.generate_expression(each)?;
34232        }
34233        self.write(")");
34234        Ok(())
34235    }
34236
34237    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
34238        // READ_CSV(this, expressions...)
34239        self.write_keyword("READ_CSV");
34240        self.write("(");
34241        self.generate_expression(&e.this)?;
34242        for expr in &e.expressions {
34243            self.write(", ");
34244            self.generate_expression(expr)?;
34245        }
34246        self.write(")");
34247        Ok(())
34248    }
34249
34250    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
34251        // READ_PARQUET(expressions...)
34252        self.write_keyword("READ_PARQUET");
34253        self.write("(");
34254        for (i, expr) in e.expressions.iter().enumerate() {
34255            if i > 0 {
34256                self.write(", ");
34257            }
34258            self.generate_expression(expr)?;
34259        }
34260        self.write(")");
34261        Ok(())
34262    }
34263
34264    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
34265        // SEARCH kind FIRST BY this SET expression [USING using]
34266        // or CYCLE this SET expression [USING using]
34267        if e.kind == "CYCLE" {
34268            self.write_keyword("CYCLE");
34269        } else {
34270            self.write_keyword("SEARCH");
34271            self.write_space();
34272            self.write(&e.kind);
34273            self.write_space();
34274            self.write_keyword("FIRST BY");
34275        }
34276        self.write_space();
34277        self.generate_expression(&e.this)?;
34278        self.write_space();
34279        self.write_keyword("SET");
34280        self.write_space();
34281        self.generate_expression(&e.expression)?;
34282        if let Some(using) = &e.using {
34283            self.write_space();
34284            self.write_keyword("USING");
34285            self.write_space();
34286            self.generate_expression(using)?;
34287        }
34288        Ok(())
34289    }
34290
34291    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
34292        // REDUCE(this, initial, merge, [finish])
34293        self.write_keyword("REDUCE");
34294        self.write("(");
34295        self.generate_expression(&e.this)?;
34296        if let Some(initial) = &e.initial {
34297            self.write(", ");
34298            self.generate_expression(initial)?;
34299        }
34300        if let Some(merge) = &e.merge {
34301            self.write(", ");
34302            self.generate_expression(merge)?;
34303        }
34304        if let Some(finish) = &e.finish {
34305            self.write(", ");
34306            self.generate_expression(finish)?;
34307        }
34308        self.write(")");
34309        Ok(())
34310    }
34311
34312    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
34313        // REFERENCES this (expressions) [options]
34314        self.write_keyword("REFERENCES");
34315        self.write_space();
34316        self.generate_expression(&e.this)?;
34317        if !e.expressions.is_empty() {
34318            self.write(" (");
34319            for (i, expr) in e.expressions.iter().enumerate() {
34320                if i > 0 {
34321                    self.write(", ");
34322                }
34323                self.generate_expression(expr)?;
34324            }
34325            self.write(")");
34326        }
34327        for opt in &e.options {
34328            self.write_space();
34329            self.generate_expression(opt)?;
34330        }
34331        Ok(())
34332    }
34333
34334    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
34335        // REFRESH [kind] this
34336        self.write_keyword("REFRESH");
34337        if !e.kind.is_empty() {
34338            self.write_space();
34339            self.write_keyword(&e.kind);
34340        }
34341        self.write_space();
34342        self.generate_expression(&e.this)?;
34343        Ok(())
34344    }
34345
34346    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
34347        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
34348        self.write_keyword("REFRESH");
34349        self.write_space();
34350        self.write_keyword(&e.method);
34351
34352        if let Some(ref kind) = e.kind {
34353            self.write_space();
34354            self.write_keyword("ON");
34355            self.write_space();
34356            self.write_keyword(kind);
34357
34358            // EVERY n UNIT
34359            if let Some(ref every) = e.every {
34360                self.write_space();
34361                self.write_keyword("EVERY");
34362                self.write_space();
34363                self.generate_expression(every)?;
34364                if let Some(ref unit) = e.unit {
34365                    self.write_space();
34366                    self.write_keyword(unit);
34367                }
34368            }
34369
34370            // STARTS 'datetime'
34371            if let Some(ref starts) = e.starts {
34372                self.write_space();
34373                self.write_keyword("STARTS");
34374                self.write_space();
34375                self.generate_expression(starts)?;
34376            }
34377        }
34378        Ok(())
34379    }
34380
34381    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
34382        // REGEXP_COUNT(this, expression, position, parameters)
34383        self.write_keyword("REGEXP_COUNT");
34384        self.write("(");
34385        self.generate_expression(&e.this)?;
34386        self.write(", ");
34387        self.generate_expression(&e.expression)?;
34388        if let Some(position) = &e.position {
34389            self.write(", ");
34390            self.generate_expression(position)?;
34391        }
34392        if let Some(parameters) = &e.parameters {
34393            self.write(", ");
34394            self.generate_expression(parameters)?;
34395        }
34396        self.write(")");
34397        Ok(())
34398    }
34399
34400    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
34401        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
34402        self.write_keyword("REGEXP_EXTRACT_ALL");
34403        self.write("(");
34404        self.generate_expression(&e.this)?;
34405        self.write(", ");
34406        self.generate_expression(&e.expression)?;
34407        if let Some(group) = &e.group {
34408            self.write(", ");
34409            self.generate_expression(group)?;
34410        }
34411        self.write(")");
34412        Ok(())
34413    }
34414
34415    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
34416        // REGEXP_FULL_MATCH(this, expression)
34417        self.write_keyword("REGEXP_FULL_MATCH");
34418        self.write("(");
34419        self.generate_expression(&e.this)?;
34420        self.write(", ");
34421        self.generate_expression(&e.expression)?;
34422        self.write(")");
34423        Ok(())
34424    }
34425
34426    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
34427        use crate::dialects::DialectType;
34428        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
34429        if matches!(
34430            self.config.dialect,
34431            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
34432        ) && e.flag.is_none()
34433        {
34434            self.generate_expression(&e.this)?;
34435            self.write(" ~* ");
34436            self.generate_expression(&e.expression)?;
34437        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
34438            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
34439            self.write_keyword("REGEXP_LIKE");
34440            self.write("(");
34441            self.generate_expression(&e.this)?;
34442            self.write(", ");
34443            self.generate_expression(&e.expression)?;
34444            self.write(", ");
34445            if let Some(flag) = &e.flag {
34446                self.generate_expression(flag)?;
34447            } else {
34448                self.write("'i'");
34449            }
34450            self.write(")");
34451        } else {
34452            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
34453            self.generate_expression(&e.this)?;
34454            self.write_space();
34455            self.write_keyword("REGEXP_ILIKE");
34456            self.write_space();
34457            self.generate_expression(&e.expression)?;
34458            if let Some(flag) = &e.flag {
34459                self.write(", ");
34460                self.generate_expression(flag)?;
34461            }
34462        }
34463        Ok(())
34464    }
34465
34466    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
34467        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
34468        self.write_keyword("REGEXP_INSTR");
34469        self.write("(");
34470        self.generate_expression(&e.this)?;
34471        self.write(", ");
34472        self.generate_expression(&e.expression)?;
34473        if let Some(position) = &e.position {
34474            self.write(", ");
34475            self.generate_expression(position)?;
34476        }
34477        if let Some(occurrence) = &e.occurrence {
34478            self.write(", ");
34479            self.generate_expression(occurrence)?;
34480        }
34481        if let Some(option) = &e.option {
34482            self.write(", ");
34483            self.generate_expression(option)?;
34484        }
34485        if let Some(parameters) = &e.parameters {
34486            self.write(", ");
34487            self.generate_expression(parameters)?;
34488        }
34489        if let Some(group) = &e.group {
34490            self.write(", ");
34491            self.generate_expression(group)?;
34492        }
34493        self.write(")");
34494        Ok(())
34495    }
34496
34497    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
34498        // REGEXP_SPLIT(this, expression, limit)
34499        self.write_keyword("REGEXP_SPLIT");
34500        self.write("(");
34501        self.generate_expression(&e.this)?;
34502        self.write(", ");
34503        self.generate_expression(&e.expression)?;
34504        if let Some(limit) = &e.limit {
34505            self.write(", ");
34506            self.generate_expression(limit)?;
34507        }
34508        self.write(")");
34509        Ok(())
34510    }
34511
34512    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
34513        // REGR_AVGX(this, expression)
34514        self.write_keyword("REGR_AVGX");
34515        self.write("(");
34516        self.generate_expression(&e.this)?;
34517        self.write(", ");
34518        self.generate_expression(&e.expression)?;
34519        self.write(")");
34520        Ok(())
34521    }
34522
34523    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
34524        // REGR_AVGY(this, expression)
34525        self.write_keyword("REGR_AVGY");
34526        self.write("(");
34527        self.generate_expression(&e.this)?;
34528        self.write(", ");
34529        self.generate_expression(&e.expression)?;
34530        self.write(")");
34531        Ok(())
34532    }
34533
34534    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
34535        // REGR_COUNT(this, expression)
34536        self.write_keyword("REGR_COUNT");
34537        self.write("(");
34538        self.generate_expression(&e.this)?;
34539        self.write(", ");
34540        self.generate_expression(&e.expression)?;
34541        self.write(")");
34542        Ok(())
34543    }
34544
34545    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
34546        // REGR_INTERCEPT(this, expression)
34547        self.write_keyword("REGR_INTERCEPT");
34548        self.write("(");
34549        self.generate_expression(&e.this)?;
34550        self.write(", ");
34551        self.generate_expression(&e.expression)?;
34552        self.write(")");
34553        Ok(())
34554    }
34555
34556    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
34557        // REGR_R2(this, expression)
34558        self.write_keyword("REGR_R2");
34559        self.write("(");
34560        self.generate_expression(&e.this)?;
34561        self.write(", ");
34562        self.generate_expression(&e.expression)?;
34563        self.write(")");
34564        Ok(())
34565    }
34566
34567    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
34568        // REGR_SLOPE(this, expression)
34569        self.write_keyword("REGR_SLOPE");
34570        self.write("(");
34571        self.generate_expression(&e.this)?;
34572        self.write(", ");
34573        self.generate_expression(&e.expression)?;
34574        self.write(")");
34575        Ok(())
34576    }
34577
34578    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
34579        // REGR_SXX(this, expression)
34580        self.write_keyword("REGR_SXX");
34581        self.write("(");
34582        self.generate_expression(&e.this)?;
34583        self.write(", ");
34584        self.generate_expression(&e.expression)?;
34585        self.write(")");
34586        Ok(())
34587    }
34588
34589    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
34590        // REGR_SXY(this, expression)
34591        self.write_keyword("REGR_SXY");
34592        self.write("(");
34593        self.generate_expression(&e.this)?;
34594        self.write(", ");
34595        self.generate_expression(&e.expression)?;
34596        self.write(")");
34597        Ok(())
34598    }
34599
34600    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
34601        // REGR_SYY(this, expression)
34602        self.write_keyword("REGR_SYY");
34603        self.write("(");
34604        self.generate_expression(&e.this)?;
34605        self.write(", ");
34606        self.generate_expression(&e.expression)?;
34607        self.write(")");
34608        Ok(())
34609    }
34610
34611    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
34612        // REGR_VALX(this, expression)
34613        self.write_keyword("REGR_VALX");
34614        self.write("(");
34615        self.generate_expression(&e.this)?;
34616        self.write(", ");
34617        self.generate_expression(&e.expression)?;
34618        self.write(")");
34619        Ok(())
34620    }
34621
34622    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
34623        // REGR_VALY(this, expression)
34624        self.write_keyword("REGR_VALY");
34625        self.write("(");
34626        self.generate_expression(&e.this)?;
34627        self.write(", ");
34628        self.generate_expression(&e.expression)?;
34629        self.write(")");
34630        Ok(())
34631    }
34632
34633    fn generate_remote_with_connection_model_property(
34634        &mut self,
34635        e: &RemoteWithConnectionModelProperty,
34636    ) -> Result<()> {
34637        // REMOTE WITH CONNECTION this
34638        self.write_keyword("REMOTE WITH CONNECTION");
34639        self.write_space();
34640        self.generate_expression(&e.this)?;
34641        Ok(())
34642    }
34643
34644    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
34645        // RENAME COLUMN [IF EXISTS] this TO new_name
34646        self.write_keyword("RENAME COLUMN");
34647        if e.exists {
34648            self.write_space();
34649            self.write_keyword("IF EXISTS");
34650        }
34651        self.write_space();
34652        self.generate_expression(&e.this)?;
34653        if let Some(to) = &e.to {
34654            self.write_space();
34655            self.write_keyword("TO");
34656            self.write_space();
34657            self.generate_expression(to)?;
34658        }
34659        Ok(())
34660    }
34661
34662    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
34663        // REPLACE PARTITION expression [FROM source]
34664        self.write_keyword("REPLACE PARTITION");
34665        self.write_space();
34666        self.generate_expression(&e.expression)?;
34667        if let Some(source) = &e.source {
34668            self.write_space();
34669            self.write_keyword("FROM");
34670            self.write_space();
34671            self.generate_expression(source)?;
34672        }
34673        Ok(())
34674    }
34675
34676    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
34677        // RETURNING expressions [INTO into]
34678        // TSQL and Fabric use OUTPUT instead of RETURNING
34679        let keyword = match self.config.dialect {
34680            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
34681            _ => "RETURNING",
34682        };
34683        self.write_keyword(keyword);
34684        self.write_space();
34685        for (i, expr) in e.expressions.iter().enumerate() {
34686            if i > 0 {
34687                self.write(", ");
34688            }
34689            self.generate_expression(expr)?;
34690        }
34691        if let Some(into) = &e.into {
34692            self.write_space();
34693            self.write_keyword("INTO");
34694            self.write_space();
34695            self.generate_expression(into)?;
34696        }
34697        Ok(())
34698    }
34699
34700    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
34701        // OUTPUT expressions [INTO into_table]
34702        self.write_space();
34703        self.write_keyword("OUTPUT");
34704        self.write_space();
34705        for (i, expr) in output.columns.iter().enumerate() {
34706            if i > 0 {
34707                self.write(", ");
34708            }
34709            self.generate_expression(expr)?;
34710        }
34711        if let Some(into_table) = &output.into_table {
34712            self.write_space();
34713            self.write_keyword("INTO");
34714            self.write_space();
34715            self.generate_expression(into_table)?;
34716        }
34717        Ok(())
34718    }
34719
34720    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
34721        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
34722        self.write_keyword("RETURNS");
34723        if e.is_table.is_some() {
34724            self.write_space();
34725            self.write_keyword("TABLE");
34726        }
34727        if let Some(table) = &e.table {
34728            self.write_space();
34729            self.generate_expression(table)?;
34730        } else if let Some(this) = &e.this {
34731            self.write_space();
34732            self.generate_expression(this)?;
34733        }
34734        if e.null.is_some() {
34735            self.write_space();
34736            self.write_keyword("NULL ON NULL INPUT");
34737        }
34738        Ok(())
34739    }
34740
34741    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
34742        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
34743        self.write_keyword("ROLLBACK");
34744
34745        // TSQL always uses ROLLBACK TRANSACTION
34746        if e.this.is_none()
34747            && matches!(
34748                self.config.dialect,
34749                Some(DialectType::TSQL) | Some(DialectType::Fabric)
34750            )
34751        {
34752            self.write_space();
34753            self.write_keyword("TRANSACTION");
34754        }
34755
34756        // Check if this has TRANSACTION keyword or transaction name
34757        if let Some(this) = &e.this {
34758            // Check if it's just the "TRANSACTION" marker or an actual transaction name
34759            let is_transaction_marker = matches!(
34760                this.as_ref(),
34761                Expression::Identifier(id) if id.name == "TRANSACTION"
34762            );
34763
34764            self.write_space();
34765            self.write_keyword("TRANSACTION");
34766
34767            // If it's a real transaction name, output it
34768            if !is_transaction_marker {
34769                self.write_space();
34770                self.generate_expression(this)?;
34771            }
34772        }
34773
34774        // Output TO savepoint
34775        if let Some(savepoint) = &e.savepoint {
34776            self.write_space();
34777            self.write_keyword("TO");
34778            self.write_space();
34779            self.generate_expression(savepoint)?;
34780        }
34781        Ok(())
34782    }
34783
34784    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
34785        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
34786        if e.expressions.is_empty() {
34787            self.write_keyword("WITH ROLLUP");
34788        } else {
34789            self.write_keyword("ROLLUP");
34790            self.write("(");
34791            for (i, expr) in e.expressions.iter().enumerate() {
34792                if i > 0 {
34793                    self.write(", ");
34794                }
34795                self.generate_expression(expr)?;
34796            }
34797            self.write(")");
34798        }
34799        Ok(())
34800    }
34801
34802    fn generate_row_format_delimited_property(
34803        &mut self,
34804        e: &RowFormatDelimitedProperty,
34805    ) -> Result<()> {
34806        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
34807        self.write_keyword("ROW FORMAT DELIMITED");
34808        if let Some(fields) = &e.fields {
34809            self.write_space();
34810            self.write_keyword("FIELDS TERMINATED BY");
34811            self.write_space();
34812            self.generate_expression(fields)?;
34813        }
34814        if let Some(escaped) = &e.escaped {
34815            self.write_space();
34816            self.write_keyword("ESCAPED BY");
34817            self.write_space();
34818            self.generate_expression(escaped)?;
34819        }
34820        if let Some(items) = &e.collection_items {
34821            self.write_space();
34822            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
34823            self.write_space();
34824            self.generate_expression(items)?;
34825        }
34826        if let Some(keys) = &e.map_keys {
34827            self.write_space();
34828            self.write_keyword("MAP KEYS TERMINATED BY");
34829            self.write_space();
34830            self.generate_expression(keys)?;
34831        }
34832        if let Some(lines) = &e.lines {
34833            self.write_space();
34834            self.write_keyword("LINES TERMINATED BY");
34835            self.write_space();
34836            self.generate_expression(lines)?;
34837        }
34838        if let Some(null) = &e.null {
34839            self.write_space();
34840            self.write_keyword("NULL DEFINED AS");
34841            self.write_space();
34842            self.generate_expression(null)?;
34843        }
34844        if let Some(serde) = &e.serde {
34845            self.write_space();
34846            self.generate_expression(serde)?;
34847        }
34848        Ok(())
34849    }
34850
34851    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
34852        // ROW FORMAT this
34853        self.write_keyword("ROW FORMAT");
34854        self.write_space();
34855        self.generate_expression(&e.this)?;
34856        Ok(())
34857    }
34858
34859    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
34860        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
34861        self.write_keyword("ROW FORMAT SERDE");
34862        self.write_space();
34863        self.generate_expression(&e.this)?;
34864        if let Some(props) = &e.serde_properties {
34865            self.write_space();
34866            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
34867            self.generate_expression(props)?;
34868        }
34869        Ok(())
34870    }
34871
34872    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
34873        // SHA2(this, length)
34874        self.write_keyword("SHA2");
34875        self.write("(");
34876        self.generate_expression(&e.this)?;
34877        if let Some(length) = e.length {
34878            self.write(", ");
34879            self.write(&length.to_string());
34880        }
34881        self.write(")");
34882        Ok(())
34883    }
34884
34885    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
34886        // SHA2_DIGEST(this, length)
34887        self.write_keyword("SHA2_DIGEST");
34888        self.write("(");
34889        self.generate_expression(&e.this)?;
34890        if let Some(length) = e.length {
34891            self.write(", ");
34892            self.write(&length.to_string());
34893        }
34894        self.write(")");
34895        Ok(())
34896    }
34897
34898    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
34899        let name = if matches!(
34900            self.config.dialect,
34901            Some(crate::dialects::DialectType::Spark)
34902                | Some(crate::dialects::DialectType::Databricks)
34903        ) {
34904            "TRY_ADD"
34905        } else {
34906            "SAFE_ADD"
34907        };
34908        self.write_keyword(name);
34909        self.write("(");
34910        self.generate_expression(&e.this)?;
34911        self.write(", ");
34912        self.generate_expression(&e.expression)?;
34913        self.write(")");
34914        Ok(())
34915    }
34916
34917    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
34918        // SAFE_DIVIDE(this, expression)
34919        self.write_keyword("SAFE_DIVIDE");
34920        self.write("(");
34921        self.generate_expression(&e.this)?;
34922        self.write(", ");
34923        self.generate_expression(&e.expression)?;
34924        self.write(")");
34925        Ok(())
34926    }
34927
34928    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
34929        let name = if matches!(
34930            self.config.dialect,
34931            Some(crate::dialects::DialectType::Spark)
34932                | Some(crate::dialects::DialectType::Databricks)
34933        ) {
34934            "TRY_MULTIPLY"
34935        } else {
34936            "SAFE_MULTIPLY"
34937        };
34938        self.write_keyword(name);
34939        self.write("(");
34940        self.generate_expression(&e.this)?;
34941        self.write(", ");
34942        self.generate_expression(&e.expression)?;
34943        self.write(")");
34944        Ok(())
34945    }
34946
34947    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
34948        let name = if matches!(
34949            self.config.dialect,
34950            Some(crate::dialects::DialectType::Spark)
34951                | Some(crate::dialects::DialectType::Databricks)
34952        ) {
34953            "TRY_SUBTRACT"
34954        } else {
34955            "SAFE_SUBTRACT"
34956        };
34957        self.write_keyword(name);
34958        self.write("(");
34959        self.generate_expression(&e.this)?;
34960        self.write(", ");
34961        self.generate_expression(&e.expression)?;
34962        self.write(")");
34963        Ok(())
34964    }
34965
34966    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
34967    /// METHOD (size UNIT) [REPEATABLE (seed)]
34968    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
34969        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
34970        if matches!(sample.method, SampleMethod::Bucket) {
34971            self.write(" (");
34972            self.write_keyword("BUCKET");
34973            self.write_space();
34974            if let Some(ref num) = sample.bucket_numerator {
34975                self.generate_expression(num)?;
34976            }
34977            self.write_space();
34978            self.write_keyword("OUT OF");
34979            self.write_space();
34980            if let Some(ref denom) = sample.bucket_denominator {
34981                self.generate_expression(denom)?;
34982            }
34983            if let Some(ref field) = sample.bucket_field {
34984                self.write_space();
34985                self.write_keyword("ON");
34986                self.write_space();
34987                self.generate_expression(field)?;
34988            }
34989            self.write(")");
34990            return Ok(());
34991        }
34992
34993        // Output method name if explicitly specified, or for dialects that always require it
34994        let is_snowflake = matches!(
34995            self.config.dialect,
34996            Some(crate::dialects::DialectType::Snowflake)
34997        );
34998        let is_postgres = matches!(
34999            self.config.dialect,
35000            Some(crate::dialects::DialectType::PostgreSQL)
35001                | Some(crate::dialects::DialectType::Redshift)
35002        );
35003        // Databricks and Spark don't output method names
35004        let is_databricks = matches!(
35005            self.config.dialect,
35006            Some(crate::dialects::DialectType::Databricks)
35007        );
35008        let is_spark = matches!(
35009            self.config.dialect,
35010            Some(crate::dialects::DialectType::Spark)
35011        );
35012        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
35013        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
35014        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
35015        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
35016            self.write_space();
35017            if !sample.explicit_method && (is_snowflake || force_method) {
35018                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
35019                self.write_keyword("BERNOULLI");
35020            } else {
35021                match sample.method {
35022                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
35023                    SampleMethod::System => self.write_keyword("SYSTEM"),
35024                    SampleMethod::Block => self.write_keyword("BLOCK"),
35025                    SampleMethod::Row => self.write_keyword("ROW"),
35026                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
35027                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
35028                    SampleMethod::Bucket => {} // handled above
35029                }
35030            }
35031        }
35032
35033        // Output size, with or without parentheses depending on dialect
35034        let emit_size_no_parens = !self.config.tablesample_requires_parens;
35035        if emit_size_no_parens {
35036            self.write_space();
35037            match &sample.size {
35038                Expression::Tuple(tuple) => {
35039                    for (i, expr) in tuple.expressions.iter().enumerate() {
35040                        if i > 0 {
35041                            self.write(", ");
35042                        }
35043                        self.generate_expression(expr)?;
35044                    }
35045                }
35046                expr => self.generate_expression(expr)?,
35047            }
35048        } else {
35049            self.write(" (");
35050            self.generate_expression(&sample.size)?;
35051        }
35052
35053        // Determine unit
35054        let is_rows_method = matches!(
35055            sample.method,
35056            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
35057        );
35058        let is_percent = matches!(
35059            sample.method,
35060            SampleMethod::Percent
35061                | SampleMethod::System
35062                | SampleMethod::Bernoulli
35063                | SampleMethod::Block
35064        );
35065
35066        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
35067        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
35068        // For Databricks and Spark, always output PERCENT for percentage samples.
35069        let is_presto = matches!(
35070            self.config.dialect,
35071            Some(crate::dialects::DialectType::Presto)
35072                | Some(crate::dialects::DialectType::Trino)
35073                | Some(crate::dialects::DialectType::Athena)
35074        );
35075        let should_output_unit = if is_databricks || is_spark {
35076            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
35077            is_percent || is_rows_method || sample.unit_after_size
35078        } else if is_snowflake || is_postgres || is_presto {
35079            sample.unit_after_size
35080        } else {
35081            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
35082        };
35083
35084        if should_output_unit {
35085            self.write_space();
35086            if sample.is_percent {
35087                self.write_keyword("PERCENT");
35088            } else if is_rows_method && !sample.unit_after_size {
35089                self.write_keyword("ROWS");
35090            } else if sample.unit_after_size {
35091                match sample.method {
35092                    SampleMethod::Percent
35093                    | SampleMethod::System
35094                    | SampleMethod::Bernoulli
35095                    | SampleMethod::Block => {
35096                        self.write_keyword("PERCENT");
35097                    }
35098                    SampleMethod::Row | SampleMethod::Reservoir => {
35099                        self.write_keyword("ROWS");
35100                    }
35101                    _ => self.write_keyword("ROWS"),
35102                }
35103            } else {
35104                self.write_keyword("PERCENT");
35105            }
35106        }
35107
35108        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
35109            if let Some(ref offset) = sample.offset {
35110                self.write_space();
35111                self.write_keyword("OFFSET");
35112                self.write_space();
35113                self.generate_expression(offset)?;
35114            }
35115        }
35116        if !emit_size_no_parens {
35117            self.write(")");
35118        }
35119
35120        Ok(())
35121    }
35122
35123    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
35124        // SAMPLE this (ClickHouse uses SAMPLE BY)
35125        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
35126            self.write_keyword("SAMPLE BY");
35127        } else {
35128            self.write_keyword("SAMPLE");
35129        }
35130        self.write_space();
35131        self.generate_expression(&e.this)?;
35132        Ok(())
35133    }
35134
35135    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
35136        // this (expressions...)
35137        if let Some(this) = &e.this {
35138            self.generate_expression(this)?;
35139        }
35140        if !e.expressions.is_empty() {
35141            // Add space before column list if there's a preceding expression
35142            if e.this.is_some() {
35143                self.write_space();
35144            }
35145            self.write("(");
35146            for (i, expr) in e.expressions.iter().enumerate() {
35147                if i > 0 {
35148                    self.write(", ");
35149                }
35150                self.generate_expression(expr)?;
35151            }
35152            self.write(")");
35153        }
35154        Ok(())
35155    }
35156
35157    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
35158        // COMMENT this
35159        self.write_keyword("COMMENT");
35160        self.write_space();
35161        self.generate_expression(&e.this)?;
35162        Ok(())
35163    }
35164
35165    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
35166        // [this::]expression
35167        if let Some(this) = &e.this {
35168            self.generate_expression(this)?;
35169            self.write("::");
35170        }
35171        self.generate_expression(&e.expression)?;
35172        Ok(())
35173    }
35174
35175    fn generate_search(&mut self, e: &Search) -> Result<()> {
35176        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
35177        self.write_keyword("SEARCH");
35178        self.write("(");
35179        self.generate_expression(&e.this)?;
35180        self.write(", ");
35181        self.generate_expression(&e.expression)?;
35182        if let Some(json_scope) = &e.json_scope {
35183            self.write(", ");
35184            self.generate_expression(json_scope)?;
35185        }
35186        if let Some(analyzer) = &e.analyzer {
35187            self.write(", ");
35188            self.generate_expression(analyzer)?;
35189        }
35190        if let Some(analyzer_options) = &e.analyzer_options {
35191            self.write(", ");
35192            self.generate_expression(analyzer_options)?;
35193        }
35194        if let Some(search_mode) = &e.search_mode {
35195            self.write(", ");
35196            self.generate_expression(search_mode)?;
35197        }
35198        self.write(")");
35199        Ok(())
35200    }
35201
35202    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
35203        // SEARCH_IP(this, expression)
35204        self.write_keyword("SEARCH_IP");
35205        self.write("(");
35206        self.generate_expression(&e.this)?;
35207        self.write(", ");
35208        self.generate_expression(&e.expression)?;
35209        self.write(")");
35210        Ok(())
35211    }
35212
35213    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
35214        // SECURITY this
35215        self.write_keyword("SECURITY");
35216        self.write_space();
35217        self.generate_expression(&e.this)?;
35218        Ok(())
35219    }
35220
35221    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
35222        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
35223        self.write("SEMANTIC_VIEW(");
35224
35225        if self.config.pretty {
35226            // Pretty print: each clause on its own line
35227            self.write_newline();
35228            self.indent_level += 1;
35229            self.write_indent();
35230            self.generate_expression(&e.this)?;
35231
35232            if let Some(metrics) = &e.metrics {
35233                self.write_newline();
35234                self.write_indent();
35235                self.write_keyword("METRICS");
35236                self.write_space();
35237                self.generate_semantic_view_tuple(metrics)?;
35238            }
35239            if let Some(dimensions) = &e.dimensions {
35240                self.write_newline();
35241                self.write_indent();
35242                self.write_keyword("DIMENSIONS");
35243                self.write_space();
35244                self.generate_semantic_view_tuple(dimensions)?;
35245            }
35246            if let Some(facts) = &e.facts {
35247                self.write_newline();
35248                self.write_indent();
35249                self.write_keyword("FACTS");
35250                self.write_space();
35251                self.generate_semantic_view_tuple(facts)?;
35252            }
35253            if let Some(where_) = &e.where_ {
35254                self.write_newline();
35255                self.write_indent();
35256                self.write_keyword("WHERE");
35257                self.write_space();
35258                self.generate_expression(where_)?;
35259            }
35260            self.write_newline();
35261            self.indent_level -= 1;
35262            self.write_indent();
35263        } else {
35264            // Compact: all on one line
35265            self.generate_expression(&e.this)?;
35266            if let Some(metrics) = &e.metrics {
35267                self.write_space();
35268                self.write_keyword("METRICS");
35269                self.write_space();
35270                self.generate_semantic_view_tuple(metrics)?;
35271            }
35272            if let Some(dimensions) = &e.dimensions {
35273                self.write_space();
35274                self.write_keyword("DIMENSIONS");
35275                self.write_space();
35276                self.generate_semantic_view_tuple(dimensions)?;
35277            }
35278            if let Some(facts) = &e.facts {
35279                self.write_space();
35280                self.write_keyword("FACTS");
35281                self.write_space();
35282                self.generate_semantic_view_tuple(facts)?;
35283            }
35284            if let Some(where_) = &e.where_ {
35285                self.write_space();
35286                self.write_keyword("WHERE");
35287                self.write_space();
35288                self.generate_expression(where_)?;
35289            }
35290        }
35291        self.write(")");
35292        Ok(())
35293    }
35294
35295    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
35296    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
35297        if let Expression::Tuple(t) = expr {
35298            for (i, e) in t.expressions.iter().enumerate() {
35299                if i > 0 {
35300                    self.write(", ");
35301                }
35302                self.generate_expression(e)?;
35303            }
35304        } else {
35305            self.generate_expression(expr)?;
35306        }
35307        Ok(())
35308    }
35309
35310    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
35311        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
35312        if let Some(start) = &e.start {
35313            self.write_keyword("START WITH");
35314            self.write_space();
35315            self.generate_expression(start)?;
35316        }
35317        if let Some(increment) = &e.increment {
35318            self.write_space();
35319            self.write_keyword("INCREMENT BY");
35320            self.write_space();
35321            self.generate_expression(increment)?;
35322        }
35323        if let Some(minvalue) = &e.minvalue {
35324            self.write_space();
35325            self.write_keyword("MINVALUE");
35326            self.write_space();
35327            self.generate_expression(minvalue)?;
35328        }
35329        if let Some(maxvalue) = &e.maxvalue {
35330            self.write_space();
35331            self.write_keyword("MAXVALUE");
35332            self.write_space();
35333            self.generate_expression(maxvalue)?;
35334        }
35335        if let Some(cache) = &e.cache {
35336            self.write_space();
35337            self.write_keyword("CACHE");
35338            self.write_space();
35339            self.generate_expression(cache)?;
35340        }
35341        if let Some(owned) = &e.owned {
35342            self.write_space();
35343            self.write_keyword("OWNED BY");
35344            self.write_space();
35345            self.generate_expression(owned)?;
35346        }
35347        for opt in &e.options {
35348            self.write_space();
35349            self.generate_expression(opt)?;
35350        }
35351        Ok(())
35352    }
35353
35354    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
35355        // [WITH] SERDEPROPERTIES (expressions)
35356        if e.with_.is_some() {
35357            self.write_keyword("WITH");
35358            self.write_space();
35359        }
35360        self.write_keyword("SERDEPROPERTIES");
35361        self.write(" (");
35362        for (i, expr) in e.expressions.iter().enumerate() {
35363            if i > 0 {
35364                self.write(", ");
35365            }
35366            // Generate key=value without spaces around =
35367            match expr {
35368                Expression::Eq(eq) => {
35369                    self.generate_expression(&eq.left)?;
35370                    self.write("=");
35371                    self.generate_expression(&eq.right)?;
35372                }
35373                _ => self.generate_expression(expr)?,
35374            }
35375        }
35376        self.write(")");
35377        Ok(())
35378    }
35379
35380    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
35381        // @@[kind.]this
35382        self.write("@@");
35383        if let Some(kind) = &e.kind {
35384            self.write(kind);
35385            self.write(".");
35386        }
35387        self.generate_expression(&e.this)?;
35388        Ok(())
35389    }
35390
35391    fn generate_set(&mut self, e: &Set) -> Result<()> {
35392        // SET/UNSET [TAG] expressions
35393        if e.unset.is_some() {
35394            self.write_keyword("UNSET");
35395        } else {
35396            self.write_keyword("SET");
35397        }
35398        if e.tag.is_some() {
35399            self.write_space();
35400            self.write_keyword("TAG");
35401        }
35402        if !e.expressions.is_empty() {
35403            self.write_space();
35404            for (i, expr) in e.expressions.iter().enumerate() {
35405                if i > 0 {
35406                    self.write(", ");
35407                }
35408                self.generate_expression(expr)?;
35409            }
35410        }
35411        Ok(())
35412    }
35413
35414    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
35415        // SET this or SETCONFIG this
35416        self.write_keyword("SET");
35417        self.write_space();
35418        self.generate_expression(&e.this)?;
35419        Ok(())
35420    }
35421
35422    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
35423        // [kind] name = value
35424        if let Some(kind) = &e.kind {
35425            self.write_keyword(kind);
35426            self.write_space();
35427        }
35428        self.generate_expression(&e.name)?;
35429        self.write(" = ");
35430        self.generate_expression(&e.value)?;
35431        Ok(())
35432    }
35433
35434    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
35435        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
35436        if let Some(with_) = &e.with_ {
35437            self.generate_expression(with_)?;
35438            self.write_space();
35439        }
35440        self.generate_expression(&e.this)?;
35441        self.write_space();
35442        // kind should be UNION, INTERSECT, EXCEPT, etc.
35443        if let Some(kind) = &e.kind {
35444            self.write_keyword(kind);
35445        }
35446        if e.distinct {
35447            self.write_space();
35448            self.write_keyword("DISTINCT");
35449        } else {
35450            self.write_space();
35451            self.write_keyword("ALL");
35452        }
35453        if e.by_name.is_some() {
35454            self.write_space();
35455            self.write_keyword("BY NAME");
35456        }
35457        self.write_space();
35458        self.generate_expression(&e.expression)?;
35459        Ok(())
35460    }
35461
35462    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
35463        // SET or MULTISET
35464        if e.multi.is_some() {
35465            self.write_keyword("MULTISET");
35466        } else {
35467            self.write_keyword("SET");
35468        }
35469        Ok(())
35470    }
35471
35472    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
35473        // SETTINGS expressions
35474        self.write_keyword("SETTINGS");
35475        if self.config.pretty && e.expressions.len() > 1 {
35476            // Pretty print: each setting on its own line, indented
35477            self.indent_level += 1;
35478            for (i, expr) in e.expressions.iter().enumerate() {
35479                if i > 0 {
35480                    self.write(",");
35481                }
35482                self.write_newline();
35483                self.write_indent();
35484                self.generate_expression(expr)?;
35485            }
35486            self.indent_level -= 1;
35487        } else {
35488            self.write_space();
35489            for (i, expr) in e.expressions.iter().enumerate() {
35490                if i > 0 {
35491                    self.write(", ");
35492                }
35493                self.generate_expression(expr)?;
35494            }
35495        }
35496        Ok(())
35497    }
35498
35499    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
35500        // SHARING = this
35501        self.write_keyword("SHARING");
35502        if let Some(this) = &e.this {
35503            self.write(" = ");
35504            self.generate_expression(this)?;
35505        }
35506        Ok(())
35507    }
35508
35509    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
35510        // Python array slicing: begin:end:step
35511        if let Some(begin) = &e.this {
35512            self.generate_expression(begin)?;
35513        }
35514        self.write(":");
35515        if let Some(end) = &e.expression {
35516            self.generate_expression(end)?;
35517        }
35518        if let Some(step) = &e.step {
35519            self.write(":");
35520            self.generate_expression(step)?;
35521        }
35522        Ok(())
35523    }
35524
35525    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
35526        // SORT_ARRAY(this, asc)
35527        self.write_keyword("SORT_ARRAY");
35528        self.write("(");
35529        self.generate_expression(&e.this)?;
35530        if let Some(asc) = &e.asc {
35531            self.write(", ");
35532            self.generate_expression(asc)?;
35533        }
35534        self.write(")");
35535        Ok(())
35536    }
35537
35538    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
35539        // SORT BY expressions
35540        self.write_keyword("SORT BY");
35541        self.write_space();
35542        for (i, expr) in e.expressions.iter().enumerate() {
35543            if i > 0 {
35544                self.write(", ");
35545            }
35546            self.generate_ordered(expr)?;
35547        }
35548        Ok(())
35549    }
35550
35551    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
35552        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
35553        if e.compound.is_some() {
35554            self.write_keyword("COMPOUND");
35555            self.write_space();
35556        }
35557        self.write_keyword("SORTKEY");
35558        self.write("(");
35559        // If this is a Tuple, unwrap its contents to avoid double parentheses
35560        if let Expression::Tuple(t) = e.this.as_ref() {
35561            for (i, expr) in t.expressions.iter().enumerate() {
35562                if i > 0 {
35563                    self.write(", ");
35564                }
35565                self.generate_expression(expr)?;
35566            }
35567        } else {
35568            self.generate_expression(&e.this)?;
35569        }
35570        self.write(")");
35571        Ok(())
35572    }
35573
35574    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
35575        // SPLIT_PART(this, delimiter, part_index)
35576        self.write_keyword("SPLIT_PART");
35577        self.write("(");
35578        self.generate_expression(&e.this)?;
35579        if let Some(delimiter) = &e.delimiter {
35580            self.write(", ");
35581            self.generate_expression(delimiter)?;
35582        }
35583        if let Some(part_index) = &e.part_index {
35584            self.write(", ");
35585            self.generate_expression(part_index)?;
35586        }
35587        self.write(")");
35588        Ok(())
35589    }
35590
35591    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
35592        // READS SQL DATA or MODIFIES SQL DATA, etc.
35593        self.generate_expression(&e.this)?;
35594        Ok(())
35595    }
35596
35597    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
35598        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
35599        self.write_keyword("SQL SECURITY");
35600        self.write_space();
35601        self.generate_expression(&e.this)?;
35602        Ok(())
35603    }
35604
35605    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
35606        // ST_DISTANCE(this, expression, [use_spheroid])
35607        self.write_keyword("ST_DISTANCE");
35608        self.write("(");
35609        self.generate_expression(&e.this)?;
35610        self.write(", ");
35611        self.generate_expression(&e.expression)?;
35612        if let Some(use_spheroid) = &e.use_spheroid {
35613            self.write(", ");
35614            self.generate_expression(use_spheroid)?;
35615        }
35616        self.write(")");
35617        Ok(())
35618    }
35619
35620    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
35621        // ST_POINT(this, expression)
35622        self.write_keyword("ST_POINT");
35623        self.write("(");
35624        self.generate_expression(&e.this)?;
35625        self.write(", ");
35626        self.generate_expression(&e.expression)?;
35627        self.write(")");
35628        Ok(())
35629    }
35630
35631    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
35632        // IMMUTABLE, STABLE, VOLATILE
35633        self.generate_expression(&e.this)?;
35634        Ok(())
35635    }
35636
35637    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
35638        // STANDARD_HASH(this, [expression])
35639        self.write_keyword("STANDARD_HASH");
35640        self.write("(");
35641        self.generate_expression(&e.this)?;
35642        if let Some(expression) = &e.expression {
35643            self.write(", ");
35644            self.generate_expression(expression)?;
35645        }
35646        self.write(")");
35647        Ok(())
35648    }
35649
35650    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
35651        // STORED BY this
35652        self.write_keyword("STORED BY");
35653        self.write_space();
35654        self.generate_expression(&e.this)?;
35655        Ok(())
35656    }
35657
35658    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
35659        // STRPOS(this, substr) or STRPOS(this, substr, position)
35660        // Different dialects have different function names
35661        use crate::dialects::DialectType;
35662        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
35663            // Snowflake: CHARINDEX(substr, str[, position])
35664            self.write_keyword("CHARINDEX");
35665            self.write("(");
35666            if let Some(substr) = &e.substr {
35667                self.generate_expression(substr)?;
35668                self.write(", ");
35669            }
35670            self.generate_expression(&e.this)?;
35671            if let Some(position) = &e.position {
35672                self.write(", ");
35673                self.generate_expression(position)?;
35674            }
35675            self.write(")");
35676        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
35677            self.write_keyword("POSITION");
35678            self.write("(");
35679            self.generate_expression(&e.this)?;
35680            if let Some(substr) = &e.substr {
35681                self.write(", ");
35682                self.generate_expression(substr)?;
35683            }
35684            if let Some(position) = &e.position {
35685                self.write(", ");
35686                self.generate_expression(position)?;
35687            }
35688            if let Some(occurrence) = &e.occurrence {
35689                self.write(", ");
35690                self.generate_expression(occurrence)?;
35691            }
35692            self.write(")");
35693        } else if matches!(
35694            self.config.dialect,
35695            Some(DialectType::SQLite)
35696                | Some(DialectType::Oracle)
35697                | Some(DialectType::BigQuery)
35698                | Some(DialectType::Teradata)
35699        ) {
35700            self.write_keyword("INSTR");
35701            self.write("(");
35702            self.generate_expression(&e.this)?;
35703            if let Some(substr) = &e.substr {
35704                self.write(", ");
35705                self.generate_expression(substr)?;
35706            }
35707            if let Some(position) = &e.position {
35708                self.write(", ");
35709                self.generate_expression(position)?;
35710            } else if e.occurrence.is_some() {
35711                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
35712                // Default start position is 1
35713                self.write(", 1");
35714            }
35715            if let Some(occurrence) = &e.occurrence {
35716                self.write(", ");
35717                self.generate_expression(occurrence)?;
35718            }
35719            self.write(")");
35720        } else if matches!(
35721            self.config.dialect,
35722            Some(DialectType::MySQL)
35723                | Some(DialectType::SingleStore)
35724                | Some(DialectType::Doris)
35725                | Some(DialectType::StarRocks)
35726                | Some(DialectType::Hive)
35727                | Some(DialectType::Spark)
35728                | Some(DialectType::Databricks)
35729        ) {
35730            // LOCATE(substr, str[, position]) - substr first
35731            self.write_keyword("LOCATE");
35732            self.write("(");
35733            if let Some(substr) = &e.substr {
35734                self.generate_expression(substr)?;
35735                self.write(", ");
35736            }
35737            self.generate_expression(&e.this)?;
35738            if let Some(position) = &e.position {
35739                self.write(", ");
35740                self.generate_expression(position)?;
35741            }
35742            self.write(")");
35743        } else if matches!(
35744            self.config.dialect,
35745            Some(DialectType::TSQL) | Some(DialectType::Fabric)
35746        ) {
35747            // CHARINDEX(substr, str[, position])
35748            self.write_keyword("CHARINDEX");
35749            self.write("(");
35750            if let Some(substr) = &e.substr {
35751                self.generate_expression(substr)?;
35752                self.write(", ");
35753            }
35754            self.generate_expression(&e.this)?;
35755            if let Some(position) = &e.position {
35756                self.write(", ");
35757                self.generate_expression(position)?;
35758            }
35759            self.write(")");
35760        } else if matches!(
35761            self.config.dialect,
35762            Some(DialectType::PostgreSQL)
35763                | Some(DialectType::Materialize)
35764                | Some(DialectType::RisingWave)
35765                | Some(DialectType::Redshift)
35766        ) {
35767            // POSITION(substr IN str) syntax
35768            self.write_keyword("POSITION");
35769            self.write("(");
35770            if let Some(substr) = &e.substr {
35771                self.generate_expression(substr)?;
35772                self.write(" IN ");
35773            }
35774            self.generate_expression(&e.this)?;
35775            self.write(")");
35776        } else {
35777            self.write_keyword("STRPOS");
35778            self.write("(");
35779            self.generate_expression(&e.this)?;
35780            if let Some(substr) = &e.substr {
35781                self.write(", ");
35782                self.generate_expression(substr)?;
35783            }
35784            if let Some(position) = &e.position {
35785                self.write(", ");
35786                self.generate_expression(position)?;
35787            }
35788            if let Some(occurrence) = &e.occurrence {
35789                self.write(", ");
35790                self.generate_expression(occurrence)?;
35791            }
35792            self.write(")");
35793        }
35794        Ok(())
35795    }
35796
35797    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
35798        match self.config.dialect {
35799            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
35800                // TO_DATE(this, java_format)
35801                self.write_keyword("TO_DATE");
35802                self.write("(");
35803                self.generate_expression(&e.this)?;
35804                if let Some(format) = &e.format {
35805                    self.write(", '");
35806                    self.write(&Self::strftime_to_java_format(format));
35807                    self.write("'");
35808                }
35809                self.write(")");
35810            }
35811            Some(DialectType::DuckDB) => {
35812                // CAST(STRPTIME(this, format) AS DATE)
35813                self.write_keyword("CAST");
35814                self.write("(");
35815                self.write_keyword("STRPTIME");
35816                self.write("(");
35817                self.generate_expression(&e.this)?;
35818                if let Some(format) = &e.format {
35819                    self.write(", '");
35820                    self.write(format);
35821                    self.write("'");
35822                }
35823                self.write(")");
35824                self.write_keyword(" AS ");
35825                self.write_keyword("DATE");
35826                self.write(")");
35827            }
35828            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
35829                // TO_DATE(this, pg_format)
35830                self.write_keyword("TO_DATE");
35831                self.write("(");
35832                self.generate_expression(&e.this)?;
35833                if let Some(format) = &e.format {
35834                    self.write(", '");
35835                    self.write(&Self::strftime_to_postgres_format(format));
35836                    self.write("'");
35837                }
35838                self.write(")");
35839            }
35840            Some(DialectType::BigQuery) => {
35841                // PARSE_DATE(format, this) - note: format comes first for BigQuery
35842                self.write_keyword("PARSE_DATE");
35843                self.write("(");
35844                if let Some(format) = &e.format {
35845                    self.write("'");
35846                    self.write(format);
35847                    self.write("'");
35848                    self.write(", ");
35849                }
35850                self.generate_expression(&e.this)?;
35851                self.write(")");
35852            }
35853            Some(DialectType::Teradata) => {
35854                // CAST(this AS DATE FORMAT 'teradata_fmt')
35855                self.write_keyword("CAST");
35856                self.write("(");
35857                self.generate_expression(&e.this)?;
35858                self.write_keyword(" AS ");
35859                self.write_keyword("DATE");
35860                if let Some(format) = &e.format {
35861                    self.write_keyword(" FORMAT ");
35862                    self.write("'");
35863                    self.write(&Self::strftime_to_teradata_format(format));
35864                    self.write("'");
35865                }
35866                self.write(")");
35867            }
35868            _ => {
35869                // STR_TO_DATE(this, format) - MySQL default
35870                self.write_keyword("STR_TO_DATE");
35871                self.write("(");
35872                self.generate_expression(&e.this)?;
35873                if let Some(format) = &e.format {
35874                    self.write(", '");
35875                    self.write(format);
35876                    self.write("'");
35877                }
35878                self.write(")");
35879            }
35880        }
35881        Ok(())
35882    }
35883
35884    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
35885    fn strftime_to_teradata_format(fmt: &str) -> String {
35886        let mut result = String::with_capacity(fmt.len() * 2);
35887        let bytes = fmt.as_bytes();
35888        let len = bytes.len();
35889        let mut i = 0;
35890        while i < len {
35891            if bytes[i] == b'%' && i + 1 < len {
35892                let replacement = match bytes[i + 1] {
35893                    b'Y' => "YYYY",
35894                    b'y' => "YY",
35895                    b'm' => "MM",
35896                    b'B' => "MMMM",
35897                    b'b' => "MMM",
35898                    b'd' => "DD",
35899                    b'j' => "DDD",
35900                    b'H' => "HH",
35901                    b'M' => "MI",
35902                    b'S' => "SS",
35903                    b'f' => "SSSSSS",
35904                    b'A' => "EEEE",
35905                    b'a' => "EEE",
35906                    _ => {
35907                        result.push('%');
35908                        i += 1;
35909                        continue;
35910                    }
35911                };
35912                result.push_str(replacement);
35913                i += 2;
35914            } else {
35915                result.push(bytes[i] as char);
35916                i += 1;
35917            }
35918        }
35919        result
35920    }
35921
35922    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
35923    /// Public static version for use by other modules
35924    pub fn strftime_to_java_format_static(fmt: &str) -> String {
35925        Self::strftime_to_java_format(fmt)
35926    }
35927
35928    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
35929    fn strftime_to_java_format(fmt: &str) -> String {
35930        let mut result = String::with_capacity(fmt.len() * 2);
35931        let bytes = fmt.as_bytes();
35932        let len = bytes.len();
35933        let mut i = 0;
35934        while i < len {
35935            if bytes[i] == b'%' && i + 1 < len {
35936                // Check for non-padded variants (%-X)
35937                if bytes[i + 1] == b'-' && i + 2 < len {
35938                    let replacement = match bytes[i + 2] {
35939                        b'd' => "d",
35940                        b'm' => "M",
35941                        b'H' => "H",
35942                        b'M' => "m",
35943                        b'S' => "s",
35944                        _ => {
35945                            result.push('%');
35946                            i += 1;
35947                            continue;
35948                        }
35949                    };
35950                    result.push_str(replacement);
35951                    i += 3;
35952                } else {
35953                    let replacement = match bytes[i + 1] {
35954                        b'Y' => "yyyy",
35955                        b'y' => "yy",
35956                        b'm' => "MM",
35957                        b'B' => "MMMM",
35958                        b'b' => "MMM",
35959                        b'd' => "dd",
35960                        b'j' => "DDD",
35961                        b'H' => "HH",
35962                        b'M' => "mm",
35963                        b'S' => "ss",
35964                        b'f' => "SSSSSS",
35965                        b'A' => "EEEE",
35966                        b'a' => "EEE",
35967                        _ => {
35968                            result.push('%');
35969                            i += 1;
35970                            continue;
35971                        }
35972                    };
35973                    result.push_str(replacement);
35974                    i += 2;
35975                }
35976            } else {
35977                result.push(bytes[i] as char);
35978                i += 1;
35979            }
35980        }
35981        result
35982    }
35983
35984    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
35985    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
35986    fn strftime_to_tsql_format(fmt: &str) -> String {
35987        let mut result = String::with_capacity(fmt.len() * 2);
35988        let bytes = fmt.as_bytes();
35989        let len = bytes.len();
35990        let mut i = 0;
35991        while i < len {
35992            if bytes[i] == b'%' && i + 1 < len {
35993                // Check for non-padded variants (%-X)
35994                if bytes[i + 1] == b'-' && i + 2 < len {
35995                    let replacement = match bytes[i + 2] {
35996                        b'd' => "d",
35997                        b'm' => "M",
35998                        b'H' => "H",
35999                        b'M' => "m",
36000                        b'S' => "s",
36001                        _ => {
36002                            result.push('%');
36003                            i += 1;
36004                            continue;
36005                        }
36006                    };
36007                    result.push_str(replacement);
36008                    i += 3;
36009                } else {
36010                    let replacement = match bytes[i + 1] {
36011                        b'Y' => "yyyy",
36012                        b'y' => "yy",
36013                        b'm' => "MM",
36014                        b'B' => "MMMM",
36015                        b'b' => "MMM",
36016                        b'd' => "dd",
36017                        b'j' => "DDD",
36018                        b'H' => "HH",
36019                        b'M' => "mm",
36020                        b'S' => "ss",
36021                        b'f' => "ffffff",
36022                        b'A' => "dddd",
36023                        b'a' => "ddd",
36024                        _ => {
36025                            result.push('%');
36026                            i += 1;
36027                            continue;
36028                        }
36029                    };
36030                    result.push_str(replacement);
36031                    i += 2;
36032                }
36033            } else {
36034                result.push(bytes[i] as char);
36035                i += 1;
36036            }
36037        }
36038        result
36039    }
36040
36041    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
36042    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
36043    fn decompose_json_path(path: &str) -> Vec<String> {
36044        let mut parts = Vec::new();
36045        // Strip leading $ and optional .
36046        let path = if path.starts_with("$.") {
36047            &path[2..]
36048        } else if path.starts_with('$') {
36049            &path[1..]
36050        } else {
36051            path
36052        };
36053        if path.is_empty() {
36054            return parts;
36055        }
36056        let mut current = String::new();
36057        let chars: Vec<char> = path.chars().collect();
36058        let mut i = 0;
36059        while i < chars.len() {
36060            match chars[i] {
36061                '.' => {
36062                    if !current.is_empty() {
36063                        parts.push(current.clone());
36064                        current.clear();
36065                    }
36066                    i += 1;
36067                }
36068                '[' => {
36069                    if !current.is_empty() {
36070                        parts.push(current.clone());
36071                        current.clear();
36072                    }
36073                    i += 1;
36074                    // Read the content inside brackets
36075                    let mut bracket_content = String::new();
36076                    while i < chars.len() && chars[i] != ']' {
36077                        // Skip quotes inside brackets
36078                        if chars[i] == '"' || chars[i] == '\'' {
36079                            let quote = chars[i];
36080                            i += 1;
36081                            while i < chars.len() && chars[i] != quote {
36082                                bracket_content.push(chars[i]);
36083                                i += 1;
36084                            }
36085                            if i < chars.len() {
36086                                i += 1;
36087                            } // skip closing quote
36088                        } else {
36089                            bracket_content.push(chars[i]);
36090                            i += 1;
36091                        }
36092                    }
36093                    if i < chars.len() {
36094                        i += 1;
36095                    } // skip ]
36096                      // Skip wildcard [*] - don't add as a part
36097                    if bracket_content != "*" {
36098                        parts.push(bracket_content);
36099                    }
36100                }
36101                _ => {
36102                    current.push(chars[i]);
36103                    i += 1;
36104                }
36105            }
36106        }
36107        if !current.is_empty() {
36108            parts.push(current);
36109        }
36110        parts
36111    }
36112
36113    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
36114    fn strftime_to_postgres_format(fmt: &str) -> String {
36115        let mut result = String::with_capacity(fmt.len() * 2);
36116        let bytes = fmt.as_bytes();
36117        let len = bytes.len();
36118        let mut i = 0;
36119        while i < len {
36120            if bytes[i] == b'%' && i + 1 < len {
36121                // Check for non-padded variants (%-X)
36122                if bytes[i + 1] == b'-' && i + 2 < len {
36123                    let replacement = match bytes[i + 2] {
36124                        b'd' => "FMDD",
36125                        b'm' => "FMMM",
36126                        b'H' => "FMHH24",
36127                        b'M' => "FMMI",
36128                        b'S' => "FMSS",
36129                        _ => {
36130                            result.push('%');
36131                            i += 1;
36132                            continue;
36133                        }
36134                    };
36135                    result.push_str(replacement);
36136                    i += 3;
36137                } else {
36138                    let replacement = match bytes[i + 1] {
36139                        b'Y' => "YYYY",
36140                        b'y' => "YY",
36141                        b'm' => "MM",
36142                        b'B' => "Month",
36143                        b'b' => "Mon",
36144                        b'd' => "DD",
36145                        b'j' => "DDD",
36146                        b'H' => "HH24",
36147                        b'M' => "MI",
36148                        b'S' => "SS",
36149                        b'f' => "US",
36150                        b'A' => "Day",
36151                        b'a' => "Dy",
36152                        _ => {
36153                            result.push('%');
36154                            i += 1;
36155                            continue;
36156                        }
36157                    };
36158                    result.push_str(replacement);
36159                    i += 2;
36160                }
36161            } else {
36162                result.push(bytes[i] as char);
36163                i += 1;
36164            }
36165        }
36166        result
36167    }
36168
36169    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
36170    fn strftime_to_snowflake_format(fmt: &str) -> String {
36171        let mut result = String::with_capacity(fmt.len() * 2);
36172        let bytes = fmt.as_bytes();
36173        let len = bytes.len();
36174        let mut i = 0;
36175        while i < len {
36176            if bytes[i] == b'%' && i + 1 < len {
36177                // Check for non-padded variants (%-X)
36178                if bytes[i + 1] == b'-' && i + 2 < len {
36179                    let replacement = match bytes[i + 2] {
36180                        b'd' => "dd",
36181                        b'm' => "mm",
36182                        _ => {
36183                            result.push('%');
36184                            i += 1;
36185                            continue;
36186                        }
36187                    };
36188                    result.push_str(replacement);
36189                    i += 3;
36190                } else {
36191                    let replacement = match bytes[i + 1] {
36192                        b'Y' => "yyyy",
36193                        b'y' => "yy",
36194                        b'm' => "mm",
36195                        b'd' => "DD",
36196                        b'H' => "hh24",
36197                        b'M' => "mi",
36198                        b'S' => "ss",
36199                        b'f' => "ff",
36200                        _ => {
36201                            result.push('%');
36202                            i += 1;
36203                            continue;
36204                        }
36205                    };
36206                    result.push_str(replacement);
36207                    i += 2;
36208                }
36209            } else {
36210                result.push(bytes[i] as char);
36211                i += 1;
36212            }
36213        }
36214        result
36215    }
36216
36217    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
36218        // STR_TO_MAP(this, pair_delim, key_value_delim)
36219        self.write_keyword("STR_TO_MAP");
36220        self.write("(");
36221        self.generate_expression(&e.this)?;
36222        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
36223        let needs_defaults = matches!(
36224            self.config.dialect,
36225            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
36226        );
36227        if let Some(pair_delim) = &e.pair_delim {
36228            self.write(", ");
36229            self.generate_expression(pair_delim)?;
36230        } else if needs_defaults {
36231            self.write(", ','");
36232        }
36233        if let Some(key_value_delim) = &e.key_value_delim {
36234            self.write(", ");
36235            self.generate_expression(key_value_delim)?;
36236        } else if needs_defaults {
36237            self.write(", ':'");
36238        }
36239        self.write(")");
36240        Ok(())
36241    }
36242
36243    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
36244        // Detect format style: strftime (starts with %) vs Snowflake/Java
36245        let is_strftime = e.format.contains('%');
36246        // Helper: get strftime format from whatever style is stored
36247        let to_strftime = |f: &str| -> String {
36248            if is_strftime {
36249                f.to_string()
36250            } else {
36251                Self::snowflake_format_to_strftime(f)
36252            }
36253        };
36254        // Helper: get Java format
36255        let to_java = |f: &str| -> String {
36256            if is_strftime {
36257                Self::strftime_to_java_format(f)
36258            } else {
36259                Self::snowflake_format_to_spark(f)
36260            }
36261        };
36262        // Helper: get PG format
36263        let to_pg = |f: &str| -> String {
36264            if is_strftime {
36265                Self::strftime_to_postgres_format(f)
36266            } else {
36267                Self::convert_strptime_to_postgres_format(f)
36268            }
36269        };
36270
36271        match self.config.dialect {
36272            Some(DialectType::Exasol) => {
36273                self.write_keyword("TO_DATE");
36274                self.write("(");
36275                self.generate_expression(&e.this)?;
36276                self.write(", '");
36277                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
36278                self.write("'");
36279                self.write(")");
36280            }
36281            Some(DialectType::BigQuery) => {
36282                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
36283                let fmt = to_strftime(&e.format);
36284                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
36285                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
36286                self.write_keyword("PARSE_TIMESTAMP");
36287                self.write("('");
36288                self.write(&fmt);
36289                self.write("', ");
36290                self.generate_expression(&e.this)?;
36291                self.write(")");
36292            }
36293            Some(DialectType::Hive) => {
36294                // Hive: CAST(x AS TIMESTAMP) for simple date formats
36295                // Check both the raw format and the converted format (in case it's already Java)
36296                let java_fmt = to_java(&e.format);
36297                if java_fmt == "yyyy-MM-dd HH:mm:ss"
36298                    || java_fmt == "yyyy-MM-dd"
36299                    || e.format == "yyyy-MM-dd HH:mm:ss"
36300                    || e.format == "yyyy-MM-dd"
36301                {
36302                    self.write_keyword("CAST");
36303                    self.write("(");
36304                    self.generate_expression(&e.this)?;
36305                    self.write(" ");
36306                    self.write_keyword("AS TIMESTAMP");
36307                    self.write(")");
36308                } else {
36309                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
36310                    self.write_keyword("CAST");
36311                    self.write("(");
36312                    self.write_keyword("FROM_UNIXTIME");
36313                    self.write("(");
36314                    self.write_keyword("UNIX_TIMESTAMP");
36315                    self.write("(");
36316                    self.generate_expression(&e.this)?;
36317                    self.write(", '");
36318                    self.write(&java_fmt);
36319                    self.write("')");
36320                    self.write(") ");
36321                    self.write_keyword("AS TIMESTAMP");
36322                    self.write(")");
36323                }
36324            }
36325            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
36326                // Spark: TO_TIMESTAMP(value, java_format)
36327                let java_fmt = to_java(&e.format);
36328                self.write_keyword("TO_TIMESTAMP");
36329                self.write("(");
36330                self.generate_expression(&e.this)?;
36331                self.write(", '");
36332                self.write(&java_fmt);
36333                self.write("')");
36334            }
36335            Some(DialectType::MySQL) => {
36336                // MySQL: STR_TO_DATE(value, format)
36337                let mut fmt = to_strftime(&e.format);
36338                // MySQL uses %e for non-padded day, %T for %H:%M:%S
36339                fmt = fmt.replace("%-d", "%e");
36340                fmt = fmt.replace("%-m", "%c");
36341                fmt = fmt.replace("%H:%M:%S", "%T");
36342                self.write_keyword("STR_TO_DATE");
36343                self.write("(");
36344                self.generate_expression(&e.this)?;
36345                self.write(", '");
36346                self.write(&fmt);
36347                self.write("')");
36348            }
36349            Some(DialectType::Drill) => {
36350                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
36351                let java_fmt = to_java(&e.format);
36352                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
36353                let java_fmt = java_fmt.replace('T', "''T''");
36354                self.write_keyword("TO_TIMESTAMP");
36355                self.write("(");
36356                self.generate_expression(&e.this)?;
36357                self.write(", '");
36358                self.write(&java_fmt);
36359                self.write("')");
36360            }
36361            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
36362                // Presto: DATE_PARSE(value, strftime_format)
36363                let mut fmt = to_strftime(&e.format);
36364                // Presto uses %e for non-padded day, %T for %H:%M:%S
36365                fmt = fmt.replace("%-d", "%e");
36366                fmt = fmt.replace("%-m", "%c");
36367                fmt = fmt.replace("%H:%M:%S", "%T");
36368                self.write_keyword("DATE_PARSE");
36369                self.write("(");
36370                self.generate_expression(&e.this)?;
36371                self.write(", '");
36372                self.write(&fmt);
36373                self.write("')");
36374            }
36375            Some(DialectType::DuckDB) => {
36376                // DuckDB: STRPTIME(value, strftime_format)
36377                let fmt = to_strftime(&e.format);
36378                self.write_keyword("STRPTIME");
36379                self.write("(");
36380                self.generate_expression(&e.this)?;
36381                self.write(", '");
36382                self.write(&fmt);
36383                self.write("')");
36384            }
36385            Some(DialectType::PostgreSQL)
36386            | Some(DialectType::Redshift)
36387            | Some(DialectType::Materialize) => {
36388                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
36389                let pg_fmt = to_pg(&e.format);
36390                self.write_keyword("TO_TIMESTAMP");
36391                self.write("(");
36392                self.generate_expression(&e.this)?;
36393                self.write(", '");
36394                self.write(&pg_fmt);
36395                self.write("')");
36396            }
36397            Some(DialectType::Oracle) => {
36398                // Oracle: TO_TIMESTAMP(value, pg_format)
36399                let pg_fmt = to_pg(&e.format);
36400                self.write_keyword("TO_TIMESTAMP");
36401                self.write("(");
36402                self.generate_expression(&e.this)?;
36403                self.write(", '");
36404                self.write(&pg_fmt);
36405                self.write("')");
36406            }
36407            Some(DialectType::Snowflake) => {
36408                // Snowflake: TO_TIMESTAMP(value, format) - native format
36409                self.write_keyword("TO_TIMESTAMP");
36410                self.write("(");
36411                self.generate_expression(&e.this)?;
36412                self.write(", '");
36413                self.write(&e.format);
36414                self.write("')");
36415            }
36416            _ => {
36417                // Default: STR_TO_TIME(this, format)
36418                self.write_keyword("STR_TO_TIME");
36419                self.write("(");
36420                self.generate_expression(&e.this)?;
36421                self.write(", '");
36422                self.write(&e.format);
36423                self.write("'");
36424                self.write(")");
36425            }
36426        }
36427        Ok(())
36428    }
36429
36430    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
36431    fn snowflake_format_to_strftime(format: &str) -> String {
36432        let mut result = String::new();
36433        let chars: Vec<char> = format.chars().collect();
36434        let mut i = 0;
36435        while i < chars.len() {
36436            let remaining = &format[i..];
36437            if remaining.starts_with("yyyy") {
36438                result.push_str("%Y");
36439                i += 4;
36440            } else if remaining.starts_with("yy") {
36441                result.push_str("%y");
36442                i += 2;
36443            } else if remaining.starts_with("mmmm") {
36444                result.push_str("%B"); // full month name
36445                i += 4;
36446            } else if remaining.starts_with("mon") {
36447                result.push_str("%b"); // abbreviated month
36448                i += 3;
36449            } else if remaining.starts_with("mm") {
36450                result.push_str("%m");
36451                i += 2;
36452            } else if remaining.starts_with("DD") {
36453                result.push_str("%d");
36454                i += 2;
36455            } else if remaining.starts_with("dy") {
36456                result.push_str("%a"); // abbreviated day name
36457                i += 2;
36458            } else if remaining.starts_with("hh24") {
36459                result.push_str("%H");
36460                i += 4;
36461            } else if remaining.starts_with("hh12") {
36462                result.push_str("%I");
36463                i += 4;
36464            } else if remaining.starts_with("hh") {
36465                result.push_str("%H");
36466                i += 2;
36467            } else if remaining.starts_with("mi") {
36468                result.push_str("%M");
36469                i += 2;
36470            } else if remaining.starts_with("ss") {
36471                result.push_str("%S");
36472                i += 2;
36473            } else if remaining.starts_with("ff") {
36474                // Fractional seconds
36475                result.push_str("%f");
36476                i += 2;
36477                // Skip digits after ff (ff3, ff6, ff9)
36478                while i < chars.len() && chars[i].is_ascii_digit() {
36479                    i += 1;
36480                }
36481            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
36482                result.push_str("%p");
36483                i += 2;
36484            } else if remaining.starts_with("tz") {
36485                result.push_str("%Z");
36486                i += 2;
36487            } else {
36488                result.push(chars[i]);
36489                i += 1;
36490            }
36491        }
36492        result
36493    }
36494
36495    /// Convert Snowflake normalized format to Spark format (Java-style)
36496    fn snowflake_format_to_spark(format: &str) -> String {
36497        let mut result = String::new();
36498        let chars: Vec<char> = format.chars().collect();
36499        let mut i = 0;
36500        while i < chars.len() {
36501            let remaining = &format[i..];
36502            if remaining.starts_with("yyyy") {
36503                result.push_str("yyyy");
36504                i += 4;
36505            } else if remaining.starts_with("yy") {
36506                result.push_str("yy");
36507                i += 2;
36508            } else if remaining.starts_with("mmmm") {
36509                result.push_str("MMMM"); // full month name
36510                i += 4;
36511            } else if remaining.starts_with("mon") {
36512                result.push_str("MMM"); // abbreviated month
36513                i += 3;
36514            } else if remaining.starts_with("mm") {
36515                result.push_str("MM");
36516                i += 2;
36517            } else if remaining.starts_with("DD") {
36518                result.push_str("dd");
36519                i += 2;
36520            } else if remaining.starts_with("dy") {
36521                result.push_str("EEE"); // abbreviated day name
36522                i += 2;
36523            } else if remaining.starts_with("hh24") {
36524                result.push_str("HH");
36525                i += 4;
36526            } else if remaining.starts_with("hh12") {
36527                result.push_str("hh");
36528                i += 4;
36529            } else if remaining.starts_with("hh") {
36530                result.push_str("HH");
36531                i += 2;
36532            } else if remaining.starts_with("mi") {
36533                result.push_str("mm");
36534                i += 2;
36535            } else if remaining.starts_with("ss") {
36536                result.push_str("ss");
36537                i += 2;
36538            } else if remaining.starts_with("ff") {
36539                result.push_str("SSS"); // milliseconds
36540                i += 2;
36541                // Skip digits after ff
36542                while i < chars.len() && chars[i].is_ascii_digit() {
36543                    i += 1;
36544                }
36545            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
36546                result.push_str("a");
36547                i += 2;
36548            } else if remaining.starts_with("tz") {
36549                result.push_str("z");
36550                i += 2;
36551            } else {
36552                result.push(chars[i]);
36553                i += 1;
36554            }
36555        }
36556        result
36557    }
36558
36559    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
36560        match self.config.dialect {
36561            Some(DialectType::DuckDB) => {
36562                // DuckDB: EPOCH(STRPTIME(value, format))
36563                self.write_keyword("EPOCH");
36564                self.write("(");
36565                self.write_keyword("STRPTIME");
36566                self.write("(");
36567                if let Some(this) = &e.this {
36568                    self.generate_expression(this)?;
36569                }
36570                if let Some(format) = &e.format {
36571                    self.write(", '");
36572                    self.write(format);
36573                    self.write("'");
36574                }
36575                self.write("))");
36576            }
36577            Some(DialectType::Hive) => {
36578                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
36579                self.write_keyword("UNIX_TIMESTAMP");
36580                self.write("(");
36581                if let Some(this) = &e.this {
36582                    self.generate_expression(this)?;
36583                }
36584                if let Some(format) = &e.format {
36585                    let java_fmt = Self::strftime_to_java_format(format);
36586                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
36587                        self.write(", '");
36588                        self.write(&java_fmt);
36589                        self.write("'");
36590                    }
36591                }
36592                self.write(")");
36593            }
36594            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
36595                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
36596                self.write_keyword("UNIX_TIMESTAMP");
36597                self.write("(");
36598                if let Some(this) = &e.this {
36599                    self.generate_expression(this)?;
36600                }
36601                if let Some(format) = &e.format {
36602                    self.write(", '");
36603                    self.write(format);
36604                    self.write("'");
36605                }
36606                self.write(")");
36607            }
36608            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36609                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
36610                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
36611                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
36612                let java_fmt = Self::strftime_to_java_format(c_fmt);
36613                self.write_keyword("TO_UNIXTIME");
36614                self.write("(");
36615                self.write_keyword("COALESCE");
36616                self.write("(");
36617                self.write_keyword("TRY");
36618                self.write("(");
36619                self.write_keyword("DATE_PARSE");
36620                self.write("(");
36621                self.write_keyword("CAST");
36622                self.write("(");
36623                if let Some(this) = &e.this {
36624                    self.generate_expression(this)?;
36625                }
36626                self.write(" ");
36627                self.write_keyword("AS VARCHAR");
36628                self.write("), '");
36629                self.write(c_fmt);
36630                self.write("')), ");
36631                self.write_keyword("PARSE_DATETIME");
36632                self.write("(");
36633                self.write_keyword("DATE_FORMAT");
36634                self.write("(");
36635                self.write_keyword("CAST");
36636                self.write("(");
36637                if let Some(this) = &e.this {
36638                    self.generate_expression(this)?;
36639                }
36640                self.write(" ");
36641                self.write_keyword("AS TIMESTAMP");
36642                self.write("), '");
36643                self.write(c_fmt);
36644                self.write("'), '");
36645                self.write(&java_fmt);
36646                self.write("')))");
36647            }
36648            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
36649                // Spark: UNIX_TIMESTAMP(value, java_format)
36650                self.write_keyword("UNIX_TIMESTAMP");
36651                self.write("(");
36652                if let Some(this) = &e.this {
36653                    self.generate_expression(this)?;
36654                }
36655                if let Some(format) = &e.format {
36656                    let java_fmt = Self::strftime_to_java_format(format);
36657                    self.write(", '");
36658                    self.write(&java_fmt);
36659                    self.write("'");
36660                }
36661                self.write(")");
36662            }
36663            _ => {
36664                // Default: STR_TO_UNIX(this, format)
36665                self.write_keyword("STR_TO_UNIX");
36666                self.write("(");
36667                if let Some(this) = &e.this {
36668                    self.generate_expression(this)?;
36669                }
36670                if let Some(format) = &e.format {
36671                    self.write(", '");
36672                    self.write(format);
36673                    self.write("'");
36674                }
36675                self.write(")");
36676            }
36677        }
36678        Ok(())
36679    }
36680
36681    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
36682        // STRING_TO_ARRAY(this, delimiter, null_string)
36683        self.write_keyword("STRING_TO_ARRAY");
36684        self.write("(");
36685        self.generate_expression(&e.this)?;
36686        if let Some(expression) = &e.expression {
36687            self.write(", ");
36688            self.generate_expression(expression)?;
36689        }
36690        if let Some(null_val) = &e.null {
36691            self.write(", ");
36692            self.generate_expression(null_val)?;
36693        }
36694        self.write(")");
36695        Ok(())
36696    }
36697
36698    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
36699        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
36700            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
36701            self.write_keyword("OBJECT_CONSTRUCT");
36702            self.write("(");
36703            for (i, (name, expr)) in e.fields.iter().enumerate() {
36704                if i > 0 {
36705                    self.write(", ");
36706                }
36707                if let Some(name) = name {
36708                    self.write("'");
36709                    self.write(name);
36710                    self.write("'");
36711                    self.write(", ");
36712                } else {
36713                    self.write("'_");
36714                    self.write(&i.to_string());
36715                    self.write("'");
36716                    self.write(", ");
36717                }
36718                self.generate_expression(expr)?;
36719            }
36720            self.write(")");
36721        } else if self.config.struct_curly_brace_notation {
36722            // DuckDB-style: {'key': value, ...}
36723            self.write("{");
36724            for (i, (name, expr)) in e.fields.iter().enumerate() {
36725                if i > 0 {
36726                    self.write(", ");
36727                }
36728                if let Some(name) = name {
36729                    // Quote the key as a string literal
36730                    self.write("'");
36731                    self.write(name);
36732                    self.write("'");
36733                    self.write(": ");
36734                } else {
36735                    // Unnamed field: use positional key
36736                    self.write("'_");
36737                    self.write(&i.to_string());
36738                    self.write("'");
36739                    self.write(": ");
36740                }
36741                self.generate_expression(expr)?;
36742            }
36743            self.write("}");
36744        } else {
36745            // Standard SQL struct notation
36746            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
36747            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
36748            let value_as_name = matches!(
36749                self.config.dialect,
36750                Some(DialectType::BigQuery)
36751                    | Some(DialectType::Spark)
36752                    | Some(DialectType::Databricks)
36753                    | Some(DialectType::Hive)
36754            );
36755            self.write_keyword("STRUCT");
36756            self.write("(");
36757            for (i, (name, expr)) in e.fields.iter().enumerate() {
36758                if i > 0 {
36759                    self.write(", ");
36760                }
36761                if let Some(name) = name {
36762                    if value_as_name {
36763                        // STRUCT(value AS name)
36764                        self.generate_expression(expr)?;
36765                        self.write_space();
36766                        self.write_keyword("AS");
36767                        self.write_space();
36768                        // Quote name if it contains spaces or special chars
36769                        let needs_quoting = name.contains(' ') || name.contains('-');
36770                        if needs_quoting {
36771                            if matches!(
36772                                self.config.dialect,
36773                                Some(DialectType::Spark)
36774                                    | Some(DialectType::Databricks)
36775                                    | Some(DialectType::Hive)
36776                            ) {
36777                                self.write("`");
36778                                self.write(name);
36779                                self.write("`");
36780                            } else {
36781                                self.write(name);
36782                            }
36783                        } else {
36784                            self.write(name);
36785                        }
36786                    } else {
36787                        // STRUCT(name AS value)
36788                        self.write(name);
36789                        self.write_space();
36790                        self.write_keyword("AS");
36791                        self.write_space();
36792                        self.generate_expression(expr)?;
36793                    }
36794                } else {
36795                    self.generate_expression(expr)?;
36796                }
36797            }
36798            self.write(")");
36799        }
36800        Ok(())
36801    }
36802
36803    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
36804        // STUFF(this, start, length, expression)
36805        self.write_keyword("STUFF");
36806        self.write("(");
36807        self.generate_expression(&e.this)?;
36808        if let Some(start) = &e.start {
36809            self.write(", ");
36810            self.generate_expression(start)?;
36811        }
36812        if let Some(length) = e.length {
36813            self.write(", ");
36814            self.write(&length.to_string());
36815        }
36816        self.write(", ");
36817        self.generate_expression(&e.expression)?;
36818        self.write(")");
36819        Ok(())
36820    }
36821
36822    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
36823        // SUBSTRING_INDEX(this, delimiter, count)
36824        self.write_keyword("SUBSTRING_INDEX");
36825        self.write("(");
36826        self.generate_expression(&e.this)?;
36827        if let Some(delimiter) = &e.delimiter {
36828            self.write(", ");
36829            self.generate_expression(delimiter)?;
36830        }
36831        if let Some(count) = &e.count {
36832            self.write(", ");
36833            self.generate_expression(count)?;
36834        }
36835        self.write(")");
36836        Ok(())
36837    }
36838
36839    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
36840        // SUMMARIZE [TABLE] this
36841        self.write_keyword("SUMMARIZE");
36842        if e.table.is_some() {
36843            self.write_space();
36844            self.write_keyword("TABLE");
36845        }
36846        self.write_space();
36847        self.generate_expression(&e.this)?;
36848        Ok(())
36849    }
36850
36851    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
36852        // SYSTIMESTAMP
36853        self.write_keyword("SYSTIMESTAMP");
36854        Ok(())
36855    }
36856
36857    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
36858        // alias (columns...)
36859        if let Some(this) = &e.this {
36860            self.generate_expression(this)?;
36861        }
36862        if !e.columns.is_empty() {
36863            self.write("(");
36864            for (i, col) in e.columns.iter().enumerate() {
36865                if i > 0 {
36866                    self.write(", ");
36867                }
36868                self.generate_expression(col)?;
36869            }
36870            self.write(")");
36871        }
36872        Ok(())
36873    }
36874
36875    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
36876        // TABLE(this) [AS alias]
36877        self.write_keyword("TABLE");
36878        self.write("(");
36879        self.generate_expression(&e.this)?;
36880        self.write(")");
36881        if let Some(alias) = &e.alias {
36882            self.write_space();
36883            self.write_keyword("AS");
36884            self.write_space();
36885            self.write(alias);
36886        }
36887        Ok(())
36888    }
36889
36890    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
36891        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
36892        self.write_keyword("ROWS FROM");
36893        self.write(" (");
36894        for (i, expr) in e.expressions.iter().enumerate() {
36895            if i > 0 {
36896                self.write(", ");
36897            }
36898            // Each expression is either:
36899            // - A plain function (no alias)
36900            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
36901            match expr {
36902                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
36903                    // First element is the function, second is the TableAlias
36904                    self.generate_expression(&tuple.expressions[0])?;
36905                    self.write_space();
36906                    self.write_keyword("AS");
36907                    self.write_space();
36908                    self.generate_expression(&tuple.expressions[1])?;
36909                }
36910                _ => {
36911                    self.generate_expression(expr)?;
36912                }
36913            }
36914        }
36915        self.write(")");
36916        if e.ordinality {
36917            self.write_space();
36918            self.write_keyword("WITH ORDINALITY");
36919        }
36920        if let Some(alias) = &e.alias {
36921            self.write_space();
36922            self.write_keyword("AS");
36923            self.write_space();
36924            self.generate_expression(alias)?;
36925        }
36926        Ok(())
36927    }
36928
36929    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
36930        use crate::dialects::DialectType;
36931
36932        // New wrapper pattern: expression + Sample struct
36933        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
36934            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
36935            if self.config.alias_post_tablesample {
36936                // Handle Subquery with alias and Alias wrapper
36937                if let Expression::Subquery(ref s) = **this {
36938                    if let Some(ref alias) = s.alias {
36939                        // Create a clone without alias for output
36940                        let mut subquery_no_alias = (**s).clone();
36941                        subquery_no_alias.alias = None;
36942                        subquery_no_alias.column_aliases = Vec::new();
36943                        self.generate_expression(&Expression::Subquery(Box::new(
36944                            subquery_no_alias,
36945                        )))?;
36946                        self.write_space();
36947                        self.write_keyword(self.config.tablesample_keywords);
36948                        self.generate_sample_body(sample)?;
36949                        if let Some(ref seed) = sample.seed {
36950                            self.write_space();
36951                            let use_seed = sample.use_seed_keyword
36952                                && !matches!(
36953                                    self.config.dialect,
36954                                    Some(crate::dialects::DialectType::Databricks)
36955                                        | Some(crate::dialects::DialectType::Spark)
36956                                );
36957                            if use_seed {
36958                                self.write_keyword("SEED");
36959                            } else {
36960                                self.write_keyword("REPEATABLE");
36961                            }
36962                            self.write(" (");
36963                            self.generate_expression(seed)?;
36964                            self.write(")");
36965                        }
36966                        self.write_space();
36967                        self.write_keyword("AS");
36968                        self.write_space();
36969                        self.generate_identifier(alias)?;
36970                        return Ok(());
36971                    }
36972                } else if let Expression::Alias(ref a) = **this {
36973                    // Output the base expression without alias
36974                    self.generate_expression(&a.this)?;
36975                    self.write_space();
36976                    self.write_keyword(self.config.tablesample_keywords);
36977                    self.generate_sample_body(sample)?;
36978                    if let Some(ref seed) = sample.seed {
36979                        self.write_space();
36980                        let use_seed = sample.use_seed_keyword
36981                            && !matches!(
36982                                self.config.dialect,
36983                                Some(crate::dialects::DialectType::Databricks)
36984                                    | Some(crate::dialects::DialectType::Spark)
36985                            );
36986                        if use_seed {
36987                            self.write_keyword("SEED");
36988                        } else {
36989                            self.write_keyword("REPEATABLE");
36990                        }
36991                        self.write(" (");
36992                        self.generate_expression(seed)?;
36993                        self.write(")");
36994                    }
36995                    // Output alias after TABLESAMPLE
36996                    self.write_space();
36997                    self.write_keyword("AS");
36998                    self.write_space();
36999                    self.generate_identifier(&a.alias)?;
37000                    return Ok(());
37001                }
37002            }
37003            // Default: generate wrapped expression first, then TABLESAMPLE
37004            self.generate_expression(this)?;
37005            self.write_space();
37006            self.write_keyword(self.config.tablesample_keywords);
37007            self.generate_sample_body(sample)?;
37008            // Seed for table-level sample
37009            if let Some(ref seed) = sample.seed {
37010                self.write_space();
37011                // Databricks uses REPEATABLE, not SEED
37012                let use_seed = sample.use_seed_keyword
37013                    && !matches!(
37014                        self.config.dialect,
37015                        Some(crate::dialects::DialectType::Databricks)
37016                            | Some(crate::dialects::DialectType::Spark)
37017                    );
37018                if use_seed {
37019                    self.write_keyword("SEED");
37020                } else {
37021                    self.write_keyword("REPEATABLE");
37022                }
37023                self.write(" (");
37024                self.generate_expression(seed)?;
37025                self.write(")");
37026            }
37027            return Ok(());
37028        }
37029
37030        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
37031        self.write_keyword(self.config.tablesample_keywords);
37032        if let Some(method) = &e.method {
37033            self.write_space();
37034            self.write_keyword(method);
37035        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
37036            // Snowflake defaults to BERNOULLI when no method is specified
37037            self.write_space();
37038            self.write_keyword("BERNOULLI");
37039        }
37040        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
37041            self.write_space();
37042            self.write_keyword("BUCKET");
37043            self.write_space();
37044            self.generate_expression(numerator)?;
37045            self.write_space();
37046            self.write_keyword("OUT OF");
37047            self.write_space();
37048            self.generate_expression(denominator)?;
37049            if let Some(field) = &e.bucket_field {
37050                self.write_space();
37051                self.write_keyword("ON");
37052                self.write_space();
37053                self.generate_expression(field)?;
37054            }
37055        } else if !e.expressions.is_empty() {
37056            self.write(" (");
37057            for (i, expr) in e.expressions.iter().enumerate() {
37058                if i > 0 {
37059                    self.write(", ");
37060                }
37061                self.generate_expression(expr)?;
37062            }
37063            self.write(")");
37064        } else if let Some(percent) = &e.percent {
37065            self.write(" (");
37066            self.generate_expression(percent)?;
37067            self.write_space();
37068            self.write_keyword("PERCENT");
37069            self.write(")");
37070        }
37071        Ok(())
37072    }
37073
37074    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
37075        // [prefix]this[postfix]
37076        if let Some(prefix) = &e.prefix {
37077            self.generate_expression(prefix)?;
37078        }
37079        if let Some(this) = &e.this {
37080            self.generate_expression(this)?;
37081        }
37082        if let Some(postfix) = &e.postfix {
37083            self.generate_expression(postfix)?;
37084        }
37085        Ok(())
37086    }
37087
37088    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
37089        // TAG (expressions)
37090        self.write_keyword("TAG");
37091        self.write(" (");
37092        for (i, expr) in e.expressions.iter().enumerate() {
37093            if i > 0 {
37094                self.write(", ");
37095            }
37096            self.generate_expression(expr)?;
37097        }
37098        self.write(")");
37099        Ok(())
37100    }
37101
37102    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
37103        // TEMPORARY or TEMP or [this] TEMPORARY
37104        if let Some(this) = &e.this {
37105            self.generate_expression(this)?;
37106            self.write_space();
37107        }
37108        self.write_keyword("TEMPORARY");
37109        Ok(())
37110    }
37111
37112    /// Generate a Time function expression
37113    /// For most dialects: TIME('value')
37114    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
37115        // Standard: TIME(value)
37116        self.write_keyword("TIME");
37117        self.write("(");
37118        self.generate_expression(&e.this)?;
37119        self.write(")");
37120        Ok(())
37121    }
37122
37123    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
37124        // TIME_ADD(this, expression, unit)
37125        self.write_keyword("TIME_ADD");
37126        self.write("(");
37127        self.generate_expression(&e.this)?;
37128        self.write(", ");
37129        self.generate_expression(&e.expression)?;
37130        if let Some(unit) = &e.unit {
37131            self.write(", ");
37132            self.write_keyword(unit);
37133        }
37134        self.write(")");
37135        Ok(())
37136    }
37137
37138    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
37139        // TIME_DIFF(this, expression, unit)
37140        self.write_keyword("TIME_DIFF");
37141        self.write("(");
37142        self.generate_expression(&e.this)?;
37143        self.write(", ");
37144        self.generate_expression(&e.expression)?;
37145        if let Some(unit) = &e.unit {
37146            self.write(", ");
37147            self.write_keyword(unit);
37148        }
37149        self.write(")");
37150        Ok(())
37151    }
37152
37153    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
37154        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
37155        self.write_keyword("TIME_FROM_PARTS");
37156        self.write("(");
37157        let mut first = true;
37158        if let Some(hour) = &e.hour {
37159            self.generate_expression(hour)?;
37160            first = false;
37161        }
37162        if let Some(minute) = &e.min {
37163            if !first {
37164                self.write(", ");
37165            }
37166            self.generate_expression(minute)?;
37167            first = false;
37168        }
37169        if let Some(second) = &e.sec {
37170            if !first {
37171                self.write(", ");
37172            }
37173            self.generate_expression(second)?;
37174            first = false;
37175        }
37176        if let Some(ns) = &e.nano {
37177            if !first {
37178                self.write(", ");
37179            }
37180            self.generate_expression(ns)?;
37181        }
37182        self.write(")");
37183        Ok(())
37184    }
37185
37186    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
37187        // TIME_SLICE(this, expression, unit)
37188        self.write_keyword("TIME_SLICE");
37189        self.write("(");
37190        self.generate_expression(&e.this)?;
37191        self.write(", ");
37192        self.generate_expression(&e.expression)?;
37193        self.write(", ");
37194        self.write_keyword(&e.unit);
37195        self.write(")");
37196        Ok(())
37197    }
37198
37199    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
37200        // TIME_STR_TO_TIME(this)
37201        self.write_keyword("TIME_STR_TO_TIME");
37202        self.write("(");
37203        self.generate_expression(&e.this)?;
37204        self.write(")");
37205        Ok(())
37206    }
37207
37208    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
37209        // TIME_SUB(this, expression, unit)
37210        self.write_keyword("TIME_SUB");
37211        self.write("(");
37212        self.generate_expression(&e.this)?;
37213        self.write(", ");
37214        self.generate_expression(&e.expression)?;
37215        if let Some(unit) = &e.unit {
37216            self.write(", ");
37217            self.write_keyword(unit);
37218        }
37219        self.write(")");
37220        Ok(())
37221    }
37222
37223    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
37224        match self.config.dialect {
37225            Some(DialectType::Exasol) => {
37226                // Exasol uses TO_CHAR with Exasol-specific format
37227                self.write_keyword("TO_CHAR");
37228                self.write("(");
37229                self.generate_expression(&e.this)?;
37230                self.write(", '");
37231                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
37232                self.write("'");
37233                self.write(")");
37234            }
37235            Some(DialectType::PostgreSQL)
37236            | Some(DialectType::Redshift)
37237            | Some(DialectType::Materialize) => {
37238                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
37239                self.write_keyword("TO_CHAR");
37240                self.write("(");
37241                self.generate_expression(&e.this)?;
37242                self.write(", '");
37243                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
37244                self.write("'");
37245                self.write(")");
37246            }
37247            Some(DialectType::Oracle) => {
37248                // Oracle uses TO_CHAR with PG-like format
37249                self.write_keyword("TO_CHAR");
37250                self.write("(");
37251                self.generate_expression(&e.this)?;
37252                self.write(", '");
37253                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
37254                self.write("'");
37255                self.write(")");
37256            }
37257            Some(DialectType::Drill) => {
37258                // Drill: TO_CHAR with Java format
37259                self.write_keyword("TO_CHAR");
37260                self.write("(");
37261                self.generate_expression(&e.this)?;
37262                self.write(", '");
37263                self.write(&Self::strftime_to_java_format(&e.format));
37264                self.write("'");
37265                self.write(")");
37266            }
37267            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
37268                // TSQL: FORMAT(value, format) with .NET-style format
37269                self.write_keyword("FORMAT");
37270                self.write("(");
37271                self.generate_expression(&e.this)?;
37272                self.write(", '");
37273                self.write(&Self::strftime_to_tsql_format(&e.format));
37274                self.write("'");
37275                self.write(")");
37276            }
37277            Some(DialectType::DuckDB) => {
37278                // DuckDB: STRFTIME(value, format) - keeps C format
37279                self.write_keyword("STRFTIME");
37280                self.write("(");
37281                self.generate_expression(&e.this)?;
37282                self.write(", '");
37283                self.write(&e.format);
37284                self.write("'");
37285                self.write(")");
37286            }
37287            Some(DialectType::BigQuery) => {
37288                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
37289                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
37290                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
37291                self.write_keyword("FORMAT_DATE");
37292                self.write("('");
37293                self.write(&fmt);
37294                self.write("', ");
37295                self.generate_expression(&e.this)?;
37296                self.write(")");
37297            }
37298            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
37299                // Hive/Spark: DATE_FORMAT(value, java_format)
37300                self.write_keyword("DATE_FORMAT");
37301                self.write("(");
37302                self.generate_expression(&e.this)?;
37303                self.write(", '");
37304                self.write(&Self::strftime_to_java_format(&e.format));
37305                self.write("'");
37306                self.write(")");
37307            }
37308            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
37309                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
37310                self.write_keyword("DATE_FORMAT");
37311                self.write("(");
37312                self.generate_expression(&e.this)?;
37313                self.write(", '");
37314                self.write(&e.format);
37315                self.write("'");
37316                self.write(")");
37317            }
37318            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
37319                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
37320                self.write_keyword("DATE_FORMAT");
37321                self.write("(");
37322                self.generate_expression(&e.this)?;
37323                self.write(", '");
37324                self.write(&e.format);
37325                self.write("'");
37326                self.write(")");
37327            }
37328            _ => {
37329                // Default: TIME_TO_STR(this, format)
37330                self.write_keyword("TIME_TO_STR");
37331                self.write("(");
37332                self.generate_expression(&e.this)?;
37333                self.write(", '");
37334                self.write(&e.format);
37335                self.write("'");
37336                self.write(")");
37337            }
37338        }
37339        Ok(())
37340    }
37341
37342    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
37343        match self.config.dialect {
37344            Some(DialectType::DuckDB) => {
37345                // DuckDB: EPOCH(x)
37346                self.write_keyword("EPOCH");
37347                self.write("(");
37348                self.generate_expression(&e.this)?;
37349                self.write(")");
37350            }
37351            Some(DialectType::Hive)
37352            | Some(DialectType::Spark)
37353            | Some(DialectType::Databricks)
37354            | Some(DialectType::Doris)
37355            | Some(DialectType::StarRocks)
37356            | Some(DialectType::Drill) => {
37357                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
37358                self.write_keyword("UNIX_TIMESTAMP");
37359                self.write("(");
37360                self.generate_expression(&e.this)?;
37361                self.write(")");
37362            }
37363            Some(DialectType::Presto) | Some(DialectType::Trino) => {
37364                // Presto: TO_UNIXTIME(x)
37365                self.write_keyword("TO_UNIXTIME");
37366                self.write("(");
37367                self.generate_expression(&e.this)?;
37368                self.write(")");
37369            }
37370            _ => {
37371                // Default: TIME_TO_UNIX(x)
37372                self.write_keyword("TIME_TO_UNIX");
37373                self.write("(");
37374                self.generate_expression(&e.this)?;
37375                self.write(")");
37376            }
37377        }
37378        Ok(())
37379    }
37380
37381    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
37382        match self.config.dialect {
37383            Some(DialectType::Hive) => {
37384                // Hive: TO_DATE(x)
37385                self.write_keyword("TO_DATE");
37386                self.write("(");
37387                self.generate_expression(&e.this)?;
37388                self.write(")");
37389            }
37390            _ => {
37391                // Default: TIME_STR_TO_DATE(x)
37392                self.write_keyword("TIME_STR_TO_DATE");
37393                self.write("(");
37394                self.generate_expression(&e.this)?;
37395                self.write(")");
37396            }
37397        }
37398        Ok(())
37399    }
37400
37401    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
37402        // TIME_TRUNC(this, unit)
37403        self.write_keyword("TIME_TRUNC");
37404        self.write("(");
37405        self.generate_expression(&e.this)?;
37406        self.write(", ");
37407        self.write_keyword(&e.unit);
37408        self.write(")");
37409        Ok(())
37410    }
37411
37412    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
37413        // Just output the unit name
37414        if let Some(unit) = &e.unit {
37415            self.write_keyword(unit);
37416        }
37417        Ok(())
37418    }
37419
37420    /// Generate a Timestamp function expression
37421    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
37422    /// For other dialects: TIMESTAMP('value')
37423    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
37424        use crate::dialects::DialectType;
37425        use crate::expressions::Literal;
37426
37427        match self.config.dialect {
37428            // Exasol uses TO_TIMESTAMP for Timestamp expressions
37429            Some(DialectType::Exasol) => {
37430                self.write_keyword("TO_TIMESTAMP");
37431                self.write("(");
37432                // Extract the string value from the expression if it's a string literal
37433                if let Some(this) = &e.this {
37434                    match this.as_ref() {
37435                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
37436                            let Literal::String(s) = lit.as_ref() else {
37437                                unreachable!()
37438                            };
37439                            self.write("'");
37440                            self.write(s);
37441                            self.write("'");
37442                        }
37443                        _ => {
37444                            self.generate_expression(this)?;
37445                        }
37446                    }
37447                }
37448                self.write(")");
37449            }
37450            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
37451            _ => {
37452                self.write_keyword("TIMESTAMP");
37453                self.write("(");
37454                if let Some(this) = &e.this {
37455                    self.generate_expression(this)?;
37456                }
37457                if let Some(zone) = &e.zone {
37458                    self.write(", ");
37459                    self.generate_expression(zone)?;
37460                }
37461                self.write(")");
37462            }
37463        }
37464        Ok(())
37465    }
37466
37467    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
37468        // TIMESTAMP_ADD(this, expression, unit)
37469        self.write_keyword("TIMESTAMP_ADD");
37470        self.write("(");
37471        self.generate_expression(&e.this)?;
37472        self.write(", ");
37473        self.generate_expression(&e.expression)?;
37474        if let Some(unit) = &e.unit {
37475            self.write(", ");
37476            self.write_keyword(unit);
37477        }
37478        self.write(")");
37479        Ok(())
37480    }
37481
37482    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
37483        // TIMESTAMP_DIFF(this, expression, unit)
37484        self.write_keyword("TIMESTAMP_DIFF");
37485        self.write("(");
37486        self.generate_expression(&e.this)?;
37487        self.write(", ");
37488        self.generate_expression(&e.expression)?;
37489        if let Some(unit) = &e.unit {
37490            self.write(", ");
37491            self.write_keyword(unit);
37492        }
37493        self.write(")");
37494        Ok(())
37495    }
37496
37497    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
37498        // TIMESTAMP_FROM_PARTS(this, expression)
37499        self.write_keyword("TIMESTAMP_FROM_PARTS");
37500        self.write("(");
37501        if let Some(this) = &e.this {
37502            self.generate_expression(this)?;
37503        }
37504        if let Some(expression) = &e.expression {
37505            self.write(", ");
37506            self.generate_expression(expression)?;
37507        }
37508        if let Some(zone) = &e.zone {
37509            self.write(", ");
37510            self.generate_expression(zone)?;
37511        }
37512        if let Some(milli) = &e.milli {
37513            self.write(", ");
37514            self.generate_expression(milli)?;
37515        }
37516        self.write(")");
37517        Ok(())
37518    }
37519
37520    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
37521        // TIMESTAMP_SUB(this, INTERVAL expression unit)
37522        self.write_keyword("TIMESTAMP_SUB");
37523        self.write("(");
37524        self.generate_expression(&e.this)?;
37525        self.write(", ");
37526        self.write_keyword("INTERVAL");
37527        self.write_space();
37528        self.generate_expression(&e.expression)?;
37529        if let Some(unit) = &e.unit {
37530            self.write_space();
37531            self.write_keyword(unit);
37532        }
37533        self.write(")");
37534        Ok(())
37535    }
37536
37537    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
37538        // TIMESTAMP_TZ_FROM_PARTS(...)
37539        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
37540        self.write("(");
37541        if let Some(zone) = &e.zone {
37542            self.generate_expression(zone)?;
37543        }
37544        self.write(")");
37545        Ok(())
37546    }
37547
37548    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
37549        // TO_BINARY(this, [format])
37550        self.write_keyword("TO_BINARY");
37551        self.write("(");
37552        self.generate_expression(&e.this)?;
37553        if let Some(format) = &e.format {
37554            self.write(", '");
37555            self.write(format);
37556            self.write("'");
37557        }
37558        self.write(")");
37559        Ok(())
37560    }
37561
37562    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
37563        // TO_BOOLEAN(this)
37564        self.write_keyword("TO_BOOLEAN");
37565        self.write("(");
37566        self.generate_expression(&e.this)?;
37567        self.write(")");
37568        Ok(())
37569    }
37570
37571    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
37572        // TO_CHAR(this, [format], [nlsparam])
37573        self.write_keyword("TO_CHAR");
37574        self.write("(");
37575        self.generate_expression(&e.this)?;
37576        if let Some(format) = &e.format {
37577            self.write(", '");
37578            self.write(format);
37579            self.write("'");
37580        }
37581        if let Some(nlsparam) = &e.nlsparam {
37582            self.write(", ");
37583            self.generate_expression(nlsparam)?;
37584        }
37585        self.write(")");
37586        Ok(())
37587    }
37588
37589    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
37590        // TO_DECFLOAT(this, [format])
37591        self.write_keyword("TO_DECFLOAT");
37592        self.write("(");
37593        self.generate_expression(&e.this)?;
37594        if let Some(format) = &e.format {
37595            self.write(", '");
37596            self.write(format);
37597            self.write("'");
37598        }
37599        self.write(")");
37600        Ok(())
37601    }
37602
37603    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
37604        // TO_DOUBLE(this, [format])
37605        self.write_keyword("TO_DOUBLE");
37606        self.write("(");
37607        self.generate_expression(&e.this)?;
37608        if let Some(format) = &e.format {
37609            self.write(", '");
37610            self.write(format);
37611            self.write("'");
37612        }
37613        self.write(")");
37614        Ok(())
37615    }
37616
37617    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
37618        // TO_FILE(this, path)
37619        self.write_keyword("TO_FILE");
37620        self.write("(");
37621        self.generate_expression(&e.this)?;
37622        if let Some(path) = &e.path {
37623            self.write(", ");
37624            self.generate_expression(path)?;
37625        }
37626        self.write(")");
37627        Ok(())
37628    }
37629
37630    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
37631        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
37632        // If safe flag is set, output TRY_TO_NUMBER
37633        let is_safe = e.safe.is_some();
37634        if is_safe {
37635            self.write_keyword("TRY_TO_NUMBER");
37636        } else {
37637            self.write_keyword("TO_NUMBER");
37638        }
37639        self.write("(");
37640        self.generate_expression(&e.this)?;
37641        let precision_is_snowflake_default = e.precision.is_none()
37642            || matches!(
37643                e.precision.as_deref(),
37644                Some(Expression::Literal(lit))
37645                    if matches!(lit.as_ref(), Literal::Number(n) if n == "0")
37646            );
37647        let is_snowflake_default_precision =
37648            matches!(self.config.dialect, Some(DialectType::Snowflake))
37649                && e.nlsparam.is_none()
37650                && e.scale.is_none()
37651                && matches!(
37652                    e.format.as_deref(),
37653                    Some(Expression::Literal(lit))
37654                        if matches!(lit.as_ref(), Literal::Number(n) if n == "38")
37655                )
37656                && precision_is_snowflake_default;
37657
37658        if !is_snowflake_default_precision {
37659            if let Some(format) = &e.format {
37660                self.write(", ");
37661                self.generate_expression(format)?;
37662            }
37663            if let Some(nlsparam) = &e.nlsparam {
37664                self.write(", ");
37665                self.generate_expression(nlsparam)?;
37666            }
37667            if let Some(precision) = &e.precision {
37668                self.write(", ");
37669                self.generate_expression(precision)?;
37670            }
37671            if let Some(scale) = &e.scale {
37672                self.write(", ");
37673                self.generate_expression(scale)?;
37674            }
37675        }
37676        self.write(")");
37677        Ok(())
37678    }
37679
37680    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
37681        // TO_TABLE this
37682        self.write_keyword("TO_TABLE");
37683        self.write_space();
37684        self.generate_expression(&e.this)?;
37685        Ok(())
37686    }
37687
37688    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
37689        // Check mark to determine the format
37690        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
37691            Expression::Identifier(id) => id.name.clone(),
37692            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
37693                let Literal::String(s) = lit.as_ref() else {
37694                    unreachable!()
37695                };
37696                s.clone()
37697            }
37698            _ => String::new(),
37699        });
37700
37701        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
37702        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
37703        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
37704            matches!(m.as_ref(), Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
37705        });
37706
37707        // For Presto/Trino: always use START TRANSACTION
37708        let use_start_transaction = matches!(
37709            self.config.dialect,
37710            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
37711        );
37712        // For most dialects: strip TRANSACTION keyword
37713        let strip_transaction = matches!(
37714            self.config.dialect,
37715            Some(DialectType::Snowflake)
37716                | Some(DialectType::PostgreSQL)
37717                | Some(DialectType::Redshift)
37718                | Some(DialectType::MySQL)
37719                | Some(DialectType::Hive)
37720                | Some(DialectType::Spark)
37721                | Some(DialectType::Databricks)
37722                | Some(DialectType::DuckDB)
37723                | Some(DialectType::Oracle)
37724                | Some(DialectType::Doris)
37725                | Some(DialectType::StarRocks)
37726                | Some(DialectType::Materialize)
37727                | Some(DialectType::ClickHouse)
37728        );
37729
37730        if is_start || use_start_transaction {
37731            // START TRANSACTION [modes]
37732            self.write_keyword("START TRANSACTION");
37733            if let Some(modes) = &e.modes {
37734                self.write_space();
37735                self.generate_expression(modes)?;
37736            }
37737        } else {
37738            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
37739            self.write_keyword("BEGIN");
37740
37741            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
37742            let is_kind = e.this.as_ref().map_or(false, |t| {
37743                if let Expression::Identifier(id) = t.as_ref() {
37744                    id.name.eq_ignore_ascii_case("DEFERRED")
37745                        || id.name.eq_ignore_ascii_case("IMMEDIATE")
37746                        || id.name.eq_ignore_ascii_case("EXCLUSIVE")
37747                } else {
37748                    false
37749                }
37750            });
37751
37752            // Output kind before TRANSACTION keyword
37753            if is_kind {
37754                if let Some(this) = &e.this {
37755                    self.write_space();
37756                    if let Expression::Identifier(id) = this.as_ref() {
37757                        self.write_keyword(&id.name);
37758                    }
37759                }
37760            }
37761
37762            // Output TRANSACTION keyword if it was present and target supports it
37763            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
37764                self.write_space();
37765                self.write_keyword("TRANSACTION");
37766            }
37767
37768            // Output transaction name (not kind)
37769            if !is_kind {
37770                if let Some(this) = &e.this {
37771                    self.write_space();
37772                    self.generate_expression(this)?;
37773                }
37774            }
37775
37776            // Output WITH MARK 'description' for TSQL
37777            if has_with_mark {
37778                self.write_space();
37779                self.write_keyword("WITH MARK");
37780                if let Some(Expression::Literal(lit)) = e.mark.as_deref() {
37781                    if let Literal::String(desc) = lit.as_ref() {
37782                        if !desc.is_empty() {
37783                            self.write_space();
37784                            self.write(&format!("'{}'", desc));
37785                        }
37786                    }
37787                }
37788            }
37789
37790            // Output modes (isolation levels, etc.)
37791            if let Some(modes) = &e.modes {
37792                self.write_space();
37793                self.generate_expression(modes)?;
37794            }
37795        }
37796        Ok(())
37797    }
37798
37799    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
37800        // TRANSFORM(this, expression)
37801        self.write_keyword("TRANSFORM");
37802        self.write("(");
37803        self.generate_expression(&e.this)?;
37804        self.write(", ");
37805        self.generate_expression(&e.expression)?;
37806        self.write(")");
37807        Ok(())
37808    }
37809
37810    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
37811        // TRANSFORM(expressions)
37812        self.write_keyword("TRANSFORM");
37813        self.write("(");
37814        if self.config.pretty && !e.expressions.is_empty() {
37815            self.indent_level += 1;
37816            for (i, expr) in e.expressions.iter().enumerate() {
37817                if i > 0 {
37818                    self.write(",");
37819                }
37820                self.write_newline();
37821                self.write_indent();
37822                self.generate_expression(expr)?;
37823            }
37824            self.indent_level -= 1;
37825            self.write_newline();
37826            self.write(")");
37827        } else {
37828            for (i, expr) in e.expressions.iter().enumerate() {
37829                if i > 0 {
37830                    self.write(", ");
37831                }
37832                self.generate_expression(expr)?;
37833            }
37834            self.write(")");
37835        }
37836        Ok(())
37837    }
37838
37839    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
37840        use crate::dialects::DialectType;
37841        // TRANSIENT is Snowflake-specific; skip for other dialects
37842        if let Some(this) = &e.this {
37843            self.generate_expression(this)?;
37844            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
37845                self.write_space();
37846            }
37847        }
37848        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
37849            self.write_keyword("TRANSIENT");
37850        }
37851        Ok(())
37852    }
37853
37854    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
37855        // TRANSLATE(this, from_, to)
37856        self.write_keyword("TRANSLATE");
37857        self.write("(");
37858        self.generate_expression(&e.this)?;
37859        if let Some(from) = &e.from_ {
37860            self.write(", ");
37861            self.generate_expression(from)?;
37862        }
37863        if let Some(to) = &e.to {
37864            self.write(", ");
37865            self.generate_expression(to)?;
37866        }
37867        self.write(")");
37868        Ok(())
37869    }
37870
37871    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
37872        // TRANSLATE(this USING expression)
37873        self.write_keyword("TRANSLATE");
37874        self.write("(");
37875        self.generate_expression(&e.this)?;
37876        self.write_space();
37877        self.write_keyword("USING");
37878        self.write_space();
37879        self.generate_expression(&e.expression)?;
37880        if e.with_error.is_some() {
37881            self.write_space();
37882            self.write_keyword("WITH ERROR");
37883        }
37884        self.write(")");
37885        Ok(())
37886    }
37887
37888    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
37889        // TRUNCATE TABLE table1, table2, ...
37890        self.write_keyword("TRUNCATE TABLE");
37891        self.write_space();
37892        for (i, expr) in e.expressions.iter().enumerate() {
37893            if i > 0 {
37894                self.write(", ");
37895            }
37896            self.generate_expression(expr)?;
37897        }
37898        Ok(())
37899    }
37900
37901    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
37902        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
37903        self.write_keyword("TRY_BASE64_DECODE_BINARY");
37904        self.write("(");
37905        self.generate_expression(&e.this)?;
37906        if let Some(alphabet) = &e.alphabet {
37907            self.write(", ");
37908            self.generate_expression(alphabet)?;
37909        }
37910        self.write(")");
37911        Ok(())
37912    }
37913
37914    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
37915        // TRY_BASE64_DECODE_STRING(this, [alphabet])
37916        self.write_keyword("TRY_BASE64_DECODE_STRING");
37917        self.write("(");
37918        self.generate_expression(&e.this)?;
37919        if let Some(alphabet) = &e.alphabet {
37920            self.write(", ");
37921            self.generate_expression(alphabet)?;
37922        }
37923        self.write(")");
37924        Ok(())
37925    }
37926
37927    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
37928        // TRY_TO_DECFLOAT(this, [format])
37929        self.write_keyword("TRY_TO_DECFLOAT");
37930        self.write("(");
37931        self.generate_expression(&e.this)?;
37932        if let Some(format) = &e.format {
37933            self.write(", '");
37934            self.write(format);
37935            self.write("'");
37936        }
37937        self.write(")");
37938        Ok(())
37939    }
37940
37941    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
37942        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
37943        self.write_keyword("TS_OR_DS_ADD");
37944        self.write("(");
37945        self.generate_expression(&e.this)?;
37946        self.write(", ");
37947        self.generate_expression(&e.expression)?;
37948        if let Some(unit) = &e.unit {
37949            self.write(", ");
37950            self.write_keyword(unit);
37951        }
37952        if let Some(return_type) = &e.return_type {
37953            self.write(", ");
37954            self.generate_expression(return_type)?;
37955        }
37956        self.write(")");
37957        Ok(())
37958    }
37959
37960    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
37961        // TS_OR_DS_DIFF(this, expression, [unit])
37962        self.write_keyword("TS_OR_DS_DIFF");
37963        self.write("(");
37964        self.generate_expression(&e.this)?;
37965        self.write(", ");
37966        self.generate_expression(&e.expression)?;
37967        if let Some(unit) = &e.unit {
37968            self.write(", ");
37969            self.write_keyword(unit);
37970        }
37971        self.write(")");
37972        Ok(())
37973    }
37974
37975    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
37976        let default_time_format = "%Y-%m-%d %H:%M:%S";
37977        let default_date_format = "%Y-%m-%d";
37978        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
37979            f != default_time_format && f != default_date_format
37980        });
37981
37982        if has_non_default_format {
37983            // With non-default format: dialect-specific handling
37984            let fmt = e.format.as_ref().unwrap();
37985            match self.config.dialect {
37986                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
37987                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
37988                    // STR_TO_DATE is the MySQL-native form of StrToTime
37989                    let str_to_time = crate::expressions::StrToTime {
37990                        this: Box::new((*e.this).clone()),
37991                        format: fmt.clone(),
37992                        zone: None,
37993                        safe: None,
37994                        target_type: None,
37995                    };
37996                    self.generate_str_to_time(&str_to_time)?;
37997                }
37998                Some(DialectType::Hive)
37999                | Some(DialectType::Spark)
38000                | Some(DialectType::Databricks) => {
38001                    // Hive/Spark: TO_DATE(x, java_fmt)
38002                    self.write_keyword("TO_DATE");
38003                    self.write("(");
38004                    self.generate_expression(&e.this)?;
38005                    self.write(", '");
38006                    self.write(&Self::strftime_to_java_format(fmt));
38007                    self.write("')");
38008                }
38009                Some(DialectType::Snowflake) => {
38010                    // Snowflake: TO_DATE(x, snowflake_fmt)
38011                    self.write_keyword("TO_DATE");
38012                    self.write("(");
38013                    self.generate_expression(&e.this)?;
38014                    self.write(", '");
38015                    self.write(&Self::strftime_to_snowflake_format(fmt));
38016                    self.write("')");
38017                }
38018                Some(DialectType::Doris) => {
38019                    // Doris: TO_DATE(x) - ignores format
38020                    self.write_keyword("TO_DATE");
38021                    self.write("(");
38022                    self.generate_expression(&e.this)?;
38023                    self.write(")");
38024                }
38025                _ => {
38026                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
38027                    self.write_keyword("CAST");
38028                    self.write("(");
38029                    let str_to_time = crate::expressions::StrToTime {
38030                        this: Box::new((*e.this).clone()),
38031                        format: fmt.clone(),
38032                        zone: None,
38033                        safe: None,
38034                        target_type: None,
38035                    };
38036                    self.generate_str_to_time(&str_to_time)?;
38037                    self.write_keyword(" AS ");
38038                    self.write_keyword("DATE");
38039                    self.write(")");
38040                }
38041            }
38042        } else {
38043            // Without format (or default format): simple date conversion
38044            match self.config.dialect {
38045                Some(DialectType::MySQL)
38046                | Some(DialectType::SQLite)
38047                | Some(DialectType::StarRocks) => {
38048                    // MySQL/SQLite/StarRocks: DATE(x)
38049                    self.write_keyword("DATE");
38050                    self.write("(");
38051                    self.generate_expression(&e.this)?;
38052                    self.write(")");
38053                }
38054                Some(DialectType::Hive)
38055                | Some(DialectType::Spark)
38056                | Some(DialectType::Databricks)
38057                | Some(DialectType::Snowflake)
38058                | Some(DialectType::Doris) => {
38059                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
38060                    self.write_keyword("TO_DATE");
38061                    self.write("(");
38062                    self.generate_expression(&e.this)?;
38063                    self.write(")");
38064                }
38065                Some(DialectType::Presto)
38066                | Some(DialectType::Trino)
38067                | Some(DialectType::Athena) => {
38068                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
38069                    self.write_keyword("CAST");
38070                    self.write("(");
38071                    self.write_keyword("CAST");
38072                    self.write("(");
38073                    self.generate_expression(&e.this)?;
38074                    self.write_keyword(" AS ");
38075                    self.write_keyword("TIMESTAMP");
38076                    self.write(")");
38077                    self.write_keyword(" AS ");
38078                    self.write_keyword("DATE");
38079                    self.write(")");
38080                }
38081                Some(DialectType::ClickHouse) => {
38082                    // ClickHouse: CAST(x AS Nullable(DATE))
38083                    self.write_keyword("CAST");
38084                    self.write("(");
38085                    self.generate_expression(&e.this)?;
38086                    self.write_keyword(" AS ");
38087                    self.write("Nullable(DATE)");
38088                    self.write(")");
38089                }
38090                _ => {
38091                    // Default: CAST(x AS DATE)
38092                    self.write_keyword("CAST");
38093                    self.write("(");
38094                    self.generate_expression(&e.this)?;
38095                    self.write_keyword(" AS ");
38096                    self.write_keyword("DATE");
38097                    self.write(")");
38098                }
38099            }
38100        }
38101        Ok(())
38102    }
38103
38104    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
38105        // TS_OR_DS_TO_TIME(this, [format])
38106        self.write_keyword("TS_OR_DS_TO_TIME");
38107        self.write("(");
38108        self.generate_expression(&e.this)?;
38109        if let Some(format) = &e.format {
38110            self.write(", '");
38111            self.write(format);
38112            self.write("'");
38113        }
38114        self.write(")");
38115        Ok(())
38116    }
38117
38118    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
38119        // UNHEX(this, [expression])
38120        self.write_keyword("UNHEX");
38121        self.write("(");
38122        self.generate_expression(&e.this)?;
38123        if let Some(expression) = &e.expression {
38124            self.write(", ");
38125            self.generate_expression(expression)?;
38126        }
38127        self.write(")");
38128        Ok(())
38129    }
38130
38131    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
38132        // U&this [UESCAPE escape]
38133        self.write("U&");
38134        self.generate_expression(&e.this)?;
38135        if let Some(escape) = &e.escape {
38136            self.write_space();
38137            self.write_keyword("UESCAPE");
38138            self.write_space();
38139            self.generate_expression(escape)?;
38140        }
38141        Ok(())
38142    }
38143
38144    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
38145        // UNIFORM(this, expression, [gen], [seed])
38146        self.write_keyword("UNIFORM");
38147        self.write("(");
38148        self.generate_expression(&e.this)?;
38149        self.write(", ");
38150        self.generate_expression(&e.expression)?;
38151        if let Some(gen) = &e.gen {
38152            self.write(", ");
38153            self.generate_expression(gen)?;
38154        }
38155        if let Some(seed) = &e.seed {
38156            self.write(", ");
38157            self.generate_expression(seed)?;
38158        }
38159        self.write(")");
38160        Ok(())
38161    }
38162
38163    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
38164        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
38165        self.write_keyword("UNIQUE");
38166        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
38167        if e.nulls.is_some() {
38168            self.write(" NULLS NOT DISTINCT");
38169        }
38170        if let Some(this) = &e.this {
38171            self.write_space();
38172            self.generate_expression(this)?;
38173        }
38174        if let Some(index_type) = &e.index_type {
38175            self.write(" USING ");
38176            self.generate_expression(index_type)?;
38177        }
38178        if let Some(on_conflict) = &e.on_conflict {
38179            self.write_space();
38180            self.generate_expression(on_conflict)?;
38181        }
38182        for opt in &e.options {
38183            self.write_space();
38184            self.generate_expression(opt)?;
38185        }
38186        Ok(())
38187    }
38188
38189    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
38190        // UNIQUE KEY (expressions)
38191        self.write_keyword("UNIQUE KEY");
38192        self.write(" (");
38193        for (i, expr) in e.expressions.iter().enumerate() {
38194            if i > 0 {
38195                self.write(", ");
38196            }
38197            self.generate_expression(expr)?;
38198        }
38199        self.write(")");
38200        Ok(())
38201    }
38202
38203    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
38204        // ROLLUP (r1(col1, col2), r2(col1))
38205        self.write_keyword("ROLLUP");
38206        self.write(" (");
38207        for (i, index) in e.expressions.iter().enumerate() {
38208            if i > 0 {
38209                self.write(", ");
38210            }
38211            self.generate_identifier(&index.name)?;
38212            self.write("(");
38213            for (j, col) in index.expressions.iter().enumerate() {
38214                if j > 0 {
38215                    self.write(", ");
38216                }
38217                self.generate_identifier(col)?;
38218            }
38219            self.write(")");
38220        }
38221        self.write(")");
38222        Ok(())
38223    }
38224
38225    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
38226        match self.config.dialect {
38227            Some(DialectType::DuckDB) => {
38228                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
38229                self.write_keyword("STRFTIME");
38230                self.write("(");
38231                self.write_keyword("TO_TIMESTAMP");
38232                self.write("(");
38233                self.generate_expression(&e.this)?;
38234                self.write("), '");
38235                if let Some(format) = &e.format {
38236                    self.write(format);
38237                }
38238                self.write("')");
38239            }
38240            Some(DialectType::Hive) => {
38241                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
38242                self.write_keyword("FROM_UNIXTIME");
38243                self.write("(");
38244                self.generate_expression(&e.this)?;
38245                if let Some(format) = &e.format {
38246                    if format != "yyyy-MM-dd HH:mm:ss" {
38247                        self.write(", '");
38248                        self.write(format);
38249                        self.write("'");
38250                    }
38251                }
38252                self.write(")");
38253            }
38254            Some(DialectType::Presto) | Some(DialectType::Trino) => {
38255                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
38256                self.write_keyword("DATE_FORMAT");
38257                self.write("(");
38258                self.write_keyword("FROM_UNIXTIME");
38259                self.write("(");
38260                self.generate_expression(&e.this)?;
38261                self.write("), '");
38262                if let Some(format) = &e.format {
38263                    self.write(format);
38264                }
38265                self.write("')");
38266            }
38267            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
38268                // Spark: FROM_UNIXTIME(value, format)
38269                self.write_keyword("FROM_UNIXTIME");
38270                self.write("(");
38271                self.generate_expression(&e.this)?;
38272                if let Some(format) = &e.format {
38273                    self.write(", '");
38274                    self.write(format);
38275                    self.write("'");
38276                }
38277                self.write(")");
38278            }
38279            _ => {
38280                // Default: UNIX_TO_STR(this, [format])
38281                self.write_keyword("UNIX_TO_STR");
38282                self.write("(");
38283                self.generate_expression(&e.this)?;
38284                if let Some(format) = &e.format {
38285                    self.write(", '");
38286                    self.write(format);
38287                    self.write("'");
38288                }
38289                self.write(")");
38290            }
38291        }
38292        Ok(())
38293    }
38294
38295    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
38296        use crate::dialects::DialectType;
38297        let scale = e.scale.unwrap_or(0); // 0 = seconds
38298
38299        match self.config.dialect {
38300            Some(DialectType::Snowflake) => {
38301                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
38302                self.write_keyword("TO_TIMESTAMP");
38303                self.write("(");
38304                self.generate_expression(&e.this)?;
38305                if let Some(s) = e.scale {
38306                    if s > 0 {
38307                        self.write(", ");
38308                        self.write(&s.to_string());
38309                    }
38310                }
38311                self.write(")");
38312            }
38313            Some(DialectType::BigQuery) => {
38314                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
38315                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
38316                match scale {
38317                    0 => {
38318                        self.write_keyword("TIMESTAMP_SECONDS");
38319                        self.write("(");
38320                        self.generate_expression(&e.this)?;
38321                        self.write(")");
38322                    }
38323                    3 => {
38324                        self.write_keyword("TIMESTAMP_MILLIS");
38325                        self.write("(");
38326                        self.generate_expression(&e.this)?;
38327                        self.write(")");
38328                    }
38329                    6 => {
38330                        self.write_keyword("TIMESTAMP_MICROS");
38331                        self.write("(");
38332                        self.generate_expression(&e.this)?;
38333                        self.write(")");
38334                    }
38335                    _ => {
38336                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
38337                        self.write_keyword("TIMESTAMP_SECONDS");
38338                        self.write("(CAST(");
38339                        self.generate_expression(&e.this)?;
38340                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
38341                    }
38342                }
38343            }
38344            Some(DialectType::Spark) => {
38345                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
38346                // TIMESTAMP_MILLIS(value) for scale=3
38347                // TIMESTAMP_MICROS(value) for scale=6
38348                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
38349                match scale {
38350                    0 => {
38351                        self.write_keyword("CAST");
38352                        self.write("(");
38353                        self.write_keyword("FROM_UNIXTIME");
38354                        self.write("(");
38355                        self.generate_expression(&e.this)?;
38356                        self.write(") ");
38357                        self.write_keyword("AS TIMESTAMP");
38358                        self.write(")");
38359                    }
38360                    3 => {
38361                        self.write_keyword("TIMESTAMP_MILLIS");
38362                        self.write("(");
38363                        self.generate_expression(&e.this)?;
38364                        self.write(")");
38365                    }
38366                    6 => {
38367                        self.write_keyword("TIMESTAMP_MICROS");
38368                        self.write("(");
38369                        self.generate_expression(&e.this)?;
38370                        self.write(")");
38371                    }
38372                    _ => {
38373                        self.write_keyword("TIMESTAMP_SECONDS");
38374                        self.write("(");
38375                        self.generate_expression(&e.this)?;
38376                        self.write(&format!(" / POWER(10, {}))", scale));
38377                    }
38378                }
38379            }
38380            Some(DialectType::Databricks) => {
38381                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
38382                // TIMESTAMP_MILLIS(value) for scale=3
38383                // TIMESTAMP_MICROS(value) for scale=6
38384                match scale {
38385                    0 => {
38386                        self.write_keyword("CAST");
38387                        self.write("(");
38388                        self.write_keyword("FROM_UNIXTIME");
38389                        self.write("(");
38390                        self.generate_expression(&e.this)?;
38391                        self.write(") ");
38392                        self.write_keyword("AS TIMESTAMP");
38393                        self.write(")");
38394                    }
38395                    3 => {
38396                        self.write_keyword("TIMESTAMP_MILLIS");
38397                        self.write("(");
38398                        self.generate_expression(&e.this)?;
38399                        self.write(")");
38400                    }
38401                    6 => {
38402                        self.write_keyword("TIMESTAMP_MICROS");
38403                        self.write("(");
38404                        self.generate_expression(&e.this)?;
38405                        self.write(")");
38406                    }
38407                    _ => {
38408                        self.write_keyword("TIMESTAMP_SECONDS");
38409                        self.write("(");
38410                        self.generate_expression(&e.this)?;
38411                        self.write(&format!(" / POWER(10, {}))", scale));
38412                    }
38413                }
38414            }
38415            Some(DialectType::Hive) => {
38416                // Hive: FROM_UNIXTIME(value)
38417                if scale == 0 {
38418                    self.write_keyword("FROM_UNIXTIME");
38419                    self.write("(");
38420                    self.generate_expression(&e.this)?;
38421                    self.write(")");
38422                } else {
38423                    self.write_keyword("FROM_UNIXTIME");
38424                    self.write("(");
38425                    self.generate_expression(&e.this)?;
38426                    self.write(&format!(" / POWER(10, {})", scale));
38427                    self.write(")");
38428                }
38429            }
38430            Some(DialectType::Presto) | Some(DialectType::Trino) => {
38431                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
38432                // FROM_UNIXTIME(value) for scale=0
38433                if scale == 0 {
38434                    self.write_keyword("FROM_UNIXTIME");
38435                    self.write("(");
38436                    self.generate_expression(&e.this)?;
38437                    self.write(")");
38438                } else {
38439                    self.write_keyword("FROM_UNIXTIME");
38440                    self.write("(CAST(");
38441                    self.generate_expression(&e.this)?;
38442                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
38443                }
38444            }
38445            Some(DialectType::DuckDB) => {
38446                // DuckDB: TO_TIMESTAMP(value) for scale=0
38447                // EPOCH_MS(value) for scale=3
38448                // MAKE_TIMESTAMP(value) for scale=6
38449                match scale {
38450                    0 => {
38451                        self.write_keyword("TO_TIMESTAMP");
38452                        self.write("(");
38453                        self.generate_expression(&e.this)?;
38454                        self.write(")");
38455                    }
38456                    3 => {
38457                        self.write_keyword("EPOCH_MS");
38458                        self.write("(");
38459                        self.generate_expression(&e.this)?;
38460                        self.write(")");
38461                    }
38462                    6 => {
38463                        self.write_keyword("MAKE_TIMESTAMP");
38464                        self.write("(");
38465                        self.generate_expression(&e.this)?;
38466                        self.write(")");
38467                    }
38468                    _ => {
38469                        self.write_keyword("TO_TIMESTAMP");
38470                        self.write("(");
38471                        self.generate_expression(&e.this)?;
38472                        self.write(&format!(" / POWER(10, {}))", scale));
38473                        self.write_keyword(" AT TIME ZONE");
38474                        self.write(" 'UTC'");
38475                    }
38476                }
38477            }
38478            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
38479                // Doris/StarRocks: FROM_UNIXTIME(value)
38480                self.write_keyword("FROM_UNIXTIME");
38481                self.write("(");
38482                self.generate_expression(&e.this)?;
38483                self.write(")");
38484            }
38485            Some(DialectType::Oracle) => {
38486                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
38487                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
38488                self.generate_expression(&e.this)?;
38489                self.write(" / 86400)");
38490            }
38491            Some(DialectType::Redshift) => {
38492                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
38493                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
38494                self.write("(TIMESTAMP 'epoch' + ");
38495                if scale == 0 {
38496                    self.generate_expression(&e.this)?;
38497                } else {
38498                    self.write("(");
38499                    self.generate_expression(&e.this)?;
38500                    self.write(&format!(" / POWER(10, {}))", scale));
38501                }
38502                self.write(" * INTERVAL '1 SECOND')");
38503            }
38504            Some(DialectType::Exasol) => {
38505                // Exasol: FROM_POSIX_TIME(value)
38506                self.write_keyword("FROM_POSIX_TIME");
38507                self.write("(");
38508                self.generate_expression(&e.this)?;
38509                self.write(")");
38510            }
38511            _ => {
38512                // Default: TO_TIMESTAMP(value[, scale])
38513                self.write_keyword("TO_TIMESTAMP");
38514                self.write("(");
38515                self.generate_expression(&e.this)?;
38516                if let Some(s) = e.scale {
38517                    self.write(", ");
38518                    self.write(&s.to_string());
38519                }
38520                self.write(")");
38521            }
38522        }
38523        Ok(())
38524    }
38525
38526    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
38527        // NAME col VALUE col1, col2, ...
38528        if !matches!(&*e.this, Expression::Null(_)) {
38529            self.write_keyword("NAME");
38530            self.write_space();
38531            self.generate_expression(&e.this)?;
38532        }
38533        if !e.expressions.is_empty() {
38534            self.write_space();
38535            self.write_keyword("VALUE");
38536            self.write_space();
38537            for (i, expr) in e.expressions.iter().enumerate() {
38538                if i > 0 {
38539                    self.write(", ");
38540                }
38541                self.generate_expression(expr)?;
38542            }
38543        }
38544        Ok(())
38545    }
38546
38547    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
38548        // this(expressions) or (this)(expressions)
38549        if e.wrapped.is_some() {
38550            self.write("(");
38551        }
38552        self.generate_expression(&e.this)?;
38553        if e.wrapped.is_some() {
38554            self.write(")");
38555        }
38556        self.write("(");
38557        for (i, expr) in e.expressions.iter().enumerate() {
38558            if i > 0 {
38559                self.write(", ");
38560            }
38561            self.generate_expression(expr)?;
38562        }
38563        self.write(")");
38564        Ok(())
38565    }
38566
38567    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
38568        // USING TEMPLATE this
38569        self.write_keyword("USING TEMPLATE");
38570        self.write_space();
38571        self.generate_expression(&e.this)?;
38572        Ok(())
38573    }
38574
38575    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
38576        // UTC_TIME
38577        self.write_keyword("UTC_TIME");
38578        Ok(())
38579    }
38580
38581    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
38582        if matches!(
38583            self.config.dialect,
38584            Some(crate::dialects::DialectType::ClickHouse)
38585        ) {
38586            self.write_keyword("CURRENT_TIMESTAMP");
38587            self.write("('UTC')");
38588        } else {
38589            self.write_keyword("UTC_TIMESTAMP");
38590        }
38591        Ok(())
38592    }
38593
38594    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
38595        use crate::dialects::DialectType;
38596        // Choose UUID function name based on target dialect
38597        let func_name = match self.config.dialect {
38598            Some(DialectType::Snowflake) => "UUID_STRING",
38599            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
38600            Some(DialectType::BigQuery) => "GENERATE_UUID",
38601            _ => {
38602                if let Some(name) = &e.name {
38603                    name.as_str()
38604                } else {
38605                    "UUID"
38606                }
38607            }
38608        };
38609        self.write_keyword(func_name);
38610        self.write("(");
38611        if let Some(this) = &e.this {
38612            self.generate_expression(this)?;
38613        }
38614        self.write(")");
38615        Ok(())
38616    }
38617
38618    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
38619        // MAP(key1, value1, key2, value2, ...)
38620        self.write_keyword("MAP");
38621        self.write("(");
38622        let mut first = true;
38623        for (k, v) in e.keys.iter().zip(e.values.iter()) {
38624            if !first {
38625                self.write(", ");
38626            }
38627            self.generate_expression(k)?;
38628            self.write(", ");
38629            self.generate_expression(v)?;
38630            first = false;
38631        }
38632        self.write(")");
38633        Ok(())
38634    }
38635
38636    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
38637        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
38638        self.write_keyword("VECTOR_SEARCH");
38639        self.write("(");
38640        self.generate_expression(&e.this)?;
38641        if let Some(col) = &e.column_to_search {
38642            self.write(", ");
38643            self.generate_expression(col)?;
38644        }
38645        if let Some(query_table) = &e.query_table {
38646            self.write(", ");
38647            self.generate_expression(query_table)?;
38648        }
38649        if let Some(query_col) = &e.query_column_to_search {
38650            self.write(", ");
38651            self.generate_expression(query_col)?;
38652        }
38653        if let Some(top_k) = &e.top_k {
38654            self.write(", ");
38655            self.generate_expression(top_k)?;
38656        }
38657        if let Some(dist_type) = &e.distance_type {
38658            self.write(", ");
38659            self.generate_expression(dist_type)?;
38660        }
38661        self.write(")");
38662        Ok(())
38663    }
38664
38665    fn generate_version(&mut self, e: &Version) -> Result<()> {
38666        // Python: f"FOR {expression.name} {kind} {expr}"
38667        // e.this = Identifier("TIMESTAMP" or "VERSION")
38668        // e.kind = "AS OF" (or "BETWEEN", etc.)
38669        // e.expression = the value expression
38670        // Hive does NOT use the FOR prefix for time travel
38671        use crate::dialects::DialectType;
38672        let skip_for = matches!(
38673            self.config.dialect,
38674            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
38675        );
38676        if !skip_for {
38677            self.write_keyword("FOR");
38678            self.write_space();
38679        }
38680        // Extract the name from this (which is an Identifier expression)
38681        match e.this.as_ref() {
38682            Expression::Identifier(ident) => {
38683                self.write_keyword(&ident.name);
38684            }
38685            _ => {
38686                self.generate_expression(&e.this)?;
38687            }
38688        }
38689        self.write_space();
38690        self.write_keyword(&e.kind);
38691        if let Some(expression) = &e.expression {
38692            self.write_space();
38693            self.generate_expression(expression)?;
38694        }
38695        Ok(())
38696    }
38697
38698    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
38699        // Python: return self.sql(expression, "this")
38700        self.generate_expression(&e.this)?;
38701        Ok(())
38702    }
38703
38704    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
38705        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
38706        if e.this.is_some() {
38707            self.write_keyword("NOT VOLATILE");
38708        } else {
38709            self.write_keyword("VOLATILE");
38710        }
38711        Ok(())
38712    }
38713
38714    fn generate_watermark_column_constraint(
38715        &mut self,
38716        e: &WatermarkColumnConstraint,
38717    ) -> Result<()> {
38718        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
38719        self.write_keyword("WATERMARK FOR");
38720        self.write_space();
38721        self.generate_expression(&e.this)?;
38722        self.write_space();
38723        self.write_keyword("AS");
38724        self.write_space();
38725        self.generate_expression(&e.expression)?;
38726        Ok(())
38727    }
38728
38729    fn generate_week(&mut self, e: &Week) -> Result<()> {
38730        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
38731        self.write_keyword("WEEK");
38732        self.write("(");
38733        self.generate_expression(&e.this)?;
38734        if let Some(mode) = &e.mode {
38735            self.write(", ");
38736            self.generate_expression(mode)?;
38737        }
38738        self.write(")");
38739        Ok(())
38740    }
38741
38742    fn generate_when(&mut self, e: &When) -> Result<()> {
38743        // Python: WHEN {matched}{source}{condition} THEN {then}
38744        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
38745        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
38746        self.write_keyword("WHEN");
38747        self.write_space();
38748
38749        // Check if matched
38750        if let Some(matched) = &e.matched {
38751            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
38752            match matched.as_ref() {
38753                Expression::Boolean(b) if b.value => {
38754                    self.write_keyword("MATCHED");
38755                }
38756                _ => {
38757                    self.write_keyword("NOT MATCHED");
38758                }
38759            }
38760        } else {
38761            self.write_keyword("NOT MATCHED");
38762        }
38763
38764        // BY SOURCE / BY TARGET
38765        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
38766        // BY TARGET is the default and typically omitted in output
38767        // Only emit if the dialect supports BY SOURCE syntax
38768        if self.config.matched_by_source {
38769            if let Some(source) = &e.source {
38770                if let Expression::Boolean(b) = source.as_ref() {
38771                    if b.value {
38772                        // BY SOURCE
38773                        self.write_space();
38774                        self.write_keyword("BY SOURCE");
38775                    }
38776                    // BY TARGET (b.value == false) is omitted as it's the default
38777                } else {
38778                    // For non-boolean source, output as BY SOURCE (legacy behavior)
38779                    self.write_space();
38780                    self.write_keyword("BY SOURCE");
38781                }
38782            }
38783        }
38784
38785        // Condition
38786        if let Some(condition) = &e.condition {
38787            self.write_space();
38788            self.write_keyword("AND");
38789            self.write_space();
38790            self.generate_expression(condition)?;
38791        }
38792
38793        self.write_space();
38794        self.write_keyword("THEN");
38795        self.write_space();
38796
38797        // Generate the then expression (could be INSERT, UPDATE, DELETE)
38798        // MERGE actions are stored as Tuples with the action keyword as first element
38799        self.generate_merge_action(&e.then)?;
38800
38801        Ok(())
38802    }
38803
38804    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
38805        match action {
38806            Expression::Tuple(tuple) => {
38807                let elements = &tuple.expressions;
38808                if elements.is_empty() {
38809                    return self.generate_expression(action);
38810                }
38811                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
38812                match &elements[0] {
38813                    Expression::Var(v) if v.this == "INSERT" => {
38814                        self.write_keyword("INSERT");
38815                        // Spark: INSERT * (insert all columns)
38816                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
38817                            self.write(" *");
38818                            if let Some(Expression::Where(w)) = elements.get(2) {
38819                                self.write_space();
38820                                self.generate_where(w)?;
38821                            }
38822                        } else {
38823                            let mut values_idx = 1;
38824                            // Check if second element is column list (Tuple)
38825                            if elements.len() > 1 {
38826                                if let Expression::Tuple(cols) = &elements[1] {
38827                                    // Could be columns or values - if there's a third element, second is columns
38828                                    if elements.len() > 2 {
38829                                        // Second is columns, third is values
38830                                        self.write(" (");
38831                                        for (i, col) in cols.expressions.iter().enumerate() {
38832                                            if i > 0 {
38833                                                self.write(", ");
38834                                            }
38835                                            // Strip MERGE target qualifiers from INSERT column list
38836                                            if !self.merge_strip_qualifiers.is_empty() {
38837                                                let stripped = self.strip_merge_qualifier(col);
38838                                                self.generate_expression(&stripped)?;
38839                                            } else {
38840                                                self.generate_expression(col)?;
38841                                            }
38842                                        }
38843                                        self.write(")");
38844                                        values_idx = 2;
38845                                    } else {
38846                                        // Only two elements: INSERT + values (no explicit columns)
38847                                        values_idx = 1;
38848                                    }
38849                                }
38850                            }
38851                            let mut next_idx = values_idx;
38852                            // Generate VALUES clause
38853                            if values_idx < elements.len()
38854                                && !matches!(&elements[values_idx], Expression::Where(_))
38855                            {
38856                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
38857                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
38858                                if !is_row {
38859                                    self.write_space();
38860                                    self.write_keyword("VALUES");
38861                                }
38862                                self.write(" ");
38863                                if let Expression::Tuple(vals) = &elements[values_idx] {
38864                                    self.write("(");
38865                                    for (i, val) in vals.expressions.iter().enumerate() {
38866                                        if i > 0 {
38867                                            self.write(", ");
38868                                        }
38869                                        self.generate_expression(val)?;
38870                                    }
38871                                    self.write(")");
38872                                } else {
38873                                    self.generate_expression(&elements[values_idx])?;
38874                                }
38875                                next_idx += 1;
38876                            }
38877                            if let Some(Expression::Where(w)) = elements.get(next_idx) {
38878                                self.write_space();
38879                                self.generate_where(w)?;
38880                            }
38881                        } // close else for INSERT * check
38882                    }
38883                    Expression::Var(v) if v.this == "UPDATE" => {
38884                        self.write_keyword("UPDATE");
38885                        // Spark: UPDATE * (update all columns)
38886                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
38887                            self.write(" *");
38888                            if let Some(Expression::Where(w)) = elements.get(2) {
38889                                self.write_space();
38890                                self.generate_where(w)?;
38891                            }
38892                        } else if elements.len() > 1 {
38893                            self.write_space();
38894                            self.write_keyword("SET");
38895                            // In pretty mode, put assignments on next line with extra indent
38896                            if self.config.pretty {
38897                                self.write_newline();
38898                                self.indent_level += 1;
38899                                self.write_indent();
38900                            } else {
38901                                self.write_space();
38902                            }
38903                            if let Expression::Tuple(assignments) = &elements[1] {
38904                                for (i, assignment) in assignments.expressions.iter().enumerate() {
38905                                    if i > 0 {
38906                                        if self.config.pretty {
38907                                            self.write(",");
38908                                            self.write_newline();
38909                                            self.write_indent();
38910                                        } else {
38911                                            self.write(", ");
38912                                        }
38913                                    }
38914                                    // Strip MERGE target qualifiers from left side of UPDATE SET
38915                                    if !self.merge_strip_qualifiers.is_empty() {
38916                                        self.generate_merge_set_assignment(assignment)?;
38917                                    } else {
38918                                        self.generate_expression(assignment)?;
38919                                    }
38920                                }
38921                            } else {
38922                                self.generate_expression(&elements[1])?;
38923                            }
38924                            if self.config.pretty {
38925                                self.indent_level -= 1;
38926                            }
38927                            if let Some(Expression::Where(w)) = elements.get(2) {
38928                                self.write_space();
38929                                self.generate_where(w)?;
38930                            }
38931                        }
38932                    }
38933                    Expression::Var(v) if v.this == "DELETE" => {
38934                        self.write_keyword("DELETE");
38935                        if let Some(Expression::Where(w)) = elements.get(1) {
38936                            self.write_space();
38937                            self.generate_where(w)?;
38938                        }
38939                    }
38940                    _ => {
38941                        // Fallback: generic tuple generation
38942                        self.generate_expression(action)?;
38943                    }
38944                }
38945            }
38946            Expression::Var(v)
38947                if v.this == "INSERT"
38948                    || v.this == "UPDATE"
38949                    || v.this == "DELETE"
38950                    || v.this == "DO NOTHING" =>
38951            {
38952                self.write_keyword(&v.this);
38953            }
38954            _ => {
38955                self.generate_expression(action)?;
38956            }
38957        }
38958        Ok(())
38959    }
38960
38961    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
38962    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
38963        match assignment {
38964            Expression::Eq(eq) => {
38965                // Strip qualifier from the left side if it matches a MERGE target name
38966                let stripped_left = self.strip_merge_qualifier(&eq.left);
38967                self.generate_expression(&stripped_left)?;
38968                self.write(" = ");
38969                self.generate_expression(&eq.right)?;
38970                Ok(())
38971            }
38972            other => self.generate_expression(other),
38973        }
38974    }
38975
38976    /// Strip table qualifier from a column reference if it matches a MERGE target name
38977    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
38978        match expr {
38979            Expression::Column(col) => {
38980                if let Some(ref table_ident) = col.table {
38981                    if self
38982                        .merge_strip_qualifiers
38983                        .iter()
38984                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
38985                    {
38986                        // Strip the table qualifier
38987                        let mut col = col.clone();
38988                        col.table = None;
38989                        return Expression::Column(col);
38990                    }
38991                }
38992                expr.clone()
38993            }
38994            Expression::Dot(dot) => {
38995                // table.column -> column (strip qualifier)
38996                if let Expression::Identifier(id) = &dot.this {
38997                    if self
38998                        .merge_strip_qualifiers
38999                        .iter()
39000                        .any(|n| n.eq_ignore_ascii_case(&id.name))
39001                    {
39002                        return Expression::Identifier(dot.field.clone());
39003                    }
39004                }
39005                expr.clone()
39006            }
39007            _ => expr.clone(),
39008        }
39009    }
39010
39011    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
39012        // Python: return self.expressions(expression, sep=" ", indent=False)
39013        for (i, expr) in e.expressions.iter().enumerate() {
39014            if i > 0 {
39015                // In pretty mode, each WHEN clause on its own line
39016                if self.config.pretty {
39017                    self.write_newline();
39018                    self.write_indent();
39019                } else {
39020                    self.write_space();
39021                }
39022            }
39023            self.generate_expression(expr)?;
39024        }
39025        Ok(())
39026    }
39027
39028    fn generate_where(&mut self, e: &Where) -> Result<()> {
39029        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
39030        self.write_keyword("WHERE");
39031        self.write_space();
39032        self.generate_expression(&e.this)?;
39033        Ok(())
39034    }
39035
39036    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
39037        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
39038        self.write_keyword("WIDTH_BUCKET");
39039        self.write("(");
39040        self.generate_expression(&e.this)?;
39041        if let Some(min_value) = &e.min_value {
39042            self.write(", ");
39043            self.generate_expression(min_value)?;
39044        }
39045        if let Some(max_value) = &e.max_value {
39046            self.write(", ");
39047            self.generate_expression(max_value)?;
39048        }
39049        if let Some(num_buckets) = &e.num_buckets {
39050            self.write(", ");
39051            self.generate_expression(num_buckets)?;
39052        }
39053        self.write(")");
39054        Ok(())
39055    }
39056
39057    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
39058        // Window specification: PARTITION BY ... ORDER BY ... frame
39059        self.generate_window_spec(e)
39060    }
39061
39062    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
39063        // Window specification: PARTITION BY ... ORDER BY ... frame
39064        let mut has_content = false;
39065
39066        // PARTITION BY
39067        if !e.partition_by.is_empty() {
39068            self.write_keyword("PARTITION BY");
39069            self.write_space();
39070            for (i, expr) in e.partition_by.iter().enumerate() {
39071                if i > 0 {
39072                    self.write(", ");
39073                }
39074                self.generate_expression(expr)?;
39075            }
39076            has_content = true;
39077        }
39078
39079        // ORDER BY
39080        if !e.order_by.is_empty() {
39081            if has_content {
39082                self.write_space();
39083            }
39084            self.write_keyword("ORDER BY");
39085            self.write_space();
39086            for (i, ordered) in e.order_by.iter().enumerate() {
39087                if i > 0 {
39088                    self.write(", ");
39089                }
39090                self.generate_expression(&ordered.this)?;
39091                if ordered.desc {
39092                    self.write_space();
39093                    self.write_keyword("DESC");
39094                } else if ordered.explicit_asc {
39095                    self.write_space();
39096                    self.write_keyword("ASC");
39097                }
39098                if let Some(nulls_first) = ordered.nulls_first {
39099                    self.write_space();
39100                    self.write_keyword("NULLS");
39101                    self.write_space();
39102                    if nulls_first {
39103                        self.write_keyword("FIRST");
39104                    } else {
39105                        self.write_keyword("LAST");
39106                    }
39107                }
39108            }
39109            has_content = true;
39110        }
39111
39112        // Frame specification
39113        if let Some(frame) = &e.frame {
39114            if has_content {
39115                self.write_space();
39116            }
39117            self.generate_window_frame(frame)?;
39118        }
39119
39120        Ok(())
39121    }
39122
39123    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
39124        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
39125        self.write_keyword("WITH");
39126        self.write_space();
39127        if e.no.is_some() {
39128            self.write_keyword("NO");
39129            self.write_space();
39130        }
39131        self.write_keyword("DATA");
39132
39133        // statistics
39134        if let Some(statistics) = &e.statistics {
39135            self.write_space();
39136            self.write_keyword("AND");
39137            self.write_space();
39138            // Check if statistics is true or false
39139            match statistics.as_ref() {
39140                Expression::Boolean(b) if !b.value => {
39141                    self.write_keyword("NO");
39142                    self.write_space();
39143                }
39144                _ => {}
39145            }
39146            self.write_keyword("STATISTICS");
39147        }
39148        Ok(())
39149    }
39150
39151    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
39152        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
39153        self.write_keyword("WITH FILL");
39154
39155        if let Some(from_) = &e.from_ {
39156            self.write_space();
39157            self.write_keyword("FROM");
39158            self.write_space();
39159            self.generate_expression(from_)?;
39160        }
39161
39162        if let Some(to) = &e.to {
39163            self.write_space();
39164            self.write_keyword("TO");
39165            self.write_space();
39166            self.generate_expression(to)?;
39167        }
39168
39169        if let Some(step) = &e.step {
39170            self.write_space();
39171            self.write_keyword("STEP");
39172            self.write_space();
39173            self.generate_expression(step)?;
39174        }
39175
39176        if let Some(staleness) = &e.staleness {
39177            self.write_space();
39178            self.write_keyword("STALENESS");
39179            self.write_space();
39180            self.generate_expression(staleness)?;
39181        }
39182
39183        if let Some(interpolate) = &e.interpolate {
39184            self.write_space();
39185            self.write_keyword("INTERPOLATE");
39186            self.write(" (");
39187            // INTERPOLATE items use reversed alias format: name AS expression
39188            self.generate_interpolate_item(interpolate)?;
39189            self.write(")");
39190        }
39191
39192        Ok(())
39193    }
39194
39195    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
39196    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
39197        match expr {
39198            Expression::Alias(alias) => {
39199                // Output as: alias_name AS expression
39200                self.generate_identifier(&alias.alias)?;
39201                self.write_space();
39202                self.write_keyword("AS");
39203                self.write_space();
39204                self.generate_expression(&alias.this)?;
39205            }
39206            Expression::Tuple(tuple) => {
39207                for (i, item) in tuple.expressions.iter().enumerate() {
39208                    if i > 0 {
39209                        self.write(", ");
39210                    }
39211                    self.generate_interpolate_item(item)?;
39212                }
39213            }
39214            other => {
39215                self.generate_expression(other)?;
39216            }
39217        }
39218        Ok(())
39219    }
39220
39221    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
39222        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
39223        self.write_keyword("WITH JOURNAL TABLE");
39224        self.write("=");
39225        self.generate_expression(&e.this)?;
39226        Ok(())
39227    }
39228
39229    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
39230        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
39231        self.generate_expression(&e.this)?;
39232        self.write_space();
39233        self.write_keyword("WITH");
39234        self.write_space();
39235        self.write_keyword(&e.op);
39236        Ok(())
39237    }
39238
39239    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
39240        // Python: return f"WITH {self.expressions(expression, flat=True)}"
39241        self.write_keyword("WITH");
39242        self.write_space();
39243        for (i, expr) in e.expressions.iter().enumerate() {
39244            if i > 0 {
39245                self.write(", ");
39246            }
39247            self.generate_expression(expr)?;
39248        }
39249        Ok(())
39250    }
39251
39252    fn generate_with_schema_binding_property(
39253        &mut self,
39254        e: &WithSchemaBindingProperty,
39255    ) -> Result<()> {
39256        // Python: return f"WITH {self.sql(expression, 'this')}"
39257        self.write_keyword("WITH");
39258        self.write_space();
39259        self.generate_expression(&e.this)?;
39260        Ok(())
39261    }
39262
39263    fn generate_with_system_versioning_property(
39264        &mut self,
39265        e: &WithSystemVersioningProperty,
39266    ) -> Result<()> {
39267        // Python: complex logic for SYSTEM_VERSIONING with options
39268        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
39269        // or SYSTEM_VERSIONING=ON/OFF
39270        // with WITH(...) wrapper if with_ is set
39271
39272        let mut parts = Vec::new();
39273
39274        if let Some(this) = &e.this {
39275            // HISTORY_TABLE=...
39276            let mut s = String::from("HISTORY_TABLE=");
39277            let mut gen = Generator::new();
39278            gen.generate_expression(this)?;
39279            s.push_str(&gen.output);
39280            parts.push(s);
39281        }
39282
39283        if let Some(data_consistency) = &e.data_consistency {
39284            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
39285            let mut gen = Generator::new();
39286            gen.generate_expression(data_consistency)?;
39287            s.push_str(&gen.output);
39288            parts.push(s);
39289        }
39290
39291        if let Some(retention_period) = &e.retention_period {
39292            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
39293            let mut gen = Generator::new();
39294            gen.generate_expression(retention_period)?;
39295            s.push_str(&gen.output);
39296            parts.push(s);
39297        }
39298
39299        self.write_keyword("SYSTEM_VERSIONING");
39300        self.write("=");
39301
39302        if !parts.is_empty() {
39303            self.write_keyword("ON");
39304            self.write("(");
39305            self.write(&parts.join(", "));
39306            self.write(")");
39307        } else if e.on.is_some() {
39308            self.write_keyword("ON");
39309        } else {
39310            self.write_keyword("OFF");
39311        }
39312
39313        // Wrap in WITH(...) if with_ is set
39314        if e.with_.is_some() {
39315            let inner = self.output.clone();
39316            self.output.clear();
39317            self.write("WITH(");
39318            self.write(&inner);
39319            self.write(")");
39320        }
39321
39322        Ok(())
39323    }
39324
39325    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
39326        // Python: f"WITH ({self.expressions(expression, flat=True)})"
39327        self.write_keyword("WITH");
39328        self.write(" (");
39329        for (i, expr) in e.expressions.iter().enumerate() {
39330            if i > 0 {
39331                self.write(", ");
39332            }
39333            self.generate_expression(expr)?;
39334        }
39335        self.write(")");
39336        Ok(())
39337    }
39338
39339    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
39340        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
39341        // return self.func("XMLELEMENT", name, *expression.expressions)
39342        self.write_keyword("XMLELEMENT");
39343        self.write("(");
39344
39345        if e.evalname.is_some() {
39346            self.write_keyword("EVALNAME");
39347        } else {
39348            self.write_keyword("NAME");
39349        }
39350        self.write_space();
39351        self.generate_expression(&e.this)?;
39352
39353        for expr in &e.expressions {
39354            self.write(", ");
39355            self.generate_expression(expr)?;
39356        }
39357        self.write(")");
39358        Ok(())
39359    }
39360
39361    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
39362        // XMLGET(this, expression [, instance])
39363        self.write_keyword("XMLGET");
39364        self.write("(");
39365        self.generate_expression(&e.this)?;
39366        self.write(", ");
39367        self.generate_expression(&e.expression)?;
39368        if let Some(instance) = &e.instance {
39369            self.write(", ");
39370            self.generate_expression(instance)?;
39371        }
39372        self.write(")");
39373        Ok(())
39374    }
39375
39376    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
39377        // Python: this + optional (expr)
39378        self.generate_expression(&e.this)?;
39379        if let Some(expression) = &e.expression {
39380            self.write("(");
39381            self.generate_expression(expression)?;
39382            self.write(")");
39383        }
39384        Ok(())
39385    }
39386
39387    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
39388        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
39389        self.write_keyword("XMLTABLE");
39390        self.write("(");
39391
39392        if self.config.pretty {
39393            self.indent_level += 1;
39394            self.write_newline();
39395            self.write_indent();
39396            self.generate_expression(&e.this)?;
39397
39398            if let Some(passing) = &e.passing {
39399                self.write_newline();
39400                self.write_indent();
39401                self.write_keyword("PASSING");
39402                if let Expression::Tuple(tuple) = passing.as_ref() {
39403                    for expr in &tuple.expressions {
39404                        self.write_newline();
39405                        self.indent_level += 1;
39406                        self.write_indent();
39407                        self.generate_expression(expr)?;
39408                        self.indent_level -= 1;
39409                    }
39410                } else {
39411                    self.write_newline();
39412                    self.indent_level += 1;
39413                    self.write_indent();
39414                    self.generate_expression(passing)?;
39415                    self.indent_level -= 1;
39416                }
39417            }
39418
39419            if e.by_ref.is_some() {
39420                self.write_newline();
39421                self.write_indent();
39422                self.write_keyword("RETURNING SEQUENCE BY REF");
39423            }
39424
39425            if !e.columns.is_empty() {
39426                self.write_newline();
39427                self.write_indent();
39428                self.write_keyword("COLUMNS");
39429                for (i, col) in e.columns.iter().enumerate() {
39430                    self.write_newline();
39431                    self.indent_level += 1;
39432                    self.write_indent();
39433                    self.generate_expression(col)?;
39434                    self.indent_level -= 1;
39435                    if i < e.columns.len() - 1 {
39436                        self.write(",");
39437                    }
39438                }
39439            }
39440
39441            self.indent_level -= 1;
39442            self.write_newline();
39443            self.write_indent();
39444            self.write(")");
39445            return Ok(());
39446        }
39447
39448        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
39449        if let Some(namespaces) = &e.namespaces {
39450            self.write_keyword("XMLNAMESPACES");
39451            self.write("(");
39452            // Unwrap Tuple if present to avoid extra parentheses
39453            if let Expression::Tuple(tuple) = namespaces.as_ref() {
39454                for (i, expr) in tuple.expressions.iter().enumerate() {
39455                    if i > 0 {
39456                        self.write(", ");
39457                    }
39458                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
39459                    // See xmlnamespace_sql in generator.py
39460                    if !matches!(expr, Expression::Alias(_)) {
39461                        self.write_keyword("DEFAULT");
39462                        self.write_space();
39463                    }
39464                    self.generate_expression(expr)?;
39465                }
39466            } else {
39467                // Single namespace - check if DEFAULT
39468                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
39469                    self.write_keyword("DEFAULT");
39470                    self.write_space();
39471                }
39472                self.generate_expression(namespaces)?;
39473            }
39474            self.write("), ");
39475        }
39476
39477        // XPath expression
39478        self.generate_expression(&e.this)?;
39479
39480        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
39481        if let Some(passing) = &e.passing {
39482            self.write_space();
39483            self.write_keyword("PASSING");
39484            self.write_space();
39485            // Unwrap Tuple if present to avoid extra parentheses
39486            if let Expression::Tuple(tuple) = passing.as_ref() {
39487                for (i, expr) in tuple.expressions.iter().enumerate() {
39488                    if i > 0 {
39489                        self.write(", ");
39490                    }
39491                    self.generate_expression(expr)?;
39492                }
39493            } else {
39494                self.generate_expression(passing)?;
39495            }
39496        }
39497
39498        // RETURNING SEQUENCE BY REF
39499        if e.by_ref.is_some() {
39500            self.write_space();
39501            self.write_keyword("RETURNING SEQUENCE BY REF");
39502        }
39503
39504        // COLUMNS clause
39505        if !e.columns.is_empty() {
39506            self.write_space();
39507            self.write_keyword("COLUMNS");
39508            self.write_space();
39509            for (i, col) in e.columns.iter().enumerate() {
39510                if i > 0 {
39511                    self.write(", ");
39512                }
39513                self.generate_expression(col)?;
39514            }
39515        }
39516
39517        self.write(")");
39518        Ok(())
39519    }
39520
39521    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
39522        // Python: return self.connector_sql(expression, "XOR", stack)
39523        // Handles: this XOR expression or expressions joined by XOR
39524        if let Some(this) = &e.this {
39525            self.generate_expression(this)?;
39526            if let Some(expression) = &e.expression {
39527                self.write_space();
39528                self.write_keyword("XOR");
39529                self.write_space();
39530                self.generate_expression(expression)?;
39531            }
39532        }
39533
39534        // Handle multiple expressions
39535        for (i, expr) in e.expressions.iter().enumerate() {
39536            if i > 0 || e.this.is_some() {
39537                self.write_space();
39538                self.write_keyword("XOR");
39539                self.write_space();
39540            }
39541            self.generate_expression(expr)?;
39542        }
39543        Ok(())
39544    }
39545
39546    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
39547        // ZIPF(this, elementcount [, gen])
39548        self.write_keyword("ZIPF");
39549        self.write("(");
39550        self.generate_expression(&e.this)?;
39551        if let Some(elementcount) = &e.elementcount {
39552            self.write(", ");
39553            self.generate_expression(elementcount)?;
39554        }
39555        if let Some(gen) = &e.gen {
39556            self.write(", ");
39557            self.generate_expression(gen)?;
39558        }
39559        self.write(")");
39560        Ok(())
39561    }
39562}
39563
39564impl Default for Generator {
39565    fn default() -> Self {
39566        Self::new()
39567    }
39568}
39569
39570#[cfg(test)]
39571mod tests {
39572    use super::*;
39573    use crate::parser::Parser;
39574
39575    fn roundtrip(sql: &str) -> String {
39576        let ast = Parser::parse_sql(sql).unwrap();
39577        Generator::sql(&ast[0]).unwrap()
39578    }
39579
39580    #[test]
39581    fn test_simple_select() {
39582        let result = roundtrip("SELECT 1");
39583        assert_eq!(result, "SELECT 1");
39584    }
39585
39586    #[test]
39587    fn test_select_from() {
39588        let result = roundtrip("SELECT a, b FROM t");
39589        assert_eq!(result, "SELECT a, b FROM t");
39590    }
39591
39592    #[test]
39593    fn test_select_where() {
39594        let result = roundtrip("SELECT * FROM t WHERE x = 1");
39595        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
39596    }
39597
39598    #[test]
39599    fn test_select_join() {
39600        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
39601        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
39602    }
39603
39604    #[test]
39605    fn test_insert() {
39606        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
39607        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
39608    }
39609
39610    #[test]
39611    fn test_pretty_print() {
39612        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
39613        let result = Generator::pretty_sql(&ast[0]).unwrap();
39614        assert!(result.contains('\n'));
39615    }
39616
39617    #[test]
39618    fn test_window_function() {
39619        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
39620        assert_eq!(
39621            result,
39622            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
39623        );
39624    }
39625
39626    #[test]
39627    fn test_window_function_with_frame() {
39628        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
39629        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
39630    }
39631
39632    #[test]
39633    fn test_aggregate_with_filter() {
39634        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
39635        assert_eq!(
39636            result,
39637            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
39638        );
39639    }
39640
39641    #[test]
39642    fn test_subscript() {
39643        let result = roundtrip("SELECT arr[0]");
39644        assert_eq!(result, "SELECT arr[0]");
39645    }
39646
39647    // DDL tests
39648    #[test]
39649    fn test_create_table() {
39650        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
39651        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
39652    }
39653
39654    #[test]
39655    fn test_create_table_with_constraints() {
39656        let result = roundtrip(
39657            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
39658        );
39659        assert_eq!(
39660            result,
39661            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
39662        );
39663    }
39664
39665    #[test]
39666    fn test_create_table_if_not_exists() {
39667        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
39668        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
39669    }
39670
39671    #[test]
39672    fn test_drop_table() {
39673        let result = roundtrip("DROP TABLE users");
39674        assert_eq!(result, "DROP TABLE users");
39675    }
39676
39677    #[test]
39678    fn test_drop_table_if_exists_cascade() {
39679        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
39680        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
39681    }
39682
39683    #[test]
39684    fn test_alter_table_add_column() {
39685        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
39686        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
39687    }
39688
39689    #[test]
39690    fn test_alter_table_drop_column() {
39691        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
39692        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
39693    }
39694
39695    #[test]
39696    fn test_create_index() {
39697        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
39698        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
39699    }
39700
39701    #[test]
39702    fn test_create_unique_index() {
39703        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
39704        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
39705    }
39706
39707    #[test]
39708    fn test_drop_index() {
39709        let result = roundtrip("DROP INDEX idx_name");
39710        assert_eq!(result, "DROP INDEX idx_name");
39711
39712        let result = roundtrip(r#"DROP INDEX IF EXISTS "idx_tokenKey__pb_users_auth_""#);
39713        assert_eq!(
39714            result,
39715            r#"DROP INDEX IF EXISTS "idx_tokenKey__pb_users_auth_""#
39716        );
39717
39718        let result = roundtrip(r#"DROP INDEX "public"."IdxMixed""#);
39719        assert_eq!(result, r#"DROP INDEX "public"."IdxMixed""#);
39720    }
39721
39722    #[test]
39723    fn test_create_view() {
39724        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
39725        assert_eq!(
39726            result,
39727            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
39728        );
39729    }
39730
39731    #[test]
39732    fn test_drop_view() {
39733        let result = roundtrip("DROP VIEW active_users");
39734        assert_eq!(result, "DROP VIEW active_users");
39735    }
39736
39737    #[test]
39738    fn test_truncate() {
39739        let result = roundtrip("TRUNCATE TABLE users");
39740        assert_eq!(result, "TRUNCATE TABLE users");
39741    }
39742
39743    #[test]
39744    fn test_string_literal_escaping_default() {
39745        // Default: double single quotes
39746        let result = roundtrip("SELECT 'hello'");
39747        assert_eq!(result, "SELECT 'hello'");
39748
39749        // Single quotes are doubled
39750        let result = roundtrip("SELECT 'it''s a test'");
39751        assert_eq!(result, "SELECT 'it''s a test'");
39752    }
39753
39754    #[test]
39755    fn test_not_in_style_prefix_default_generic() {
39756        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
39757        assert_eq!(
39758            result,
39759            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
39760        );
39761    }
39762
39763    #[test]
39764    fn test_not_in_style_infix_generic_override() {
39765        let ast =
39766            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
39767                .unwrap();
39768        let config = GeneratorConfig {
39769            not_in_style: NotInStyle::Infix,
39770            ..Default::default()
39771        };
39772        let mut gen = Generator::with_config(config);
39773        let result = gen.generate(&ast[0]).unwrap();
39774        assert_eq!(
39775            result,
39776            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
39777        );
39778    }
39779
39780    #[test]
39781    fn test_string_literal_escaping_mysql() {
39782        use crate::dialects::DialectType;
39783
39784        let config = GeneratorConfig {
39785            dialect: Some(DialectType::MySQL),
39786            ..Default::default()
39787        };
39788
39789        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
39790        let mut gen = Generator::with_config(config.clone());
39791        let result = gen.generate(&ast[0]).unwrap();
39792        assert_eq!(result, "SELECT 'hello'");
39793
39794        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
39795        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
39796        let mut gen = Generator::with_config(config.clone());
39797        let result = gen.generate(&ast[0]).unwrap();
39798        assert_eq!(result, "SELECT 'it''s'");
39799    }
39800
39801    #[test]
39802    fn test_string_literal_escaping_postgres() {
39803        use crate::dialects::DialectType;
39804
39805        let config = GeneratorConfig {
39806            dialect: Some(DialectType::PostgreSQL),
39807            ..Default::default()
39808        };
39809
39810        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
39811        let mut gen = Generator::with_config(config.clone());
39812        let result = gen.generate(&ast[0]).unwrap();
39813        assert_eq!(result, "SELECT 'hello'");
39814
39815        // PostgreSQL uses doubled quotes for regular strings
39816        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
39817        let mut gen = Generator::with_config(config.clone());
39818        let result = gen.generate(&ast[0]).unwrap();
39819        assert_eq!(result, "SELECT 'it''s'");
39820    }
39821
39822    #[test]
39823    fn test_string_literal_escaping_bigquery() {
39824        use crate::dialects::DialectType;
39825
39826        let config = GeneratorConfig {
39827            dialect: Some(DialectType::BigQuery),
39828            ..Default::default()
39829        };
39830
39831        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
39832        let mut gen = Generator::with_config(config.clone());
39833        let result = gen.generate(&ast[0]).unwrap();
39834        assert_eq!(result, "SELECT 'hello'");
39835
39836        // BigQuery escapes single quotes with backslash
39837        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
39838        let mut gen = Generator::with_config(config.clone());
39839        let result = gen.generate(&ast[0]).unwrap();
39840        assert_eq!(result, "SELECT 'it\\'s'");
39841    }
39842
39843    #[test]
39844    fn test_generate_deep_and_chain_without_stack_growth() {
39845        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
39846            Expression::column("c0"),
39847            Expression::number(0),
39848        )));
39849
39850        for i in 1..2500 {
39851            let predicate = Expression::Eq(Box::new(BinaryOp::new(
39852                Expression::column(format!("c{i}")),
39853                Expression::number(i as i64),
39854            )));
39855            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
39856        }
39857
39858        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
39859        assert!(sql.contains("c2499 = 2499"), "{}", sql);
39860    }
39861
39862    #[test]
39863    fn test_generate_deep_or_chain_without_stack_growth() {
39864        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
39865            Expression::column("c0"),
39866            Expression::number(0),
39867        )));
39868
39869        for i in 1..2500 {
39870            let predicate = Expression::Eq(Box::new(BinaryOp::new(
39871                Expression::column(format!("c{i}")),
39872                Expression::number(i as i64),
39873            )));
39874            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
39875        }
39876
39877        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
39878        assert!(sql.contains("c2499 = 2499"), "{}", sql);
39879    }
39880}